Loading...
Loading...
Implement workspaces in Umbraco backoffice using official docs
npx skill4agent add umbraco/umbraco-cms-backoffice-skills umbraco-workspacekind: 'default'kind: 'routable'| Feature | | |
|---|---|---|
| Use case | Static pages, root workspaces | Entity editing with unique IDs |
| Tree integration | No selection state | Proper selection state |
| URL routing | No route params | Supports |
| Context | Simple | Has |
kind: 'routable'kind: 'routable'import { UmbWorkspaceRouteManager, UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { LitElement, html } from '@umbraco-cms/backoffice/external/lit';
import { customElement } from '@umbraco-cms/backoffice/external/lit';
// Workspace editor element that renders views
@customElement('my-workspace-editor')
class MyWorkspaceEditorElement extends LitElement {
override render() {
return html`<umb-workspace-editor></umb-workspace-editor>`;
}
}
interface MyEntityData {
unique: string;
name?: string;
}
export class MyWorkspaceContext extends UmbContextBase {
public readonly workspaceAlias = 'My.Workspace';
#data = new UmbObjectState<MyEntityData | undefined>(undefined);
readonly data = this.#data.asObservable();
// CRITICAL: Observable unique for workspace views to consume
readonly unique = this.#data.asObservablePart((data) => data?.unique);
readonly name = this.#data.asObservablePart((data) => data?.name);
readonly routes = new UmbWorkspaceRouteManager(this);
constructor(host: UmbControllerHost) {
super(host, UMB_WORKSPACE_CONTEXT);
// Route pattern for tree item navigation
this.routes.setRoutes([
{
path: 'edit/:unique',
component: MyWorkspaceEditorElement,
setup: (_component, info) => {
const unique = info.match.params.unique;
this.load(unique);
},
},
]);
}
async load(unique: string) {
// Load entity data and update state
this.#data.setValue({ unique });
}
getUnique() {
return this.#data.getValue()?.unique;
}
getEntityType() {
return 'my-entity'; // Must match tree item entityType!
}
public override destroy(): void {
this.#data.destroy();
super.destroy();
}
}
export { MyWorkspaceContext as api };uniqueimport { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
override connectedCallback() {
super.connectedCallback();
this.consumeContext(UMB_WORKSPACE_CONTEXT, (context) => {
if (!context) return;
// Observe unique - will fire when navigating between items
this.observe((context as any).unique, (unique: string | null) => {
if (unique) {
this._loadData(unique);
}
});
});
}/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/workspace-context-counter//Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/workspace-context-initial-name//Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/workspace-view-hint/umbraco-context-apiumbraco-state-managementumbraco-umbraco-elementumbraco-controllersumbraco-treekind: 'routable'kind: 'default'.csprojexport const manifests: UmbExtensionManifest[] = [
// Routable workspace for tree integration
{
type: 'workspace',
kind: 'routable',
alias: 'My.Workspace',
name: 'My Workspace',
api: () => import('./my-workspace.context.js'),
meta: {
entityType: 'my-entity', // Must match tree item entityType!
},
},
// Workspace view
{
type: 'workspaceView',
alias: 'My.WorkspaceView',
name: 'My Workspace View',
element: () => import('./my-workspace-view.element.js'),
weight: 100,
meta: {
label: 'Details',
pathname: 'details',
icon: 'icon-info',
},
conditions: [
{
alias: 'Umb.Condition.WorkspaceAlias',
match: 'My.Workspace',
},
],
},
];