skills/extensions/umbraco-dynamic-root/SKILL.md
Implement dynamic root origins and query steps in Umbraco backoffice using official docs
npx skillsauth add albanist/umbraco_cli umbraco-dynamic-rootInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Dynamic Roots allow content pickers to have a starting point (origin) that is determined dynamically rather than being a fixed node. This includes two extension types:
These enable flexible content picker configurations that adapt based on context.
Always fetch the latest docs before implementing:
import type { ManifestDynamicRootOrigin } from '@umbraco-cms/backoffice/extension-registry';
const manifest: ManifestDynamicRootOrigin = {
type: 'dynamicRootOrigin',
alias: 'My.DynamicRootOrigin.SiteRoot',
name: 'Site Root Origin',
meta: {
originAlias: 'SiteRoot',
label: 'Site Root',
description: 'Start from the root of the current site',
icon: 'icon-home',
},
};
export const manifests = [manifest];
import type { ManifestDynamicRootQueryStep } from '@umbraco-cms/backoffice/extension-registry';
const manifest: ManifestDynamicRootQueryStep = {
type: 'dynamicRootQueryStep',
alias: 'My.DynamicRootQueryStep.NearestBlog',
name: 'Nearest Blog Query Step',
meta: {
queryStepAlias: 'NearestBlog',
label: 'Nearest Blog',
description: 'Find the nearest blog ancestor',
icon: 'icon-article',
},
};
export const manifests = [manifest];
import type { ManifestDynamicRootOrigin, ManifestDynamicRootQueryStep } from '@umbraco-cms/backoffice/extension-registry';
const originManifests: ManifestDynamicRootOrigin[] = [
{
type: 'dynamicRootOrigin',
alias: 'My.DynamicRootOrigin.CurrentPage',
name: 'Current Page Origin',
meta: {
originAlias: 'CurrentPage',
label: 'Current Page',
description: 'Start from the page being edited',
icon: 'icon-document',
},
},
{
type: 'dynamicRootOrigin',
alias: 'My.DynamicRootOrigin.Parent',
name: 'Parent Page Origin',
meta: {
originAlias: 'Parent',
label: 'Parent Page',
description: 'Start from the parent of current page',
icon: 'icon-arrow-up',
},
},
];
const queryStepManifests: ManifestDynamicRootQueryStep[] = [
{
type: 'dynamicRootQueryStep',
alias: 'My.DynamicRootQueryStep.Children',
name: 'Children Query Step',
meta: {
queryStepAlias: 'Children',
label: 'Children',
description: 'Get all child pages',
icon: 'icon-tree',
},
},
{
type: 'dynamicRootQueryStep',
alias: 'My.DynamicRootQueryStep.Ancestors',
name: 'Ancestors Query Step',
meta: {
queryStepAlias: 'Ancestors',
label: 'Ancestors',
description: 'Get ancestor pages',
icon: 'icon-navigation-up',
},
},
];
export const manifests = [...originManifests, ...queryStepManifests];
Dynamic roots require backend C# implementation to handle the actual query logic:
// Example C# implementation for a custom origin
public class SiteRootDynamicRootOrigin : IDynamicRootOrigin
{
public string OriginAlias => "SiteRoot";
public Task<Guid?> GetOriginAsync(Guid contentKey, string? entityType)
{
// Return the site root GUID based on the content's position
// Implementation depends on your site structure
return Task.FromResult<Guid?>(GetSiteRootForContent(contentKey));
}
}
// Example C# implementation for a custom query step
public class NearestBlogQueryStep : IDynamicRootQueryStep
{
public string QueryStepAlias => "NearestBlog";
public Task<IEnumerable<Guid>> ExecuteAsync(Guid originKey, string? entityType)
{
// Find nearest blog ancestor from the origin
// Return matching content GUIDs
return Task.FromResult(FindNearestBlogAncestors(originKey));
}
}
// Register in Composer
public class DynamicRootComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.DynamicRootSteps()
.AddOrigin<SiteRootDynamicRootOrigin>()
.AddQueryStep<NearestBlogQueryStep>();
}
}
| Property | Description |
|----------|-------------|
| originAlias | Unique identifier matching backend implementation |
| label | Display name in picker configuration |
| description | Help text explaining the origin |
| icon | Icon shown in configuration UI |
| Property | Description |
|----------|-------------|
| queryStepAlias | Unique identifier matching backend implementation |
| label | Display name in picker configuration |
| description | Help text explaining the query step |
| icon | Icon shown in configuration UI |
That's it! Always fetch fresh docs, keep examples minimal, generate complete working code.
tools
Umbraco Automate operations (event-driven workflow automation)
development
Webhook management (the Management API's outbound event notifications)
development
Backoffice user management (accounts, state, groups, API credentials)
tools
Backoffice user group management (permission sets)