Umbraco Extensibility
ASecurityUmbraco's own extension mechanisms, and how to build on them instead of inventing your own — Composer registration, collection-builder extensibility, notification handlers, and attribute-based discovery — plus a catalogue of the most common concrete, backend-side Umbraco CMS extension points (property value converters, Management API endpoints, content finders, URL segment providers, health checks, search indexing, and more). Use when deciding how to make a feature extensible by other package...
- 2 stars
- 0 votes
- 0 copies
- 0 views
- Added October 15, 2026
Works with
Security analysis
100/100Pro scans all 3 files and shows the line behind each finding
npx -y skills add mattbrailsford/umbraco-devkit --skill umbraco-extensibility --agent claude-codeAre you the author of Umbraco Extensibility?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mattbrailsford-umbraco-extensibility)---
name: umbraco-extensibility
description: >-
Umbraco's own extension mechanisms, and how to build on them instead of inventing your own —
Composer registration, collection-builder extensibility, notification handlers, and
attribute-based discovery — plus a catalogue of the most common concrete, backend-side
Umbraco CMS extension points (property value converters, Management API endpoints, content
finders, URL segment providers, health checks, search indexing, and more). Use when
deciding how to make a feature extensible by other packages, or when choosing which CMS
extension point fits a given task. For general backend package conventions that aren't
about extension mechanisms specifically (async naming, visibility (internal by default), public-API
compatibility), see `umbraco-package-conventions` instead. Leaves out what the official
Backoffice Extension Skills plugin already covers: property editor UI and schema,
dashboards, sections, trees.
---
# Extending Umbraco, not just building on top of it
## 🎯 Why: Design for Change
Packages get extended by code you'll never see. Reuse Umbraco's own extension mechanisms so
the extension surface stays stable while the implementation changes; don't invent your own
DI/config/plugin system. For package conventions beyond extension points, see
`umbraco-package-conventions`.
## Composers — your package's DI entry point
A `Composer` (`IComposer`) registers a package into Umbraco's startup, auto-discovered via
assembly scanning. Keep one per package (or per logical concern in a large package). Keep it
thin: it wires things up, no logic.
```csharp
public class MyPackageComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Services.AddSingleton<IMyPackageService, MyPackageService>();
builder.MyPackageThings().Add<DefaultThing>();
}
}
```
## Collection builders — how you make *your own* extension points
If other packages (or your own future features) need to plug in, expose a collection builder,
not a raw list or config flag. Umbraco uses the same mechanism for content finders, editors,
and notification handlers:
```csharp
public class MyPackageThingCollectionBuilder
: OrderedCollectionBuilderBase<MyPackageThingCollectionBuilder, MyPackageThingCollection, IMyPackageThing>
{
protected override MyPackageThingCollectionBuilder This => this;
}
public static class MyPackageBuilderExtensions
{
public static MyPackageThingCollectionBuilder MyPackageThings(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<MyPackageThingCollectionBuilder>();
}
```
Consumers then call `builder.MyPackageThings().Add<Custom>().InsertBefore<Custom, Default>()`.
Order via `Append()`/`InsertBefore<T>()`/`InsertAfter<T>()`, not an `Order` property on the
interface, so the registration site controls ordering, not each implementation.
## Attribute-based discovery, when the extension point is "plugins"
For pluggable implementations shipped as separate NuGet packages (providers, connectors,
adapters), prefer attribute + assembly-scan discovery over a config list the consumer must
update:
```csharp
[MyPackageProvider("cool-vendor", "Cool Vendor")]
public class CoolVendorProvider : MyPackageProviderBase { /* ... */ }
```
Then `dotnet add package My.Package.CoolVendor` is the whole integration step, with no code
change in the consuming project.
## Notification handlers, not raw event subscriptions
To react to content/media/member lifecycle events, implement `INotificationHandler<T>` and
register it via the composer, not a static event subscription. Handlers test in isolation and
don't leak subscriptions across restarts. Keep handlers light (`umbraco-performance`), and
put side effects only in handlers that run once, not on every server
(`umbraco-load-balancing`).
Not the same as the official Backoffice Extension Skills plugin's `umbraco-notifications`
skill: that covers backoffice toast messages (`UMB_NOTIFICATION_CONTEXT`), a frontend-only
concept that shares the name.
## Publish your own notifications at extension points
When a feature adds an extensible API or extension point, propose notifications for it in the
design. Don't wait to be asked, and don't cut them as "add later". They're cheap, and they're
the easiest way for someone to extend a feature without replacing a service or writing a
plugin. List the feature's lifecycle moments (selected, executing, executed, saved) and
propose one for each moment that adds value. Match the shape of the package's or Umbraco's
existing notifications.
Pick the kind by what the extender needs:
- **An "-ed" notification** (`ThingSavedNotification`) when they want to watch or react
after the fact.
- **A cancelable "-ing" notification** (`ThingSavingNotification`) when they want to add to,
change or block what happens. Several handlers can each add something, and any one can
cancel.
- **A replaceable service or ordered collection** when they want to own the whole behavior,
when there must be one answer, or when order matters. Notification handlers have no
reliable order.
Skip a cancelable "-ing" notification when another mechanism already decides that outcome,
such as an ordered collection that picks the result. Two ways to decide the same thing will
conflict. Record that choice in `DECISION-LOG.md`.
If an "-ing" notification lets handlers change content you generate, such as text, pass it as
named parts that handlers add, remove or reorder. If handlers search and replace your raw
text, every wording change you make breaks someone.
## Catalogue: which CMS extension point for which job
Excludes what the official Backoffice Extension Skills plugin documents: the Lit/UUI element
and manifest for a backoffice-UI surface (`umbraco-property-editor-ui`, `umbraco-dashboard`,
`umbraco-sections`, `umbraco-tree`), and the C# property editor schema
(`umbraco-property-editor-schema`, covering `DataEditor`, `IConfigurationEditor`, and its own
value converter example). Property value converters stay here anyway, since you write one for
any property editor, built-in or custom.
Signatures can drift across Umbraco majors and this skill isn't version-pinned, so check
the CMS source at the target release (`umbraco-cms-source`) before trusting a base class or
attribute shape verbatim. The docs for that major (`umbraco-docs`) help find the right type. Full skeletons are in `references/`; read only the file you need.
| # | Extension point | Interface / base class | Registered via | Reach for it when |
|---|---|---|---|---|
| 1 | Property value converter | `IPropertyValueConverter` (+ `IDeliveryApiPropertyValueConverter`) | auto-discovered | a property's stored value needs a typed shape at runtime |
| 2 | Management API endpoint | `ManagementApiControllerBase` | versioned route attributes | the backoffice needs to call your package's data |
| 3 | Health check | `HealthCheck` | `[HealthCheck]` attribute | a site builder should be able to self-diagnose your package's config |
| 4 | Cache refresher | `ICacheRefresher` | auto-discovered | your package's own entities need to stay in sync across a load-balanced site |
| 5 | Content finder | `IContentFinder` | `ContentFindersCollection` | resolving custom/virtual URLs to content |
| 6 | URL segment provider | `IUrlSegmentProvider` | `UrlSegmentProviders().Insert<T>()` | generating those same custom URL segments |
| 7 | Search indexing | `IContentIndexer`, `IPropertyValueHandler` (Umbraco Search); `IValueSetBuilder` (Examine) | DI / auto-discovered; named options + composer | adding fields to indexed content, indexing a custom property editor's value, or indexing data that isn't content |¹
| 8 | Custom file system | `IFileSystem` | `builder.SetMediaFileSystem()` (or similar) | media/files live somewhere other than local disk |
| 9 | Package migration plan | `PackageMigrationPlan` + `PackageMigrationBase`/`AsyncPackageMigrationBase` | `builder.PackageMigrationPlans().Append<T>()` | your package needs to import content/schema, or evolve it, across installs and upgrades |
| 10 | Recurring background job | `RecurringBackgroundJobBase` (or `IRecurringBackgroundJob` directly) | `builder.Services.AddRecurringBackgroundJob<T>()` | your package needs scheduled/periodic work (cleanup, sync, digest) without a separate host; for work that must run on one server only, `IDistributedBackgroundJob` (v17+, `umbraco-load-balancing`) |
| 11 | Custom Management API authorization policy | `IAuthorizationRequirement` + `MustSatisfyRequirementAuthorizationHandler<T>` | `AddAuthorization` + `[Authorize(policyName)]` | a Management API endpoint (row 2) needs finer-grained access control than the built-in section/content policies |
- **`references/content-and-routing.md`** — rows 1, 5, 6, 7.
- **`references/api-and-infrastructure.md`** — rows 2, 3, 4, 8, 9, 10, 11.
¹ v19 replaces the built-in Examine indexes and hooks with Umbraco Search, which is also an
add-on for v17 and v18. Which API applies depends on the stack the site runs, not just the
major; `content-and-routing.md` row 7 covers both.
## Scope note
This skill covers Umbraco's extension mechanisms and the backend-side CMS extension points
built on them, only.
- Async naming, visibility (internal by default), public-API backwards compatibility, and where extension
methods live: `umbraco-package-conventions`.
- Extension points other products expose (a Commerce payment provider, a Forms workflow type, a
Contentment data source): that package's own docs.
- What the official Backoffice Extension Skills plugin owns (`umbraco-property-editor-ui`,
`umbraco-dashboard`, `umbraco-sections`, `umbraco-tree`, `umbraco-property-editor-schema`):
left to that plugin, even where the code is plain C#.
- Name collisions: that plugin's `umbraco-notifications` and `umbraco-health-check` are
frontend concepts (toast messages, a health check's backoffice manifest), distinct from this
skill's `INotificationHandler<T>` and `HealthCheck` C# base class.
Files in this skill
- SKILL.md
- references/api-and-infrastructure.md
- references/content-and-routing.md
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.