umbraco-example-generator by umbraco/umbraco-cms-backoffice-skills
npx skills add https://github.com/umbraco/umbraco-cms-backoffice-skills --skill umbraco-example-generator为 Umbraco 后台生成完整、可测试的扩展示例,并使用 Umbraco 源代码的开发基础设施运行它们。
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm install
my-extension/
├── index.ts # 必需 - 导出清单
└── my-element.ts # 你的元素
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' }]
}
];
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
// 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:external
打开 http://localhost:5173 - 你的扩展将出现在内容部分。
Umbraco 源代码 (Umbraco-CMS/src/Umbraco.Web.UI.Client) 提供了两种加载扩展的方式:
npm run example)放置在 Umbraco 源代码 examples/ 文件夹内的示例。
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run example
# 从示例列表中选择
工作原理:设置 VITE_EXAMPLE_PATH 并导入 ./examples/{name}/index.ts
npm run dev:external)来自文件系统任意位置的扩展 - 非常适合开发包。
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/path/to/your/extension npm run dev:external
工作原理:
VITE_UMBRACO_USE_MSW=on(模拟 API)@external-extension 别名@external-extension/index.ts 并使用 umbExtensionsRegistry 注册导出@umbraco-cms/backoffice/* 导入(避免重复的元素注册)// 来自 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.ts 必须导出清单(数组或对象),这些清单会被自动注册。
克隆并设置 Umbraco 源代码:
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm install
你的扩展需要以下最小结构:
my-extension/
├── index.ts # 导出清单数组(必需)
├── my-element.ts # 你的元素
├── my-context.ts # 上下文(如果需要)
├── package.json # 可选 - 用于 IDE 支持和测试
├── tsconfig.json # 可选 - 用于 IDE 支持
└── README.md # 文档
你的 index.ts 必须导出将被注册的清单:
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'
}
]
}
];
{
"name": "my-extension",
"type": "module",
"devDependencies": {
"@umbraco-cms/backoffice": "^17.0.0",
"typescript": "~5.8.0"
}
}
重要:@umbraco-cms/backoffice 依赖项仅用于 IDE TypeScript 支持。在运行时,导入是从主 Umbraco 项目解析的。
cd /path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/absolute/path/to/my-extension npm run dev:external
导航到 http://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/testing 添加单元测试。完整设置请参见 umbraco-unit-testing 技能。
npm install --save-dev @open-wc/testing @web/test-runner @web/test-runner-playwright
添加针对模拟后台运行的端到端测试。模式请参见 umbraco-mocked-backoffice 技能。
npm install --save-dev @playwright/test
npx playwright install chromium
位置:./examples/workspace-feature-toggle/
一个完整的独立示例,演示了:
使用 UmbArrayState 的工作区上下文
使用上下文的工作区视图
执行上下文方法的工作区操作
显示摘要的工作区页脚应用
38 个单元测试 + 13 个端到端测试
cd examples/workspace-feature-toggle npm install npm test # 单元测试 npm run test:e2e # 端到端测试(需要运行模拟后台)
位置:Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/
27 个官方示例,涵盖所有扩展类型。运行任何示例:
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run example
# 从列表中选择
| 项目 | 约定 | 示例 |
|---|---|---|
| 目录 | 描述功能的 kebab-case | workspace-context-counter |
| 别名前缀 | example. | example.workspaceView.counter |
| 元素前缀 | example- | example-counter-view |
| 上下文令牌 | EXAMPLE_ + SCREAMING_CASE | EXAMPLE_COUNTER_CONTEXT |
index.ts 是否导出了 manifests 数组VITE_EXTERNAL_EXTENSION 中的路径是否为绝对路径📦 Loading external extension from: 消息导入应使用 @umbraco-cms/backoffice/*。Vite 插件会从主项目解析这些导入。
你的扩展的 node_modules 正在被使用,而不是主项目的。external-extension-resolver 插件应该处理这个问题,但请确保:
npm run dev:external@umbraco-cms/backoffice/* 而不是指向 node_modules 的相对路径确保文件位于 VITE_EXTERNAL_EXTENSION 指定的路径内。只有该目录树中的文件会被监视。
每周安装次数
76
代码仓库
GitHub 星标数
17
首次出现
2026年2月4日
安全审计
已安装于
github-copilot55
codex25
opencode24
cursor22
gemini-cli21
amp21
Generate complete, testable example extensions for the Umbraco backoffice and run them using the Umbraco source's dev infrastructure.
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm install
my-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:external
Open http://localhost:5173 - your extension appears in the Content section.
The Umbraco source (Umbraco-CMS/src/Umbraco.Web.UI.Client) provides two ways to load extensions:
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
npm run dev:external)Extensions from any location on your filesystem - perfect for developing packages.
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/path/to/your/extension npm run dev:external
How it works :
VITE_UMBRACO_USE_MSW=on (mocked APIs)@external-extension alias pointing to your extension path@external-extension/index.ts and registers exports with umbExtensionsRegistry@umbraco-cms/backoffice/* imports from the main project (avoids duplicate element registrations)// 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);
}
});
}
}
Key point : Your index.ts must export manifests (arrays or objects) that get registered automatically.
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
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
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'
}
]
}
];
{
"name": "my-extension",
"type": "module",
"devDependencies": {
"@umbraco-cms/backoffice": "^17.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.
cd /path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/absolute/path/to/my-extension npm run dev:external
Navigate to http://localhost:5173 - your extension is loaded automatically.
Changes to your extension files trigger hot reload - no restart needed.
// 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 };
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
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
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)
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
| 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 |
index.ts exports a manifests arrayVITE_EXTERNAL_EXTENSION is absolute📦 Loading external extension from: messageImports should use @umbraco-cms/backoffice/*. The Vite plugin resolves these from the main project.
Your extension's node_modules is being used instead of the main project's. The external-extension-resolver plugin should handle this, but ensure:
npm run dev:external@umbraco-cms/backoffice/* not relative paths to node_modulesEnsure the file is within the path specified by VITE_EXTERNAL_EXTENSION. Only files in that directory tree are watched.
Weekly Installs
76
Repository
GitHub Stars
17
First Seen
Feb 4, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot55
codex25
opencode24
cursor22
gemini-cli21
amp21
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
122,000 周安装
DuckDuckGo搜索技能 - 开源网络搜索工具,集成DuckDuckGo API,支持多种开发环境
106 周安装
Nuxt 4 数据管理:useFetch、useAsyncData、useState 组合式函数与 SSR 状态管理
70 周安装
Tauri v2 前端调用 Rust 后端函数教程:命令系统与参数传递详解
108 周安装
定价策略师指南:SaaS定价模型、价值指标与分层定价策略框架
106 周安装
Godot组合式架构技能包:组件化游戏开发最佳实践与脚本示例
108 周安装
Firebase 开发指南:Firestore 数据建模、安全规则与实时应用开发
108 周安装
EXAMPLE_COUNTER_CONTEXT |