Skip to main content
umbraco-validation-context Implement form validation using UmbValidationContext in Umbraco backoffice
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/umbraco/Umbraco-CMS-Backoffice-Skills --skill umbraco-validation-contextThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... Related occupations
SOC
Based on SOC occupation classification
name umbraco-validation-context description Implement form validation using UmbValidationContext in Umbraco backoffice version 1.0.0 location managed allowed-tools Read, Write, Edit, WebFetch
Umbraco Validation Context
What is it?
UmbValidationContext provides a centralized validation system for forms in the Umbraco backoffice. It manages validation messages using JSON Path notation, supports both client-side and server-side validation, and enables reactive error counting for tabs and sections. This is essential for multi-step forms, workspace editors, and any UI that requires comprehensive validation feedback.
Documentation
Always fetch the latest docs before implementing:
Reference Examples
The Umbraco source includes working examples:
Validation Context Dashboard : /Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/validation-context/
This example demonstrates multi-tab form validation with error counting.
Custom Validation Workspace Context : /Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/custom-validation-workspace-context/
This example shows workspace-level validation patterns.
Related Foundation Skills
Workflow
Fetch docs - Use WebFetch on the URLs above
Ask questions - What fields? What validation rules? Multi-tab form?
Generate files - Create form element with validation context
Explain - Show what was created and how validation works
Basic Setup
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit' ;
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element' ;
import {
UMB_VALIDATION_CONTEXT ,
umbBindToValidation,
UmbValidationContext ,
} from '@umbraco-cms/backoffice/validation' ;
import type { UmbValidationMessage } from '@umbraco-cms/backoffice/validation' ;
@customElement ('my-validated-form' )
export class MyValidatedFormElement extends UmbLitElement {
readonly validation = new UmbValidationContext (this );
@state ()
private _name = '' ;
@state ()
private _email = '' ;
@state ()
private _messages ?: UmbValidationMessage [];
constructor ( ) {
super ();
this .consumeContext (UMB_VALIDATION_CONTEXT , ( ) => {
. (
validationContext?. . ,
{
. = messages;
},
);
});
}
( ) {
html` ;
}
# ( ) {
isValid = . . ();
(isValid) {
. ( );
}
}
}
Multi-Tab Form with Error Counting
import { html, customElement, state, when } from '@umbraco-cms/backoffice/external/lit' ;
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element' ;
import { UmbValidationContext , umbBindToValidation } from '@umbraco-cms/backoffice/validation' ;
@customElement ('my-tabbed-form' )
export class MyTabbedFormElement extends UmbLitElement {
readonly validation = new UmbValidationContext (this );
@state () private _tab = '1' ;
@state () private _totalErrors = 0 ;
@state () private _tab1Errors = 0 ;
@state () private _tab2Errors = 0 ;
@state () private _name = '' ;
@state () private _email = '' ;
@state () private _city = '' ;
@state () private _country = '' ;
constructor ( ) {
super ();
. (
. . . ( ),
{
. = [... (messages. ( x. ))]. ;
}
);
. (
. . . ( ),
{
. = [... (messages. ( x. ))]. ;
}
);
. (
. . . ( ),
{
. = [... (messages. ( x. ))]. ;
}
);
}
( ) {
html` ;
}
# ( ) {
html` ;
}
# ( ) {
html` ;
}
# ( ) {
. = (e. ). ( ) ?? ;
}
# ( ) {
isValid = . . ();
(!isValid) {
. ( );
}
}
}
Server-Side Validation Errors
Add server validation errors after an API call:
async #handleSave ( ) {
const isValid = await this .validation .validate ();
if (!isValid) return ;
try {
const response = await this .#saveToServer ();
if (!response.ok ) {
const errors = await response.json ();
for (const error of errors.validationErrors ) {
this .validation .messages .addMessage (
'server' ,
error.path ,
error.message ,
crypto.randomUUID ()
);
}
}
} catch (error) {
console .error ('Save failed:' , error);
}
}
Key APIs
UmbValidationContext
const validation = new UmbValidationContext (this );
const isValid = await validation.validate ();
validation.messages ;
Validation Messages
validation.messages .addMessage (source, path, message, key);
validation.messages .removeMessagesBySource ('server' );
this .observe (
validation.messages .messagesOfPathAndDescendant ('$.form.tab1' ),
(messages ) => { }
);
this .observe (
validation.messages .messages ,
(messages ) => { }
);
umbBindToValidation Directive
${umbBindToValidation (this , '$.form.fieldName' , fieldValue)}
JSON Path Notation
Validation uses JSON Path to identify fields:
Path Description $.formRoot form object $.form.nameName field $.form.tab1.emailEmail field in tab1 $.form.items[0].valueFirst item's value $.form.items[*].nameAll item names
Validation Message Interface
interface UmbValidationMessage {
source : string ;
path : string ;
message : string ;
key : string ;
}
Best Practices
Use JSON Path hierarchy - Organize paths by tab/section for easy error counting
Wrap inputs - Use <uui-form-validation-message> around inputs
Clear server errors - Remove old server errors before new validation
Unique keys - Use crypto.randomUUID() for server error keys
Observe specific paths - Use messagesOfPathAndDescendant for scoped error counts
Show counts on tabs - Display error badges to guide users to problems
That's it! Always fetch fresh docs, keep examples minimal, generate complete working code.
validationContext
this
observe
messages
messages
(messages ) =>
this
_messages
'observeValidationMessages'
override
render
return
<uui-form >
<form >
<div >
<label > Name</label >
<uui-form-validation-message >
<uui-input
type ="text"
.value =
${this ._name}
@input =
${(e: InputEvent) => (this ._name = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this , '$.form.name' , this ._name)}
required
> </uui-input >
</uui-form-validation-message >
</div >
<div >
<label > Email</label >
<uui-form-validation-message >
<uui-input
type ="email"
.value =
${this ._email}
@input =
${(e: InputEvent) => (this ._email = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this , '$.form.email' , this ._email)}
required
> </uui-input >
</uui-form-validation-message >
</div >
<uui-button look ="primary" @click =
${this .#handleSave}
> Save</uui-button >
</form >
</uui-form >
<pre >
${JSON .stringify(this ._messages ?? [], null , 2 )}
</pre >
`
async
handleSave
const
await
this
validation
validate
if
console
log
'Form is valid!'
this
observe
this
validation
messages
messagesOfPathAndDescendant
'$.form'
(messages ) =>
this
_totalErrors
new
Set
map
(x ) =>
path
length
this
observe
this
validation
messages
messagesOfPathAndDescendant
'$.form.tab1'
(messages ) =>
this
_tab1Errors
new
Set
map
(x ) =>
path
length
this
observe
this
validation
messages
messagesOfPathAndDescendant
'$.form.tab2'
(messages ) =>
this
_tab2Errors
new
Set
map
(x ) =>
path
length
override
render
return
<uui-box >
<p > Total errors:
${this ._totalErrors}
</p >
<uui-tab-group @click =
${this .#onTabChange}
>
<uui-tab ?active =
${this ._tab === '1' }
data-tab ="1" >
Tab 1
${when(
this ._tab1Errors,
() => html`<uui-badge color ="danger" > ${this ._tab1Errors} </uui-badge > `
)}
</uui-tab >
<uui-tab ?active =
${this ._tab === '2' }
data-tab ="2" >
Tab 2
${when(
this ._tab2Errors,
() => html`<uui-badge color ="danger" > ${this ._tab2Errors} </uui-badge > `
)}
</uui-tab >
</uui-tab-group >
${when(this ._tab === '1' , () => this .#renderTab1())}
${when(this ._tab === '2' , () => this .#renderTab2())}
<uui-button look ="primary" @click =
${this .#handleSave}
> Save</uui-button >
</uui-box >
`
renderTab1
return
<uui-form >
<form >
<label > Name</label >
<uui-form-validation-message >
<uui-input
.value =
${this ._name}
@input =
${(e: InputEvent) => (this ._name = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this , '$.form.tab1.name' , this ._name)}
required
> </uui-input >
</uui-form-validation-message >
<label > Email</label >
<uui-form-validation-message >
<uui-input
type ="email"
.value =
${this ._email}
@input =
${(e: InputEvent) => (this ._email = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this , '$.form.tab1.email' , this ._email)}
required
> </uui-input >
</uui-form-validation-message >
</form >
</uui-form >
`
renderTab2
return
<uui-form >
<form >
<label > City</label >
<uui-form-validation-message >
<uui-input
.value =
${this ._city}
@input =
${(e: InputEvent) => (this ._city = (e.target as HTMLInputElement).value)}
${umbBindToValidation(this , '$.form.tab2.city' , this ._city)}
required
> </uui-input >
</uui-form-validation-message >
<label > Country</label >
<uui-form-validation-message >
<uui-input
.value =
${this ._country}
@input =
${(e: InputEvent) => (this ._country = (e.target as HTMLInputElement).value)}
required
> </uui-input >
</uui-form-validation-message >
</form >
</uui-form >
`
onTabChange
e : Event
this
_tab
target
as
HTMLElement
getAttribute
'data-tab'
'1'
async
handleSave
const
await
this
validation
validate
if
console
log
'Form has validation errors'
More from this repository