grill-me
Interview the user relentlessly about a plan or design until reaching shared understanding,…
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()
$ npx -y skills add webiny/webiny-js --skill custom-field-type --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/custom-field-typeContext preview
The summary Claude sees to decide when to auto-load 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()
name: webiny-api-cms-custom-field-type description: > 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.
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.
Use a custom field type when:
A custom field type consists of three parts:
1. **Builder interface** — extends `DataFieldBuilder<"type">` plus `FieldTypeValidator.*` types 2. **Builder class** — implements the interface, calls `this.validation()` for each validator 3. **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);
}
});// 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: []
});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")
];
}
}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 th
Open-source content platform. Self-hosted on AWS serverless. Built as a TypeScript framework you extend with code, not a closed product you configure through a UI. Runs on Lambda, DynamoDB, S3, and CloudFront inside your own AWS account. Scales automatically.
Repo: webiny/webiny-js
Interview the user relentlessly about a plan or design until reaching shared understanding,…
Turn a PRD into a multi-phase implementation plan using tracer-bullet vertical slices, saved…
Webiny-only. Run all checks required before packages are ready for publish: deps, build,…
Use when running tests. Shows how to run tests for a single package, including OpenSearch…
Generate, refresh, and maintain Webiny MCP server skills from source documentation and…
Create a PRD through user interview, codebase exploration, and module design, then submit as…