Loading...
Loading...
Generate testable example extensions and run them in the Umbraco backoffice
npx skill4agent add umbraco/umbraco-cms-backoffice-skills umbraco-example-generatorgit clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm installmy-extension/
├── index.ts # REQUIRED - exports manifests
└── my-element.ts # Your element(s)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' }]
}
];// 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>`;
}
}cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/full/path/to/my-extension npm run dev:externalhttp://localhost:5173Umbraco-CMS/src/Umbraco.Web.UI.Clientnpm run exampleexamples/cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run example
# Select from list of examplesVITE_EXAMPLE_PATH./examples/{name}/index.tsnpm run dev:externalcd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/path/to/your/extension npm run dev:externalVITE_UMBRACO_USE_MSW=on@external-extension@external-extension/index.tsumbExtensionsRegistry@umbraco-cms/backoffice/*// From Umbraco-CMS/src/Umbraco.Web.UI.Client/index.ts
if (import.meta.env.VITE_EXTERNAL_EXTENSION) {
const js = await import('@external-extension/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);
}
});
}
}index.tsgit clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm installmy-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 # Documentationindex.tsimport './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'
}
]
}
];{
"name": "my-extension",
"type": "module",
"devDependencies": {
"@umbraco-cms/backoffice": "^17.0.0",
"typescript": "~5.8.0"
}
}@umbraco-cms/backofficecd /path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/absolute/path/to/my-extension npm run dev:externalhttp://localhost:5173// 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;
}
}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;
}
}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 };@open-wc/testingnpm install --save-dev @open-wc/testing @web/test-runner @web/test-runner-playwrightnpm install --save-dev @playwright/test
npx playwright install chromium./examples/workspace-feature-toggle/UmbArrayStatecd examples/workspace-feature-toggle
npm install
npm test # Unit tests
npm run test:e2e # E2E tests (requires mocked backoffice running)Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run example
# Select from list| Item | Convention | Example |
|---|---|---|
| Directory | kebab-case describing feature | |
| Alias prefix | | |
| Element prefix | | |
| Context token | | |
index.tsmanifestsVITE_EXTERNAL_EXTENSION📦 Loading external extension from:@umbraco-cms/backoffice/*node_modulesexternal-extension-resolvernpm run dev:external@umbraco-cms/backoffice/*VITE_EXTERNAL_EXTENSION