
Umbraco Validation Checks
- 275 installs
- 26 repo stars
- Updated August 1, 2026
- umbraco/umbraco-cms-backoffice-skills
Helps with ai & agent building tasks.
About
umbraco-validation-checks is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- umbraco-validation-checks
- AI & Agent Building
- AI-coding skill
Umbraco Validation Checks by the numbers
- 275 all-time installs (skills.sh)
- Ranked #2,400 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/umbraco/umbraco-cms-backoffice-skills --skill umbraco-validation-checksAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 275 |
|---|---|
| repo stars | ★ 26 |
| Last updated | August 1, 2026 |
| Repository | umbraco/umbraco-cms-backoffice-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Umbraco Extension Validation Checks
Reference skill containing validation checks for manual browser testing of Umbraco backoffice extensions. Load this skill before beginning validation testing.
Check Categories
| Category | File | Checks |
|---|---|---|
| Configuration | configuration-checks.md | VC-1 to VC-2 |
| Navigation | navigation-checks.md | VN-1 to VN-3 |
| API Debugging | api-debugging-checks.md | VA-1 to VA-3 |
| Form Controls | form-control-checks.md | VF-1 to VF-2 |
| Workspace | workspace-checks.md | VW-1 to VW-3 |
Quick Reference
| ID | Check | Common Symptom |
|---|---|---|
| Configuration | ||
| VC-1 | Section Permissions | New section not visible |
| VC-2 | User Group Access | Extension appears for some users only |
| Navigation | ||
| VN-1 | Tree Complexity | Tree not rendering or items missing |
| VN-2 | Hidden Tree Actions | Cannot find expected button/action |
| VN-3 | Menu Item Visibility | Menu items not appearing |
| API Debugging | ||
| VA-1 | 400 Error Investigation | API calls failing silently |
| VA-2 | Request Payload Validation | Wrong data structure being sent |
| VA-3 | CORS and Auth Issues | Requests blocked or unauthorized |
| Form Controls | ||
| VF-1 | Select/Combobox Behavior | Select not populating and causing 400 errors |
| VF-2 | Input Binding Issues | Values not updating or saving |
| Workspace | ||
| VW-1 | Missing Save Button | Editable workspace has no Save button |
| VW-2 | Data Not Loading | Workspace opens but shows empty values |
| VW-3 | Submit Not Working | Save clicked but nothing happens |
Usage
Always load this skill before starting manual browser validation.
Read all check files when validating a new extension, or focus on specific categories based on the symptoms you observe.
| Symptom | Load Files |
|---|---|
| "Can't see my extension" | configuration-checks, navigation-checks |
| "API not working" | api-debugging-checks |
| "Form doesn't work" | form-control-checks |
| "Tree issues" | navigation-checks |
| "Workspace issues" | workspace-checks |
| "No Save button" | workspace-checks |
Validation Workflow
1. Before testing: Ensure the extension is built and the browser cache is cleared 2. Check DevTools Console: Open Chrome DevTools (F12) before interacting 3. Check Network tab: Filter by Fetch/XHR to see API calls 4. Read relevant check files based on what you observe
Capturing New Issues
When you encounter a validation issue not covered by existing checks:
1. Log it immediately in discovered-issues.md 2. Include: symptom, root cause, solution, suggested category 3. Issues will be reviewed and promoted to proper checks
This compounds knowledge over time - every validation session improves future sessions.
Related Skills
| Pattern Area | Skill |
|---|---|
| Tree implementation | umbraco-tree |
| Section setup | umbraco-sections |
| API client setup | umbraco-openapi-client |
| Workspace structure | umbraco-workspace |
API Debugging Checks
VA-1: 400 Error Investigation
Common Symptom: API calls failing, often silently or with generic errors
ALWAYS check for 400 (Bad Request) errors first. These indicate the data sent to the server is wrong.
How to investigate:
1. Open Chrome DevTools (F12) 2. Go to Network tab 3. Filter by Fetch/XHR 4. Look for requests with red status (400, 401, 403, 404, 500) 5. Click the failed request 6. Check Response tab for error details
Before deep-diving into 400 errors: Check all form selects/dropdowns first. Empty or broken selects are a common cause of 400 errors that leads to circular debugging. See VF-1: Select/Combobox Behavior.
400 errors usually mean:
| Error Pattern | Likely Cause |
|---|---|
| "Validation failed" | Required field missing or wrong type |
| "Invalid property" | Property name doesn't match backend model |
| "Cannot deserialize" | Wrong data structure (array vs object, string vs number) |
| Empty response body | Check Request payload - likely malformed |
Debug checklist for 400 errors:
- [ ] Check Request tab > Payload - is the data correct?
- [ ] Compare payload structure to backend model/DTO
- [ ] Check for
nullvalues where object expected - [ ] Check for string where number expected (and vice versa)
- [ ] Verify enum values are strings (if using
JsonStringEnumConverter)
---
VA-2: Request Payload Validation
Common Symptom: Data appears correct in UI but API rejects it
Check the actual payload being sent:
1. Network tab > Click failed request > Payload tab 2. Compare against what the backend expects
Common payload issues:
| Issue | What to Look For |
|---|---|
| Wrong property names | Backend uses camelCase, frontend sending PascalCase |
| Missing required fields | Check backend model for [Required] attributes |
| Wrong ID format | GUIDs should be lowercase with dashes |
| Enum as number | Should be string if backend uses JsonStringEnumConverter |
| Nested object null | Parent object exists but child is null |
Example - verifying GUID format:
// WRONG - uppercase or no dashes
const id = "A1B2C3D4E5F6";
// CORRECT - lowercase with dashes
const id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";Example - verifying enum as string:
// WRONG - sending number
{ status: 0 }
// CORRECT - sending string (if backend has JsonStringEnumConverter)
{ status: "Draft" }---
VA-3: CORS and Auth Issues
Common Symptom: Requests blocked or returning 401/403
401 Unauthorized:
- Token expired - try refreshing the page
- API requires authentication but OpenAPI client not configured
- Check
umbraco-openapi-clientskill for proper setup
403 Forbidden:
- User doesn't have permission for this action
- Check user group permissions
CORS errors (visible in Console):
- API endpoint doesn't allow cross-origin requests
- Usually indicates wrong API URL or missing CORS configuration on backend
Debug checklist:
- [ ] Is there a CORS error in the Console?
- [ ] Check request headers - is Authorization header present?
- [ ] For custom APIs, verify OpenAPI client is configured
- [ ] Try refreshing the page to get new auth token
- [ ] Check if API endpoint is correct (no typos in URL)
If using custom API controllers:
Ensure the controller inherits from the correct base class and has proper authorization attributes. See umbraco-openapi-client skill for proper setup.
Configuration Checks
VC-1: Section Permissions
Common Symptom: New section not visible in the backoffice sidebar
When creating a new section, it won't appear unless the user's group has permission to access it.
Before testing a new section:
1. Go to Users > User Groups > Administrators 2. Scroll to Sections (or "Allowed Sections") 3. Ensure your new section is checked/enabled 4. Save the user group 5. Refresh the page (Ctrl+Shift+R / Cmd+Shift+R)
Why this happens: Umbraco's permission system hides sections by default. Even the admin user needs explicit group permission to see new sections.
Code verification: Check if the section manifest is registered:
// Should be in umbraco-package.json or registered via entry point
{
type: 'section',
alias: 'My.Section',
name: 'My Section',
meta: {
label: 'My Section',
pathname: 'my-section'
}
}---
VC-2: User Group Access
Common Symptom: Extension works for one user but not another
Different user groups have different permissions. When validating:
1. Test with the admin account first - this has the most permissions 2. If it works for admin but not other users, it's a permissions issue 3. Check the specific user group's allowed sections and permissions
Debug checklist:
- [ ] Is the user logged in with the correct account?
- [ ] Does the user's group have access to the section?
- [ ] Are there any
conditionson the extension that might filter by user role?
Example condition that limits access:
{
type: 'dashboard',
alias: 'My.Dashboard',
conditions: [
{
alias: 'Umb.Condition.SectionAlias',
match: 'My.Section' // Only shows when in this section
}
]
}Discovered Issues Log
Log new validation issues here as they are discovered. These will be reviewed and incorporated into the appropriate check files.
Format
When logging a new issue, include:
### [Date] Brief title
**Symptom:** What you observed
**Root cause:** What was actually wrong
**Solution:** How to fix/avoid it
**Suggested category:** (configuration | navigation | api-debugging | form-control | new)---
Pending Issues
<!-- Add new discovered issues below this line -->
Form Control Checks
VF-1: Select/Combobox Behavior
Common Symptom: Dropdown not working, wrong value selected, or options not appearing
Key insight: Empty or broken selects are a common cause of mysterious 400 API errors. When debugging 400 errors, always check selects first - if a select has no data or wrong data, the form submits invalid values that the API rejects.
The select → 400 error trap:
1. Select component renders but has no options (data didn't load) 2. User fills out form, select value is null or undefined 3. Form submits, API returns 400 4. Developer checks API payload, sees missing/wrong value 5. Goes in circles checking API, models, etc. 6. Root cause was the select never populated
Before debugging 400 errors, check all selects:
- [ ] Does the dropdown show options when clicked?
- [ ] Is the correct option selected?
- [ ] In DevTools Elements tab, inspect the select - does it have a value?
UUI Select vs Combobox:
| Component | Use Case |
|---|---|
uui-select | Simple dropdowns with predefined options |
uui-combobox | Searchable/filterable dropdowns, typeahead |
Common select issues:
| Issue | Likely Cause |
|---|---|
| Options don't appear | Options array is empty or malformed |
| Wrong item selected | Value binding uses wrong property |
| Selection doesn't persist | Event handler not updating state |
| Shows "[object Object]" | Display property not specified |
Verify options format:
// uui-select expects this format
const options = [
{ name: 'Display Text', value: 'actual-value' },
{ name: 'Another Option', value: 'another-value' }
];
// In template
html`<uui-select .options=${this._options} @change=${this.#onChange}></uui-select>`Debug checklist for selects:
- [ ] Check Console - any errors when clicking dropdown?
- [ ] Verify options array is populated (add
console.logtemporarily) - [ ] Check
nameandvalueproperties in options - [ ] Verify change event handler is being called
- [ ] Check if value is being set correctly (
.valuevsvalueattribute)
Property binding matters:
// WRONG - attribute binding for objects
<uui-select options=${this._options}>
// CORRECT - property binding with dot prefix
<uui-select .options=${this._options}>Anti-pattern: Child elements do NOT work:
Unlike native HTML <select>, the uui-select component does not support child elements for options. This is a common mistake that results in an empty dropdown and 400 errors when the form submits with null/undefined values.
// WRONG - child elements do NOT work
html`
<uui-select>
<uui-option value="draft">Draft</uui-option>
<uui-option value="published">Published</uui-option>
</uui-select>
`
// CORRECT - use .options property
html`
<uui-select
.options=${[
{ name: 'Draft', value: 'draft' },
{ name: 'Published', value: 'published' },
]}
@change=${this.#onChange}
></uui-select>
`---
VF-2: Input Binding Issues
Common Symptom: Values not updating, not saving, or reverting
Check binding direction:
| Syntax | Direction | Use For |
|---|---|---|
.value=${x} | JS to DOM | Setting input value |
@input=${fn} | DOM to JS | Reacting to typing |
@change=${fn} | DOM to JS | Reacting to value commit |
Common input issues:
| Issue | Likely Cause |
|---|---|
| Value doesn't update | Missing . in property binding |
| Changes lost on re-render | State not being updated in event handler |
| Value reverts after typing | Two-way binding not implemented |
| Saves wrong value | Reading wrong property from event |
Correct two-way binding pattern:
@state() private _name = '';
#onNameInput(e: UUIInputEvent) {
this._name = e.target.value as string;
}
render() {
return html`
<uui-input
.value=${this._name}
@input=${this.#onNameInput}>
</uui-input>
`;
}Debug checklist for inputs:
- [ ] Is the value property bound with dot prefix (
.value)? - [ ] Is there an event handler for
@inputor@change? - [ ] Does the event handler update component state?
- [ ] Is the state decorated with
@state()? - [ ] Check event target - is it the input element or a wrapper?
Getting value from event:
// For UUI components
#onInput(e: UUIInputEvent) {
const value = e.target.value; // Usually works
}
// If nested or wrapped, may need to cast
#onInput(e: Event) {
const target = e.target as UUIInputElement;
const value = target.value;
}Navigation Checks
VN-1: Tree Complexity
Common Symptom: Tree not rendering, items missing, or incorrect hierarchy
Trees in Umbraco are complex multi-part systems. When trees don't work:
ALWAYS check these reference implementations first:
1. Use the `umbraco-tree` skill - contains working examples 2. Look at the notes-wiki example if available in your project 3. Check Umbraco CMS source for reference tree implementations
Tree requires multiple parts:
| Part | Purpose |
|---|---|
| Tree manifest | Registers the tree |
| Tree repository | Provides data to the tree |
| Tree store | Caches tree data |
| Tree item manifest | Renders individual items |
| Entity actions | Context menu items |
Common tree issues:
| Issue | Likely Cause |
|---|---|
| Tree doesn't appear | Missing tree manifest or wrong section alias |
| Items don't load | Repository not returning correct data structure |
| Can't expand items | hasChildren not set correctly |
| No context menu | Entity actions not registered for this entity type |
Debug steps:
1. Check Console for errors when clicking tree 2. Check Network tab - is the API being called? 3. Verify tree alias matches in all related manifests
---
VN-2: Hidden Tree Actions
Common Symptom: Cannot find expected button or action
Buttons and actions can be in several places:
| Location | How to Access |
|---|---|
| Tree context menu | Right-click on tree item |
| Tree item actions | Look for "..." (three dots) button on tree item row |
| Workspace header | Actions bar at top of workspace |
| Entity actions tray | Expandable tray (often bottom or side) |
If you can't find a button:
1. Right-click the tree item - context menu may have the action 2. Hover over tree items - action buttons may appear on hover 3. Look for "..." or kebab menu on the tree item row 4. Check workspace header if you've opened an item 5. Scroll the workspace - action bars can be at bottom
Tree actions vs Entity actions:
- Tree actions: Appear in tree context menu
- Entity actions: Appear in workspace header or action tray
- Collection actions: Appear in collection/list view toolbar
---
VN-3: Menu Item Visibility
Common Symptom: Menu items not appearing where expected
Menu items require:
1. Correct `menus` array in manifest 2. Section must be visible to user 3. Any conditions must be satisfied
Check the manifest:
{
type: 'menuItem',
alias: 'My.MenuItem',
name: 'My Menu Item',
menus: ['Umb.Menu.StructureSettings'], // Must match existing menu
meta: {
label: 'My Item',
icon: 'icon-settings'
}
}Common menu aliases:
| Menu | Alias |
|---|---|
| Settings structure | Umb.Menu.StructureSettings |
| Content menu | Umb.Menu.Content |
| Media menu | Umb.Menu.Media |
If menu item doesn't appear, verify the menu alias is correct and the user has access to the parent section.
Workspace Checks
VW-1: Missing Save Button
Common Symptom: Custom workspace opens correctly, displays data, form fields work, but there is no Save button in the workspace footer. User cannot save changes.
Note: Not all workspaces need a Save button. Read-only workspaces, preview workspaces, and dashboard-style workspaces that don't edit data don't need one. This check only applies to workspaces intended to edit and persist entity data.
Root cause: Missing workspaceAction manifest. The workspace context may implement submit() correctly, but without registering a workspaceAction using UmbSubmitWorkspaceAction, no button appears.
Debug checklist:
- [ ] Is there a
workspaceActionmanifest registered? - [ ] Does it use
UmbSubmitWorkspaceActionas theapi? - [ ] Does the condition's
matchvalue exactly match your workspace alias? - [ ] Is the manifest included in your bundle/entry point?
Solution: Add a workspaceAction manifest to your workspace manifests:
import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
// In your manifests array:
{
type: 'workspaceAction',
kind: 'default',
alias: 'My.WorkspaceAction.Save',
name: 'Save Workspace Action',
weight: 90,
api: UmbSubmitWorkspaceAction,
meta: {
label: 'Save',
look: 'primary',
color: 'positive',
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'My.Workspace.Alias', // Must match your workspace alias
},
],
}Why this happens: Unlike some frameworks where save behavior is implicit, Umbraco workspaces require explicit registration of UI actions. The workspace context's submit() method handles the data persistence logic, but the button to trigger it must be separately registered as a workspaceAction extension.
---
VW-2: Workspace Data Not Loading
Common Symptom: Workspace opens but shows empty/default values, even though data exists
Debug checklist:
- [ ] Check Network tab - is the GET request being made?
- [ ] Is the workspace context's
load()method being called? - [ ] Does the route include the correct entity ID parameter?
- [ ] Is the observable data being consumed correctly in the workspace view?
Common causes:
1. Route mismatch - The workspace route doesn't capture the ID parameter 2. Context not requesting data - load() not called or called with wrong ID 3. Observable not subscribed - Data loads but view doesn't react to it
Verification:
// Check your workspace context implements load correctly
async load(unique: string) {
// Should make API call and update state
const { data } = await this.#repository.requestByUnique(unique);
if (data) {
this.#data.setValue(data);
}
}---
VW-3: Workspace Submit Not Working
Common Symptom: Save button exists and can be clicked, but nothing happens or data doesn't persist
Debug checklist:
- [ ] Check Network tab - is a POST/PUT request being made when Save is clicked?
- [ ] If request is made, what is the response? (check for 400/500 errors)
- [ ] Does the workspace context implement
IUmbSubmittableWorkspaceContext? - [ ] Does the
submit()method call the repository's create/save method?
Common causes:
1. Submit not implemented - Context doesn't implement IUmbSubmittableWorkspaceContext 2. Repository method not called - submit() exists but doesn't persist data 3. Validation failing silently - Form validation blocks submission
Verification:
// Workspace context must implement submit
export class MyWorkspaceContext
extends UmbEntityWorkspaceContextBase<MyEntityModel>
implements IUmbSubmittableWorkspaceContext {
async submit() {
const data = this.#data.getValue();
if (!data) return;
if (this.getIsNew()) {
await this.#repository.create(data);
} else {
await this.#repository.save(data);
}
}
}