Umbraco publishes a notification for most things it does, usually as a before/after pair
(ContentPublishingNotification / ContentPublishedNotification). You react with a registered
handler class, and you can publish notifications of your own.
Check first: if the user only wants an HTTP call to a URL on publish/unpublish/delete (or media save/delete), built-in Webhooks (Settings > Webhooks) do that with no code. Point them there and stop.
| A: Handle a built-in notification (default) | B: Publish your own notification | |
|---|---|---|
| Reference | approach-a-handle-builtin.md | approach-b-publish-custom.md |
| You write | A handler (INotificationHandler<T> or async variant) + one registration line |
A notification class, the publish call, usually a handler |
| Shipped code | assets/PublishingRuleHandler.cs, assets/NotificationHandlersComposer.cs |
assets/ContentImportCompletedNotification.cs, assets/ContentImportCompletedHandler.cs |
| Best for | Reacting to or vetoing what Umbraco does | Letting other code react to what your service/job does |
Before writing any code, open the reference for the approach you choose and start from its assets. The facts the docs pages leave out (cache-refresher payloads, cancelled publishes, scope delivery) are only there.
How to decide
"When Umbraco does X, do Y" is A, which is nearly everything. Pick B when the event is your own code and others should be able to listen without referencing it. A job that publishes its own notification plus a handler for it uses both.
Version compatibility
Umbraco 17+. API details in the references were checked against Umbraco 17.5; the shipped code is gated on the 17 reference instance.
Best practices
- Right half of the pair. Veto or alter in the "before" notification (
CancelOperation); do side effects in the "after" one. "After" notifications can't be cancelled (they have noCancelOperation), and "before" side effects run even if the operation fails. - Handlers are created fresh per notification. Fields don't survive between calls; keep
state in an injected service. Pass data before-to-after via the notification's
State. - "Published" ran here; "cache refreshed" runs everywhere.
ContentPublishedNotificationfires once, on the server that published.ContentCacheRefresherNotificationfires on every server after its own cache has updated, so use it for per-server work (local caches) and for anything that must see the published state on other nodes of a load-balanced site. - Languages change what fires. Unpublishing one language is a publish of the rest, so it
arrives as
ContentPublishingNotification. UseIsPublishingCulture/IsUnpublishingCulture. - Keep startup handlers quick (they run every boot; see
umbraco-common-pitfalls), and gate them onIRuntimeState.Level == RuntimeLevel.Run: they also fire during install and upgrade boots.umbraco-composingcovers startup work and runtime levels in full. - Register the way the project already does: existing
IComposers, or theCreateUmbracoBuilder()chain inProgram.cs. If neither, a composer.
Validation
Assertions live in evals/evals.json; run them with umbraco-skill-evaluator.