umbraco-tree by umbraco/umbraco-cms-backoffice-skills
npx skills add https://github.com/umbraco/umbraco-cms-backoffice-skills --skill umbraco-tree在 Umbraco 中,树是一种注册在后台扩展注册表中的节点层次结构。树用于显示组织好的内容层次结构,并且可以在后台的任何地方使用 <umb-tree /> 元素进行渲染。它们需要一个数据源实现来获取根项目、子项目和祖先项目。
在实现之前,请务必获取最新的文档:
树和工作区是紧密耦合的。 当使用 kind: 'default' 树项目时:
树项目需要工作区 - 点击树项目会导航到该实体类型的工作区。如果没有为 注册工作区,点击会导致"无限加载"
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
entityType工作区必须是 kind: 'routable' - 为了获得正确的树项目选择状态以及在同一类型项目之间导航,请使用 kind: 'routable' 工作区(而不是 kind: 'default')
实体类型将树与工作区链接起来 - 树项目数据中的 entityType 必须与工作区清单中的 entityType 匹配
当实现带有可点击项目的树时,请同时参考 umbraco-workspace 技能。
现代树使用 2-3 个文件:
my-tree/
├── manifest.ts # 注册仓库和树
├── tree.repository.ts # 仓库 + 内联数据源
└── types.ts # 类型定义(可选)
Umbraco 源码中包含一个可运行的示例:
位置 : /Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/tree/
此示例演示了一个包含数据源、仓库和菜单集成的完整自定义树。请研究此示例以了解生产模式。
如果在实现树时需要解释这些基础概念,请参考以下技能:
仓库模式 : 当实现树数据源、仓库、数据获取或 CRUD 操作时
umbraco-repository-pattern上下文 API : 当实现仓库上下文或解释仓库如何连接到 UI 组件时
umbraco-context-api状态管理 : 当实现响应式树更新、可观察对象或管理树状态时
umbraco-state-managementmanifest.ts, tree.repository.ts (包含内联数据源)tree.store.ts (已弃用), 单独的 tree.data-source.ts (可以内联)umbraco-workspace 技能)要在根级别显示树项目(没有父文件夹),请在 menuItem 清单上使用 hideTreeRoot: true:
// 正确 - hideTreeRoot 在 menuItem 上
{
type: 'menuItem',
kind: 'tree',
alias: 'My.MenuItem.Tree',
meta: {
treeAlias: 'My.Tree',
menus: ['My.Menu'],
hideTreeRoot: true, // 在根级别显示项目
},
}
// 错误 - hideTreeRoot 在 tree 上(无效)
{
type: 'tree',
meta: {
hideTreeRoot: true, // 这没有任何效果!
},
}
export const manifests: UmbExtensionManifest[] = [
// 仓库
{
type: 'repository',
alias: 'My.Tree.Repository',
name: 'My Tree Repository',
api: () => import('./tree.repository.js'),
},
// 树
{
type: 'tree',
kind: 'default',
alias: 'My.Tree',
name: 'My Tree',
meta: {
repositoryAlias: 'My.Tree.Repository',
},
},
// 树项目 - 当工作区存在时使用 kind: 'default'
{
type: 'treeItem',
kind: 'default',
alias: 'My.TreeItem',
name: 'My Tree Item',
forEntityTypes: ['my-entity'],
},
// MenuItem - 在此处设置 hideTreeRoot
{
type: 'menuItem',
kind: 'tree',
alias: 'My.MenuItem.Tree',
meta: {
treeAlias: 'My.Tree',
menus: ['My.Menu'],
hideTreeRoot: true,
},
},
];
现代简化模式 - 所有内容在一个文件中:
import { UmbTreeRepositoryBase, UmbTreeServerDataSourceBase } from '@umbraco-cms/backoffice/tree';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { MyTreeItemModel, MyTreeRootModel } from './types.js';
import { MY_ROOT_ENTITY_TYPE, MY_ENTITY_TYPE } from './entity.js';
// 使用辅助基类的简单数据源类
class MyTreeDataSource extends UmbTreeServerDataSourceBase<any, MyTreeItemModel> {
constructor(host: UmbControllerHost) {
super(host, {
getRootItems: async (args) => {
// 从 API 获取或返回模拟数据
const items: MyTreeItemModel[] = [
{
unique: 'item-1',
parent: { unique: null, entityType: MY_ROOT_ENTITY_TYPE },
entityType: MY_ENTITY_TYPE,
name: 'Item 1',
hasChildren: false,
isFolder: false,
icon: 'icon-document',
},
];
return { data: { items, total: items.length } };
},
getChildrenOf: async (args) => {
// 返回父项的子项
return { data: { items: [], total: 0 } };
},
getAncestorsOf: async (args) => {
// 返回祖先路径
return { data: [] };
},
mapper: (item: any) => item, // 此示例的身份映射器
});
}
}
// 仓库
export class MyTreeRepository
extends UmbTreeRepositoryBase<MyTreeItemModel, MyTreeRootModel>
implements UmbApi
{
constructor(host: UmbControllerHost) {
super(host, MyTreeDataSource);
}
async requestTreeRoot() {
const data: MyTreeRootModel = {
unique: null,
entityType: MY_ROOT_ENTITY_TYPE,
name: 'My Tree',
hasChildren: true,
isFolder: true,
};
return { data };
}
}
export { MyTreeRepository as api };
为什么这更简单:
UmbTreeServerDataSourceBase 辅助类(传递函数,而不是方法)对于包含 API 调用的复杂树,您仍然可以分离到不同的文件中,但这不是必需的。
就是这样!请始终获取最新的文档,保持示例最简化,生成完整可运行的代码。
每周安装次数
72
仓库
GitHub 星标数
15
首次出现
2026年2月4日
安全审计
安装在
github-copilot53
cursor25
opencode23
codex23
gemini-cli21
amp21
A tree in Umbraco is a hierarchical structure of nodes registered in the Backoffice extension registry. Trees display organized content hierarchies and can be rendered anywhere in the Backoffice using the <umb-tree /> element. They require a data source implementation to fetch root items, children, and ancestors.
Always fetch the latest docs before implementing:
Trees and workspaces are tightly coupled. When using kind: 'default' tree items:
Tree items REQUIRE workspaces - Clicking a tree item navigates to a workspace for that entity type. Without a workspace registered for the entityType, clicking causes "forever loading"
Workspaces must bekind: 'routable' - For proper tree item selection state and navigation between items of the same type, use kind: 'routable' workspaces (not kind: 'default')
Entity types link trees to workspaces - The entityType in your tree item data must match the entityType in your workspace manifest
When implementing trees with clickable items, also reference the umbraco-workspace skill.
Modern trees use 2-3 files:
my-tree/
├── manifest.ts # Registers repository and tree
├── tree.repository.ts # Repository + inline data source
└── types.ts # Type definitions (optional)
The Umbraco source includes a working example:
Location : /Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/tree/
This example demonstrates a complete custom tree with data source, repository, and menu integration. Study this for production patterns.
If you need to explain these foundational concepts when implementing trees, reference these skills:
Repository Pattern : When implementing tree data sources, repositories, data fetching, or CRUD operations
umbraco-repository-patternContext API : When implementing repository contexts or explaining how repositories connect to UI components
umbraco-context-apiState Management : When implementing reactive tree updates, observables, or managing tree state
umbraco-state-managementmanifest.ts, tree.repository.ts (with inline data source)tree.store.ts (deprecated), separate tree.data-source.ts (can inline)umbraco-workspace skill)To show tree items at root level (without a parent folder), use hideTreeRoot: true on the menuItem manifest:
// CORRECT - hideTreeRoot on menuItem
{
type: 'menuItem',
kind: 'tree',
alias: 'My.MenuItem.Tree',
meta: {
treeAlias: 'My.Tree',
menus: ['My.Menu'],
hideTreeRoot: true, // Shows items at root level
},
}
// WRONG - hideTreeRoot on tree (has no effect)
{
type: 'tree',
meta: {
hideTreeRoot: true, // This does nothing!
},
}
export const manifests: UmbExtensionManifest[] = [
// Repository
{
type: 'repository',
alias: 'My.Tree.Repository',
name: 'My Tree Repository',
api: () => import('./tree.repository.js'),
},
// Tree
{
type: 'tree',
kind: 'default',
alias: 'My.Tree',
name: 'My Tree',
meta: {
repositoryAlias: 'My.Tree.Repository',
},
},
// Tree Items - use kind: 'default' when workspaces exist
{
type: 'treeItem',
kind: 'default',
alias: 'My.TreeItem',
name: 'My Tree Item',
forEntityTypes: ['my-entity'],
},
// MenuItem - hideTreeRoot here
{
type: 'menuItem',
kind: 'tree',
alias: 'My.MenuItem.Tree',
meta: {
treeAlias: 'My.Tree',
menus: ['My.Menu'],
hideTreeRoot: true,
},
},
];
Modern simplified pattern - everything in one file:
import { UmbTreeRepositoryBase, UmbTreeServerDataSourceBase } from '@umbraco-cms/backoffice/tree';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { MyTreeItemModel, MyTreeRootModel } from './types.js';
import { MY_ROOT_ENTITY_TYPE, MY_ENTITY_TYPE } from './entity.js';
// Data source as simple class using helper base
class MyTreeDataSource extends UmbTreeServerDataSourceBase<any, MyTreeItemModel> {
constructor(host: UmbControllerHost) {
super(host, {
getRootItems: async (args) => {
// Fetch from API or return mock data
const items: MyTreeItemModel[] = [
{
unique: 'item-1',
parent: { unique: null, entityType: MY_ROOT_ENTITY_TYPE },
entityType: MY_ENTITY_TYPE,
name: 'Item 1',
hasChildren: false,
isFolder: false,
icon: 'icon-document',
},
];
return { data: { items, total: items.length } };
},
getChildrenOf: async (args) => {
// Return children for parent
return { data: { items: [], total: 0 } };
},
getAncestorsOf: async (args) => {
// Return ancestor path
return { data: [] };
},
mapper: (item: any) => item, // Identity mapper for this example
});
}
}
// Repository
export class MyTreeRepository
extends UmbTreeRepositoryBase<MyTreeItemModel, MyTreeRootModel>
implements UmbApi
{
constructor(host: UmbControllerHost) {
super(host, MyTreeDataSource);
}
async requestTreeRoot() {
const data: MyTreeRootModel = {
unique: null,
entityType: MY_ROOT_ENTITY_TYPE,
name: 'My Tree',
hasChildren: true,
isFolder: true,
};
return { data };
}
}
export { MyTreeRepository as api };
Why this is simpler:
UmbTreeServerDataSourceBase helper (pass functions, not methods)For complex trees with API calls , you can still separate into different files, but it's not required.
That's it! Always fetch fresh docs, keep examples minimal, generate complete working code.
Weekly Installs
72
Repository
GitHub Stars
15
First Seen
Feb 4, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykPass
Installed on
github-copilot53
cursor25
opencode23
codex23
gemini-cli21
amp21
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
120,000 周安装