Umbraco CMS Documentation

repository·main·Indexed 26 days ago

https://github.com/umbraco/umbraco-cms

A content management system (CMS) featuring a backoffice for content management, an installer, and support for public-facing sites. Documentation covers building backoffice extensions using Vanilla JavaScript, TypeScript, and Lit via the @umbraco-cms/backoffice package, as well as the backoffice validation system, JSONPath-based messaging, and experimental tools like the Icon Manager and sqlite-to-mock.

Tokens
99.9K
Snippets
154
Records
1K
Agent score
89%

What's inside Umbraco CMS

  1. Use the Icon Manager to curate Umbraco backoffice icons

    main

    The Icon Manager is an experimental browser-based UI used to curate the Umbraco backoffice icon dictionary located at src/packages/core/icon-registry/icon-dictionary.json. It allows you to edit icon metadata such as keywords, groups, and names.

    Note: This tool is self-contained and reads from the parent backoffice source tree, but it does not write changes back to the filesystem automatically. You must manually export and replace the JSON file to apply changes.

  2. Understand the Umbraco Client-side Testing Strategy

    main

    The client-side project uses a two-layer testing approach:

    1. Unit and Integration Tests: These run in-browser via @web/test-runner. API calls are intercepted by MSW (Mock Service Worker) handlers, allowing tests to run without a live backend.
    2. E2E Acceptance Tests: Located in tests/Umbraco.Tests.AcceptanceTest/, these use Playwright to run against a real CMS installation to cover full user journeys.

    Testing Priority

    • Critical: src/libs/ (Framework foundations). Changes must have tests.
    • High: src/packages/core/ (UI framework infrastructure). Focus on unit tests for classes (contexts, controllers, managers, repositories).
    • Standard: src/packages/* (CMS packages). Domain features covered by both unit tests and E2E tests.
  3. Understand the Backoffice Validation System architecture

    main

    The Umbraco Backoffice validation system is built around three core concepts:

    1. Validation Context: The central repository that holds all current Validation Messages.
    2. Validation Messages: Objects containing a type, path (JSONPath), and the error message. Because messages are stored in the context, they persist even if the UI element associated with them is removed from the screen.
    3. Validators: The glue between a validation source (like a form or a server) and the Validation Context. Validators are assigned to a context and their validate method is called when the context performs validation.

    This system supports both client-side and server-side validation and allows for asynchronous validation processes.

  4. Understand the Repository Pattern in Umbraco Backoffice

    main

    Repositories serve as a domain-specific, feature-scoped data access layer. They decouple the UI from the data source (server API, manifest, or local cache).

    Core Principles:

    • Feature-scoped: Repositories are located within the feature folder they serve, not in a global repository directory.
    • Extension-registered: They are registered as type: 'repository' extensions. Higher weight extensions can override them.
    • Data source delegation: Repositories orchestrate data flow but delegate actual transport and mapping to a Data Source.
    • Single Responsibility: Each repository should handle one concern (e.g., a Detail repository for CRUD, a Publishing repository for publish actions).
  5. Understand the Umbraco Entity System

    main

    The entity system is the primary discriminator used to tie together workspaces, trees, actions, pickers, and routing in the backoffice. An entity is any object that requires identification and extension scoping (e.g., documents, media, users, or structural elements like tree roots).

    Every entity is defined by an UmbEntityModel containing:

    • entityType: A string constant classifying the entity (e.g., 'document', 'media-root').
    • unique: A unique identifier (GUID string), or null for roots or singletons.
  6. Understand Umbraco Backoffice Workspaces

    main
    Workspaces are the primary editing surfaces in the Umbraco backoffice. Each workspace manages a context (state and behavior), views (tabs), and actions (toolbar buttons) for a specific entity or feature. Workspaces are registered and composed through the extension system, allowing any workspace to be extended, overridden, or replaced by other packages.
  7. Understand Umbraco Backoffice Manifests

    main
    Every extension in the Umbraco Backoffice (sections, dashboards, property editors, etc.) is defined by a manifest. A manifest is a plain object that declares the extension's type, identity, behavior, and activation rules. Manifests are the fundamental building blocks of the extension-first architecture, allowing default behaviors to be replaced, overridden, or removed.
  8. Understand the Umbraco Backoffice Technology Stack

    main

    The Umbraco Backoffice is a Single Page Application (SPA) built as an npm library (@umbraco-cms/backoffice) and delivered as a bundle of Web Components.

    Core Technologies:

    • Runtime: Node.js >=22, npm >=10
    • Language: TypeScript 5.x (ESM with .js extensions)
    • Framework: Lit 3 (Web Components)
    • UI Library: @umbraco-ui/uui 2.x (Always prefer UUI components like <uui-button>, <uui-input>, etc., over native HTML)
    • Rich Text: TipTap 3
    • State Management: RxJS 7 wrapped by UmbState classes
    • Dependency Injection: DOM-event-based Context API
    • Build Tool: Vite 7
    • Real-time: SignalR
  9. Organize Test Files and Mocks

    main

    Follow this directory structure for organizing tests and mock data:

    src/
    ├── **/*.test.ts              # Unit tests co-located with source
    mocks/
    │   ├── data/                 # Mock data & in-memory databases
    │   └── handlers/             # MSW request handlers
    ├── examples/
    │   └── **/*.test.ts          # Example tests
    └── utils/
        └── test-utils.ts         # Shared test utilities
    
    e2e/
    ├── **/*.spec.ts              # Playwright E2E tests
    └── fixtures/                 # E2E test fixtures
  10. Extend Workspaces using `workspaceContext`

    main

    To add capabilities to a workspace without modifying its core code, use workspaceContext extensions. This is the primary pattern for modularity and reuse. The extension is registered by the package that owns the feature, not the package that owns the workspace.

    Registration Pattern: Register the extension with conditions that match the target workspace alias.

    Implementation Pattern: Extend UmbContextBase, consume the parent workspace context using this.consumeContext(), and provide a unique context token.

    // Registration
    {
      type: 'workspaceContext',
      alias: 'Umb.WorkspaceContext.Document.Publishing',
      api: () => import('./document-publishing.workspace-context.js'),
      conditions: [
        { alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_WORKSPACE_ALIAS },
      ],
    }
    
    // Implementation
    export class UmbDocumentPublishingWorkspaceContext extends UmbContextBase
      implements UmbPublishableWorkspaceContext {
    
      constructor(host: UmbControllerHost) {
        super(host, UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT);
    
        this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (workspaceContext) => {
          // Access workspace data and add publishing capabilities
        });
      }
    
      async saveAndPublish() { /* ... */ }
      async publish() { /* ... */ }
      async unpublish() { /* ... */ }
    }
  11. Handle deferred feedback for Workspace Actions with modals

    main

    When a workspace action (like Save) opens a confirmation modal, using the legacy waiting state causes incorrect UI behavior (e.g., showing a success tick when a user cancels). To fix this, implement the deferred-feedback contract using UmbWorkspaceActionExecutionOptions.

    1. Action Side

    Subclass UmbWorkspaceActionBase, call this.setExecuting(false) in the constructor to opt-in, and use onActionStarting to trigger the loading state only when real work begins.

    export class MyWorkspaceAction extends UmbWorkspaceActionBase {
        constructor(host: UmbControllerHost, args: UmbWorkspaceActionArgs) {
            super(host, args);
            // Opt in — exposes `isExecuting` so the button waits for real work.
            this.setExecuting(false);
        }
    
        override async execute() {
            try {
                await this._workspaceContext?.doTheirThing({
                    onActionStarting: () => this.setExecuting(true),
                });
            } finally {
                this.setExecuting(false);
            }
        }
    }

    2. Context Side

    In your workspace-context method, await the modal result. If the user cancels (result is falsy), return silently. If they proceed, call notifyWorkspaceActionStarting(options) before performing the actual work.

    public async doTheirThing(options?: UmbWorkspaceActionExecutionOptions): Promise<void> {
        const result = await umbOpenModal(this, MY_CONFIRM_MODAL, { /* ... */ })
            .catch(() => undefined);
        if (!result) return; // user cancelled — silent return, no spinner, no tick
    
        notifyWorkspaceActionStarting(options);
        // real work below
        await this.#repository.doTheirThing(result);
    }
    export class MyWorkspaceAction extends UmbWorkspaceActionBase {
        constructor(host: UmbControllerHost, args: UmbWorkspaceActionArgs) {
            super(host, args);
            // Opt in — exposes `isExecuting` so the button waits for real work.
            this.setExecuting(false);
        }
    
        override async execute() {
            try {
                await this._workspaceContext?.doTheirThing({
                    onActionStarting: () => this.setExecuting(true),
                });
            } finally {
                this.setExecuting(false);
            }
        }
    }
    
    public async doTheirThing(options?: UmbWorkspaceActionExecutionOptions): Promise<void> {
        const result = await umbOpenModal(this, MY_CONFIRM_MODAL, { /* ... */ })
            .catch(() => undefined);
        if (!result) return; // user cancelled — silent return, no spinner, no tick
    
        notifyWorkspaceActionStarting(options);
        // real work below
        await this.#repository.doTheirThing(result);
    }