Umbraco has compiled with nullable reference types since v10, and its public API is annotated:
many calls a site developer makes every day can return null. Once a site turns on
<Nullable>enable</Nullable> — especially with WarningsAsErrors=nullable, as Umbraco does for its
own source — the compiler flags every place that ignores that. This skill says which Umbraco APIs
return null and why, and how to handle each one so the code is correct at runtime, not just
quiet at compile time.
Umbraco's own docs page on this topic exists only for v13 and is a short pointer to Microsoft's
docs. Every Umbraco-specific contract in references/ was checked against the v17 source instead.
Index
| Situation | Look for | Reference |
|---|---|---|
Unsure whether an Umbraco call can return null |
Any Umbraco API under CS86xx | umbraco-nullable-surface.md |
| Reading property values, pickers, media, Models Builder props | Value<T>(...)!, .Trim() / .Length on a value, defaultValue: |
published-content-values.md |
Getting UmbracoContext / UmbracoHelper outside a view |
TryGetUmbracoContext, GetRequiredUmbracoContext() in a service, hosted service or job |
accessor-try-patterns.md |
.cshtml templates and partials |
@Model.Value<...>()!, foreach / .Any() over a picker value |
razor-views.md |
| Turning NRT on, CS8618, your own helpers | <Nullable>, #nullable disable, uninitialised view-model strings, IOptions<T> classes |
project-settings.md |
Before writing code, open the reference for each situation the code in front of you touches, and
only those. A compiled, tested example of the controller → mapper → view-model shape is in
assets/: start from it when you write that shape.
Best practices
- A
?in Umbraco's API describes a real content state, not compiler noise: an editor left a property empty, a picked node was deleted or unpublished, or there's no request and so noUmbracoContext. Decide what the page should do in that state. - Guard, then default, then — rarely —
!. Prefer an early return oris not nullguard; then a real default (?? string.Empty, an empty collection). Use!only where you can prove non-null, with a comment saying why.!and#nullable disablesilence the compiler and leave the sameNullReferenceExceptionwaiting for the first empty field. defaultValue:alone does nothing.Value<T>(alias, defaultValue: x)appliesxonly when you also passfallback: Fallback.ToDefaultValue, and the return type staysT?either way.- A blank text property is
"", notnull.Value<string>()on an empty Textstring or Textarea returnsstring.Empty, soValue<string>("title") ?? content.Namenever falls back. Usestring.IsNullOrWhiteSpaceorfallback: Fallback.ToDefaultValuewhen blank should mean "use something else".nullcomes back when the alias isn't on the Document Type. - Use the
Try*accessors and act onfalse.TryGetUmbracoContext/TryGetUmbracoHelperare annotated so theoutvalue is non-null when they returntrue. Don't callGetRequiredUmbracoContext()where there may be no request. Instead, inject the singletonIPublishedContentCachefor plain content lookups, or useIUmbracoContextFactory.EnsureUmbracoContext()in ausingscoped to one unit of work. - Prefer the
Parent()/Children()extensions over the[Obsolete]IPublishedContent.Parent/.Childrenproperties (removal scheduled for v18). - Don't assume views are checked. With InMemoryAuto models, Razor views compile at runtime
without nullable analysis, so a clean build says nothing about your
.cshtml. Apply the same rules by hand. - Never claim a build passed that you didn't run. If you can't compile, say so, then state
plainly whether you expect the change to build with no nullable warnings, and name anything you
couldn't confirm, such as an assumed signature or a missing
using. - Include the
usingdirectives in the code you hand back.UmbracoContextReferenceis inUmbraco.Cms.Core, andMediaWithCropsis inUmbraco.Cms.Core.Models. Either one missing breaks the build even when the null handling is right.
Version compatibility
Targets Umbraco 17 (net10.0). Contracts were checked against the Umbraco CMS source at
release-17.5.3 (the version this repo pins), and the assets/ compile against the repo's pinned Umbraco 17 with
Nullable=enable and WarningsAsErrors=nullable. Umbraco has shipped NRT annotations since v10, so
the patterns apply from v10, but individual signatures (for example Parent() vs .Parent) differ
in older versions — check the installed version before relying on one.
Validation
Objective assertions live in evals/evals.json; run them with
umbraco-skill-evaluator. The assets/ are Asserted: compiled and HTTP-asserted on the Clean
reference host by the dotnet test gate (examples/approach-a-nullable-patterns/).