Webiny-js webiny-api-cms-content-models
install
source · Clone the upstream repo
git clone https://github.com/webiny/webiny-js
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/webiny/webiny-js "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/user-skills/content-models" ~/.claude/skills/webiny-webiny-js-webiny-api-cms-content-models && rm -rf "$T"
manifest:
skills/user-skills/content-models/SKILL.mdsource content
Creating Content Models via Code
TL;DR
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>.
The ModelFactory Pattern
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
extension in the .ts
prop. For example, use src
src={"/extensions/MyModel.ts"}, NOT src={"/extensions/MyModel"}. Omitting the file extension will cause a build failure.
YOU MUST use
for the export default
call when the file is targeted directly by an Extension createImplementation()
src prop. Using a named export (export const MyModel = ...) will cause a build failure. Named exports are only valid inside files registered via createFeature.
Model Configuration Methods
| Method | Purpose |
|---|---|
| Creates a public model (accessible via Read API). is the internal DB identifier. organizes it in the Admin sidebar. |
| Model description shown in Admin UI |
| Define all fields using the fluent field builder |
| Arrange fields in rows in the Admin editor. Each inner array is one row. |
| Which field to use as the entry's display title |
| Which field to use as the entry's description |
| Singular name for GraphQL queries (e.g., ) |
| Plural name for GraphQL queries (e.g., ) |
| Makes the model a singleton (only one entry can exist). Automatically adds the tag. |
| Assign custom tags to the model. The tag is always added automatically. Duplicates are removed. |
Field Types
| Builder Method | Description | Common Renderers |
|---|---|---|
| Single-line text | |
| Multi-line text | |
| Rich text (Lexical) | |
| Numeric value | |
| True/false toggle | |
| Date/time picker | |
| File/image attachment | |
| Reference to another model | , |
| Nested object with sub-fields | |
Field Validators (Chainable)
| Validator | Description | Example |
|---|---|---|
| Field is required | |
| Value must be unique across entries | |
| Must be a valid email | |
| Must match a regex | |
| Minimum string length | |
| Maximum string length | |
| Greater than or equal (numbers) | |
| Restrict to predefined options | |
Field Configuration (Chainable)
| Method | Description |
|---|---|
| Set the Admin UI renderer |
| Field label in the editor |
| Helper text shown below the field |
| Make the field accept multiple values (arrays) |
| For fields: which models can be referenced |
| Assign tags to a field (e.g., ) |
Full Examples
Product Category Model
// extensions/ProductCategoryModel.ts import { ModelFactory } from "webiny/api/cms/model"; export const PRODUCT_CATEGORY_MODEL_ID = "productCategory"; class ProductCategoryModelImpl implements ModelFactory.Interface { async execute(builder: ModelFactory.Builder) { return [ builder .public({ modelId: PRODUCT_CATEGORY_MODEL_ID, name: "Product Category", group: "ungrouped" }) .description("Product categories for organizing products") .fields(fields => ({ name: fields .text() .renderer("textInput") .label("Name") .help("Name of the product category") .required("Name is required") .minLength(2) .maxLength(100), slug: fields .text() .renderer("textInput") .label("Slug") .help("URL-friendly identifier") .required("Slug is required") .unique(), description: fields.longText().renderer("textarea").label("Description").minLength(10) })) .layout([["name", "slug"], ["description"]]) .titleFieldId("name") .singularApiName("ProductCategory") .pluralApiName("ProductCategories") ]; } } export default ModelFactory.createImplementation({ implementation: ProductCategoryModelImpl, dependencies: [] });
Product Model (with Reference Field)
// extensions/ProductModel.ts import { ModelFactory } from "webiny/api/cms/model"; export const PRODUCT_MODEL_ID = "product"; class ProductModelImpl implements ModelFactory.Interface { async execute(builder: ModelFactory.Builder) { return [ builder .public({ modelId: PRODUCT_MODEL_ID, name: "Product", group: "ungrouped" }) .description("Products for our e-commerce store") .fields(fields => ({ name: fields .text() .renderer("textInput") .label("Name") .help("Product name") .required("Name is required"), sku: fields .text() .renderer("textInput") .label("SKU") .help("Stock Keeping Unit - unique product identifier") .required("SKU is required") .unique(), description: fields .longText() .renderer("textarea") .label("Description") .help("Detailed product description"), price: fields .number() .renderer("numberInput") .label("Price") .required("Price is required") .gte(0, "Price must be greater than or equal to 0"), category: fields .ref() .renderer("refDialogSingle") .label("Category") .models([{ modelId: "productCategory" }]) })) .layout([["name"], ["sku"], ["category"], ["description"], ["price"]]) .titleFieldId("name") .singularApiName("Product") .pluralApiName("Products") ]; } } export default ModelFactory.createImplementation({ implementation: ProductModelImpl, dependencies: [] });
Contact Submission Model (with Predefined Values)
// extensions/contactSubmission/ContactSubmissionModel.ts import { ModelFactory } from "webiny/api/cms/model"; export const CONTACT_SUBMISSION_MODEL_ID = "contactSubmission"; class ContactSubmissionModelImpl implements ModelFactory.Interface { async execute(builder: ModelFactory.Builder) { return [ builder .public({ modelId: CONTACT_SUBMISSION_MODEL_ID, name: "Contact Submission", group: "ungrouped" }) .description("Stores contact form submissions from the website") .fields(fields => ({ name: fields .text() .renderer("textInput") .label("Name") .help("Enter your full name") .required("Name is required") .minLength(2) .maxLength(100), email: fields .text() .renderer("textInput") .label("Email") .help("Enter a valid email address") .required("Email is required") .email(), message: fields .longText() .renderer("textarea") .label("Message") .help("Enter your message...") .required("Message is required") .minLength(10) .maxLength(1000), emailType: fields .text() .renderer("radioButtons") .label("Email Type") .help("Automatically classified as Work or Personal") .predefinedValues([ { label: "Work", value: "work" }, { label: "Personal", value: "personal" } ]) })) .layout([["name", "email"], ["message"], ["emailType"]]) .titleFieldId("name") .descriptionFieldId("message") .singularApiName("ContactSubmission") .pluralApiName("ContactSubmissions") ]; } } export default ModelFactory.createImplementation({ implementation: ContactSubmissionModelImpl, dependencies: [] });
Quick Reference
Import: import { ModelFactory } from "webiny/api/cms/model"; Interface: ModelFactory.Interface Builder: ModelFactory.Builder Export: export default ModelFactory.createImplementation({ implementation, dependencies }) Register: <Api.Extension src={"/extensions/MyModel.ts"} /> Deploy: yarn webiny deploy api (or use watch mode)
Related Skills
-- Thewebiny-dependency-injection
pattern used herecreateImplementation
-- Query and write data to your models from external appswebiny-sdk