umbraco-example-generator
Generate testable example extensions and run them in the Umbraco backoffice
How do I install this agent skill?
npx skills add https://github.com/umbraco/umbraco-cms-backoffice-skills --skill umbraco-example-generatorIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The analyzed skill is safe. It outlines development practices, setup procedures, and code architecture blueprints for generating testable frontend extensions within the Umbraco backoffice environment. No security risks were identified.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
- Runlayerwarn
14/14 files flagged
What does this agent skill do?
Umbraco Example Generator
Generate complete, testable example extensions for the Umbraco backoffice and run them using the Umbraco source's dev infrastructure.
When to Use
- Creating demonstration extensions
- Building testable extension examples
- Rapid development with hot reload
- Testing extensions without .NET backend
Related Skills
- umbraco-unit-testing - Add unit tests to examples
- umbraco-mocked-backoffice - E2E testing patterns
- umbraco-backoffice - Extension type blueprints
Quick Start
1. Clone Umbraco source (one-time setup)
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm install
2. Create your extension folder
my-extension/
├── index.ts # REQUIRED - exports manifests
└── my-element.ts # Your element(s)
3. Create index.ts (exports manifests)
import './my-element.js';
export const manifests = [
{
type: 'dashboard',
alias: 'My.Dashboard',
name: 'My Dashboard',
element: 'my-element',
meta: { label: 'My Dashboard', pathname: 'my-dashboard' },
conditions: [{ alias: 'Umb.Condition.SectionAlias', match: 'Umb.Section.Content' }]
}
];
4. Create your element
// my-element.ts
import { LitElement, html, customElement } from '@umbraco-cms/backoffice/external/lit';
@customElement('my-element')
export class MyElement extends LitElement {
render() {
return html`<uui-box headline="Hello">It works!</uui-box>`;
}
}
5. Run it
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXAMPLE_PATH=/full/path/to/my-extension VITE_UMBRACO_USE_MSW=on npm run dev
Open http://localhost:5173 - your extension appears in the Content section.
How It Works
The Umbraco source (Umbraco-CMS/src/Umbraco.Web.UI.Client) provides two ways to load extensions:
1. Internal Examples (npm run example)
Examples placed in the examples/ folder inside the Umbraco source.
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run example
# Select from list of examples
How it works: Sets VITE_EXAMPLE_PATH and imports ./examples/{name}/index.ts
2. External Extensions (absolute VITE_EXAMPLE_PATH)
Extensions from any location on your filesystem - perfect for developing packages.
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXAMPLE_PATH=/path/to/your/extension VITE_UMBRACO_USE_MSW=on npm run dev
How it works:
VITE_UMBRACO_USE_MSW=onmocks the core Umbraco APIs- An absolute
VITE_EXAMPLE_PATHis served through Vite's/@fs/prefix index.tsimports<VITE_EXAMPLE_PATH>/index.tsand registers its exports withumbExtensionsRegistry- A shared-package resolver maps
@umbraco-cms/backoffice,litand@umbraco-ui/uuito the client's copy (avoids duplicate Lit/UUI)
Absolute-path support (steps 2 & 4) needs a v18 client built with external-example support. When
running this repo's mocked E2E suites, the Playwright harness injects that support into the client
at UMBRACO_CLIENT_PATH automatically and reverts afterwards — see the umbraco-mocked-backoffice skill.
Extension Loading (index.ts)
// From Umbraco-CMS/src/Umbraco.Web.UI.Client/index.ts
if (import.meta.env.VITE_EXAMPLE_PATH) {
const examplePath = import.meta.env.VITE_EXAMPLE_PATH;
// Absolute paths (external extensions) are served via Vite's /@fs/ prefix; relative paths load from ./
const importPath = examplePath.startsWith('/') ? '/@fs' + examplePath : './' + examplePath;
const js = await import(importPath + '/index.ts');
if (js) {
Object.keys(js).forEach((key) => {
const value = js[key];
if (Array.isArray(value)) {
umbExtensionsRegistry.registerMany(value);
} else if (typeof value === 'object') {
umbExtensionsRegistry.register(value);
}
});
}
}
Key point: Your index.ts must export manifests (arrays or objects) that get registered automatically.
Setup
Prerequisites
Clone and set up the Umbraco source:
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm install
Extension Structure
Your extension needs this minimal structure:
my-extension/
├── index.ts # Exports manifests array (REQUIRED)
├── my-element.ts # Your element(s)
├── my-context.ts # Context (if needed)
├── package.json # Optional - for IDE support and tests
├── tsconfig.json # Optional - for IDE support
└── README.md # Documentation
Required: index.ts
Your index.ts must export manifests that will be registered:
import './my-dashboard.element.js';
export const manifests = [
{
type: 'dashboard',
alias: 'My.Dashboard',
name: 'My Dashboard',
element: 'my-dashboard',
weight: 100,
meta: {
label: 'My Dashboard',
pathname: 'my-dashboard'
},
conditions: [
{
alias: 'Umb.Condition.SectionAlias',
match: 'Umb.Section.Content'
}
]
}
];
Optional: package.json (for IDE support)
{
"name": "my-extension",
"type": "module",
"devDependencies": {
"@umbraco-cms/backoffice": "^18.0.0",
"typescript": "~5.8.0"
}
}
Important: The @umbraco-cms/backoffice dependency is only for IDE TypeScript support. At runtime, imports are resolved from the main Umbraco project.
Running Your Extension
Start the mocked backoffice
cd /path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXAMPLE_PATH=/absolute/path/to/my-extension VITE_UMBRACO_USE_MSW=on npm run dev
Open in browser
Navigate to http://localhost:5173 - your extension is loaded automatically.
Hot reload
Changes to your extension files trigger hot reload - no restart needed.
Patterns
Basic Element
// my-dashboard.element.ts
import { LitElement, html, css, customElement } from '@umbraco-cms/backoffice/external/lit';
@customElement('my-dashboard')
export class MyDashboardElement extends LitElement {
static override styles = css`
:host {
display: block;
padding: var(--uui-size-layout-1);
}
`;
override render() {
return html`
<uui-box headline="My Extension">
<p>Running in the mocked backoffice!</p>
</uui-box>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'my-dashboard': MyDashboardElement;
}
}
Element with Context
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { EXAMPLE_MY_CONTEXT } from './my-context.js';
@customElement('example-my-feature-view')
export class ExampleMyFeatureViewElement extends UmbLitElement {
@state()
private _value?: string;
constructor() {
super();
this.consumeContext(EXAMPLE_MY_CONTEXT, (context) => {
this.observe(context.value, (value) => {
this._value = value;
});
});
}
override render() {
return html`
<uui-box headline="My Feature Example">
<p>Current value: ${this._value ?? 'Loading...'}</p>
</uui-box>
`;
}
}
export default ExampleMyFeatureViewElement;
declare global {
interface HTMLElementTagNameMap {
'example-my-feature-view': ExampleMyFeatureViewElement;
}
}
Context
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbStringState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class ExampleMyContext extends UmbContextBase {
#value = new UmbStringState('initial');
readonly value = this.#value.asObservable();
constructor(host: UmbControllerHost) {
super(host, EXAMPLE_MY_CONTEXT);
}
setValue(value: string) {
this.#value.setValue(value);
}
getValue() {
return this.#value.getValue();
}
public override destroy(): void {
this.#value.destroy();
super.destroy();
}
}
export const EXAMPLE_MY_CONTEXT = new UmbContextToken<ExampleMyContext>(
'ExampleMyContext'
);
export { ExampleMyContext as api };
Adding Tests
Unit Tests
Add unit tests using @open-wc/testing. See umbraco-unit-testing skill for full setup.
npm install --save-dev @open-wc/testing @web/test-runner @web/test-runner-playwright
E2E Tests (Playwright)
Add E2E tests that run against the mocked backoffice. See umbraco-mocked-backoffice skill for patterns.
npm install --save-dev @playwright/test
npx playwright install chromium
Examples
Reference Example
Location: ./examples/workspace-feature-toggle/
A complete standalone example demonstrating:
- Workspace context with
UmbArrayState - Workspace view consuming context
- Workspace action executing context methods
- Workspace footer app showing summary
- 38 unit tests + 13 E2E tests
cd examples/workspace-feature-toggle
npm install
npm test # Unit tests
npm run test:e2e # E2E tests (requires mocked backoffice running)
Official Umbraco Examples
Location: Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/
27 official examples covering all extension types. Run any example:
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run example
# Select from list
Naming Conventions
| Item | Convention | Example |
|---|---|---|
| Directory | kebab-case describing feature | workspace-context-counter |
| Alias prefix | example. | example.workspaceView.counter |
| Element prefix | example- | example-counter-view |
| Context token | EXAMPLE_ + SCREAMING_CASE | EXAMPLE_COUNTER_CONTEXT |
Troubleshooting
Extension not appearing
- Check
index.tsexports amanifestsarray - Verify the path in
VITE_EXAMPLE_PATHis absolute - Check browser console for
📦 Loading external example from:message - Ensure condition matches the section you're viewing
Import errors
Imports should use @umbraco-cms/backoffice/*. The Vite plugin resolves these from the main project.
"CustomElementRegistry" already defined
Your extension's node_modules is being used instead of the main project's. The external-example-resolver plugin should handle this, but ensure:
- You're using
VITE_EXAMPLE_PATH=<abs> VITE_UMBRACO_USE_MSW=on npm run dev - Imports use
@umbraco-cms/backoffice/*not relative paths to node_modules
Changes not hot reloading
Ensure the file is within the path specified by VITE_EXAMPLE_PATH. Only files in that directory tree are watched.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/umbraco/umbraco-cms-backoffice-skills/umbraco-example-generator">View umbraco-example-generator on skillZs</a>