grill-me
Interview the user relentlessly about a plan or design until reaching shared understanding,…
Creating Headless CMS content models via code using the ModelFactory pattern. Use this skill when the developer wants to create, modify, or understand content model definitions, define fields and validators, set up reference fields between models, configure field layouts
$ npx -y skills add webiny/webiny-js --skill content-models --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/content-modelsContext preview
The summary Claude sees to decide when to auto-load this skill.
Creating Headless CMS content models via code using the ModelFactory pattern. Use this skill when the developer wants to create, modify, or understand content model definitions, define fields and validators, set up reference fields between models, configure field layouts
name: webiny-api-cms-content-models description: > Creating Headless CMS content models via code using the ModelFactory pattern. Use this skill when the developer wants to create, modify, or understand content model definitions, define fields and validators, set up reference fields between models, configure field layouts (including nested layouts inside object or dynamicZone fields), pick the correct Admin UI renderer for a field type (textInput/textInputs, lexicalEditor/lexicalEditors, file/files, objectAccordionSingle/objectAccordionMultiple, etc.), or work with the ModelFactory builder API. Also covers field types (text, longText, number, boolean, datetime, asset, file, ref, object, richText, dynamicZone), list (array) fields via .list() and the singular-vs-plural renderer rule, the mandatory datetime subtype (dateOnly/timeOnly/withTimezone/withoutTimezone), validation (required, unique, email, pattern, minLength, maxLength, gte, predefinedValues), single-entry (singleton) models via .singleEntry(), model/field tags via .tags(), and field rules via .rules() for access-control and conditional visibility/editability. Includes the correct `fields` projection syntax when querying entries via the SDK: `ref` fields use double-`values.` nesting (e.g. `values.author.values.name`) because they resolve to another entry, while `object` and `dynamicZone` sub-fields are inline and use a single `values.` (e.g. `values.author.name`) — getting this wrong silently returns null.
Content models are created using the `ModelFactory` pattern. You define a class implementing `ModelFactory.Interface`, use the fluent `ModelFactory.Builder` API to declare fields, validators, layout, and API names, then export with `ModelFactory.createImplementation()`. Register in `webiny.config.tsx` as `<Api.Extension>`.
Every code-based content model follows the same structure:
import { ModelFactory } from "webiny/api/cms/model";
class MyModelImpl implements ModelFactory.Interface {
async execute(builder: ModelFactory.Builder) {
return [
builder
.public({ modelId: "myModel", name: "My Model", group: "ungrouped" })
.description("Description of the model")
.fields(fields => ({
// field definitions here
}))
.layout([/* row definitions */])
.titleFieldId("fieldId")
.singularApiName("MyModel")
.pluralApiName("MyModels")
];
}
}
export default ModelFactory.createImplementation({
implementation: MyModelImpl,
dependencies: []
});Register in `webiny.config.tsx`:
<Api.Extension src={"/extensions/MyModel.ts"} />**YOU MUST include the full file path with the `.ts` extension in the `src` prop.** For example, use `src={"/extensions/MyModel.ts"}`, NOT `src={"/extensions/MyModel"}`. Omitting the file extension will cause a build failure.
**YOU MUST use `export default` for the `createImplementation()` call** when the file is targeted directly by an Extension `src` prop. Using a named export (`export const MyModel = ...`) will cause a build failure. Named exports are only valid inside files registered via `createFeature`.
| Method | Purpose | | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `.public({ modelId, name, group })` | Creates a public model (accessible via Read API). `modelId` is the internal DB identifier. `group` organizes it in the Admin sidebar. | | `.description("...")` | Model description shown in Admin UI | | `.fields(fields => ({ ... }))` | Define all fields using the fluent field builder | | `.layout([["field1", "field2"], ["field3"]])` | Arrange fields in rows in the Admin editor. Each inner array is one row. | | `.titleFieldId("name")` | Which field to use as the entry's display title | | `.descriptionFieldId("message")` | Which field to use as the entry's description | | `.singularApiName("Product")` | Singular name for GraphQL queries (e.g., `getProduct`) | | `.pluralApiName("Products")` | Plural name for GraphQL queries (e.g., `listProducts`) | | `.singleEntry()` | Makes the model a singleton (only one en
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…