Custom CMS Field Type
SkillAI & modelsGuides your agent through building a custom content-management-system field type with its own validation rules.
Use Custom CMS Field Type in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Custom CMS Field Type and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Custom CMS Field Type skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; ahel provides instructions and does not run this skill.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
About this skill
How to implement a custom CMS field type that integrates with the model builder's fluent API. Covers extending DataFieldBuilder, composing validator interfaces, creating a FieldTypeFactory, registering via DI, and module augmentation for TypeScript autocomplete on the fields() registry.
What this skill tells your AI
The instructions your AI receives, as published by webiny/webiny-js in skills/user-skills/api/custom-field-type/SKILL.md and read by ahel’s review.
TL;DR
A custom field type is a class that extends DataFieldBuilder<"yourType">, paired with a factory class implementing FieldType.Factory. Register the factory with container.register(YourFieldType). Add a module augmentation on "webiny/api/cms/model" so the fields registry method gets TypeScript autocomplete.
When to Use This
Use a custom field type when:
- You need a field with a storage format or validation logic not covered by the built-in types (
text,number,boolean,datetime,file,ref,object,richText,longText,json,dynamicZone) - You want to expose a fluent builder API (e.g.,
fields.slug(),fields.color()) inModelFactoryimplementations
Field Type Structure
A custom field type consists of three parts:
- Builder interface — extends
DataFieldBuilder<"type">plusFieldTypeValidator.*types - Builder class — implements the interface, calls
this.validation()for each validator - Factory class — implements
FieldType.Factory, creates builder instances
As a standalone extension (not part of a larger feature), the directory layout is:
extensions/
└── SlugFieldType/
├── SlugFieldType.ts # builder interface, builder class, factory class
└── feature.ts # createFeature — registers the factory into the DI container
feature.ts:
// extensions/SlugFieldType/feature.ts
import { createFeature } from "webiny/api";
import { SlugFieldType } from "./SlugFieldType.js";
export const SlugFieldTypeFeature = createFeature({
name: "SlugFieldType",
register(container) {
container.register(SlugFieldType);
}
});
Register in the API entry point:
// api/Extension.ts
import { createFeature } from "webiny/api";
import { SlugFieldTypeFeature } from "~/extensions/SlugFieldType/feature.js";
export const Extension = createFeature({
name: "MyExtension",
register(container) {
SlugFieldTypeFeature.register(container);
}
});
Complete Example
// extensions/SlugFieldType/SlugFieldType.ts
import { DataFieldBuilder, FieldType } from "webiny/api/cms/model";
import type { FieldTypeValidator } from "webiny/api/cms/model";
// 1. Builder interface — extends DataFieldBuilder + desired FieldTypeValidator types
export interface ISlugFieldBuilder
extends
DataFieldBuilder<"slug">,
FieldTypeValidator.Required,
FieldTypeValidator.Pattern,
FieldTypeValidator.Unique {}
// 2. Module augmentation — adds fields.slug() to the registry
declare module "webiny/api/cms/model" {
interface IFieldBuilderRegistry {
slug(): ISlugFieldBuilder;
}
interface IFieldRendererRegistry {
myCustomRenderer: {
fieldType: "text" | "number";
settings: undefined;
};
}
}
// 3. Builder class — implements each validator method via this.validation()
class SlugFieldBuilder extends DataFieldBuilder<"slug"> implements ISlugFieldBuilder {
constructor() {
super("slug");
}
required(message?: string): this {
return this.validation({
name: "required",
message: message || "Value is required.",
settings: {}
});
}
pattern(regex: string, flags = "", message?: string): this {
return this.validation({
name: "pattern",
message: message || "Invalid value.",
settings: { preset: "custom", regex, flags }
});
}
unique(message?: string): this {
return this.validation({
name: "unique",
message: message || "Value must be unique.",
settings: {}
});
}
}
// 4. Factory class — implements FieldType.Factory
class SlugFieldTypeFactory implements FieldType.Factory {
readonly type = "slug";
create(): ISlugFieldBuilder {
return new SlugFieldBuilder();
}
}
// 5. Export as a FieldType implementation
export const SlugFieldType = FieldType.createImplementation({
implementation: SlugFieldTypeFactory,
dependencies: []
});
Using the Custom Field in a Model
After registration, fields.slug() is available in any ModelFactory implementation:
import { ModelFactory } from "webiny/api/cms/model";
class ProductModelImpl implements ModelFactory.Interface {
async execute(builder: ModelFactory.Builder) {
return [
builder
.public({ modelId: "product", name: "Product", group: "ungrouped" })
.fields(fields => ({
name: fields.text().label("Name").required(),
slug: fields
.slug()
.label("Slug")
.required("Slug is required.")
.unique()
.pattern("^[a-z0-9-]+$", "", "Only lowercase letters, numbers, and hyphens.")
}))
.layout([["name", "slug"]])
.titleFieldId("name")
.singularApiName("Product")
.pluralApiName("Products")
];
}
}
DataFieldBuilder API
All methods return this for chaining.
| Method | Description |
|---|---|
label(text) | Field label shown in the Admin editor |
help(text) | Help text shown below the field |
description(text) | Field description |
fieldId(id) | Override the auto-derived field ID |
storageId(id) | Override the storage identifier |
placeholder(text) | Placeholder text for the input |
defaultValue(value) | Default value for new entries |
list() | Make the field accept multiple values (array) |
listMinLength(n, msg?) | Minimum number of list items |
listMaxLength(n, msg?) | Maximum number of list items |
tags(tags) | Arbitrary tags for filtering/querying |
renderer(name, settings?) | Set the Admin UI renderer |
settings(settings) | Set arbitrary field settings |
Protected Methods (for use inside validator implementations only)
| Method | Description |
|---|---|
this.validation(rule) | Append a CmsModelFieldValidation to the field's validation array |
this.listValidation(rule) | Append a CmsModelFieldValidation to the list validation array |
A CmsModelFieldValidation has the shape:
{
name: string; // validator name (e.g., "required", "minLength", "pattern")
message: string; // error message shown to the user
settings: Record<string, any>; // validator-specific config
}
Available Validators
Import via import type { FieldTypeValidator } from "webiny/api/cms/model" and extend your builder interface with them. Each type adds one method to your interface:
| Type | Method signature |
|---|---|
FieldTypeValidator.Required | required(message?) |
FieldTypeValidator.Unique | unique(message?) |
FieldTypeValidator.MinLength | minLength(value, message?) |
FieldTypeValidator.MaxLength | maxLength(value, message?) |
FieldTypeValidator.Pattern | pattern(regex, flags?, message?) |
FieldTypeValidator.Email | email(message?) |
FieldTypeValidator.Url | url(message?) |
FieldTypeValidator.LowerCase | lowerCase(message?) |
FieldTypeValidator.UpperCase | upperCase(message?) |
FieldTypeValidator.LowerCaseSpace | lowerCaseSpace(message?) |
FieldTypeValidator.UpperCaseSpace | upperCaseSpace(message?) |
FieldTypeValidator.Gte | gte(value, message?) |
FieldTypeValidator.Lte | lte(value, message?) |
FieldTypeValidator.DateGte | dateGte(value, message?) |
FieldTypeValidator.DateLte | dateLte(value, message?) |
FieldTypeValidator.ListMinLength | listMinLength(value, message?) |
FieldTypeValidator.ListMaxLength | listMaxLength(value, message?) |
When implementing a validator method in the builder class, call this.validation() with the appropriate name and settings. For ListMinLength/ListMaxLength, call this.listValidation() instead. The settings shapes:
| Validator | name | settings |
|---|---|---|
| Required, Unique | "required" / "unique" | {} |
| MinLength, MaxLength | "minLength" / "maxLength" | { value: String(n) } |
| Gte, Lte | "gte" / "lte" | { value: String(n) } |
| DateGte, DateLte | "dateGte" / "dateLte" | { value } |
| Pattern | "pattern" | { preset: "custom", regex, flags } |
"pattern" | { preset: "email", regex: null, flags: null } | |
| Url | "pattern" | { preset: "url", regex: null, flags: null } |
| LowerCase / UpperCase / etc. | "pattern" | { preset: "lowerCase" / "upperCase" / etc., regex: null, flags: null } |
Key Rules
typestring must be unique — the factory'sreadonly typemust not collide with any built-in type (text,number,boolean,datetime,file,ref,object,richText,longText,json,dynamicZone) or other custom types.- Module augmentation target — augment
"webiny/api/cms/model"usingnamespace FieldBuilderRegistry { interface Interface { yourType(): IYourFieldBuilder; } }. validation()is protected — never call it from outside the builder class. Expose validators as named methods on the interface (e.g.,required(),minLength()).dependencies: []— field type factories have no DI dependencies; always pass an empty array.- Registration order — register custom
FieldTypeimplementations beforeFieldBuilderRegistryis resolved (i.e., in the sameregister()call or before it runs). The registry collects allFieldTypeinstances at construction time.
Related Skills
- webiny-api-cms-content-models — Using the model builder's fluent API to define CMS models
- webiny-api-cms-catalog — Full catalog of CMS abstractions including
ModelFactory,FieldType,DataFieldBuilder - webiny-dependency-injection — The
createImplementationpattern and DI scoping
Signals
- GitHub stars
- 8k
- Forks
- 679
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
webiny-api-cms-custom-field-type- Source
- github.com/webiny/webiny-js
Related picks
Skill · mattpocock
The pick for TypeScripttypescript-pro
Skill · jeffallan
The pick for TypeScriptskill-creator
Skill · anthropics
More in AI & modelstriage
Skill · mattpocock
More in AI & modelsalgorithmic-art
Skill · anthropics
More in AI & modelscode-review-and-quality
Skill · addyosmani
More in AI & models