with one click
create-content-component
// Creates a new page builder content component for both Strapi and the Next.js frontend. Triggers: add/create page component, new page section, page builder component, add form component.
// Creates a new page builder content component for both Strapi and the Next.js frontend. Triggers: add/create page component, new page section, page builder component, add form component.
| name | create-content-component |
| description | Creates a new page builder content component for both Strapi and the Next.js frontend. Triggers: add/create page component, new page section, page builder component, add form component. |
Add a new page builder (component) to both Strapi and the Next.js frontend.
Before proceeding, validate inputs:
pricing-table). Reject PascalCase/camelCase like MyComponent or pricingTable — ask user to correct.sections, forms). Reject invalid formats.If invalid format provided, ask user to correct before proceeding.
Ask the user for:
testimonials, pricing-table)sections, forms, utilities, seo-utilities, elements (default: sections), or any custom category.If category doesn't exist in apps/strapi/src/components/, create the folder first before creating the component schema.
Before proceeding, first check if the component or similar one already exists in Strapi or the Next.js frontend. If so, ask user if they want to proceed, or go with a different name.
Given category sections and name testimonials:
sections.testimonialsapps/strapi/src/components/sections/testimonials.jsoncollectionName: components_sections_testimonials (format: components_{category}_{name_underscored})StrapiTestimonials (prefix Strapi + PascalCase of name)apps/ui/src/components/page-builder/components/sections/StrapiTestimonials.tsxCreate apps/strapi/src/components/{category}/{name}.json:
{
"collectionName": "components_{category}_{name_with_underscores}",
"info": {
"displayName": "{PascalCaseName}",
"description": ""
},
"options": {},
"attributes": {
"title": {
"type": "string",
"required": true
}
}
}
Populate attributes based on user requirements. Common attribute patterns:
{ "type": "string" } or { "type": "text" } (multiline) or { "type": "richtext" }"required": true{ "type": "component", "repeatable": false, "component": "utilities.link" }{ "type": "component", "repeatable": true, "component": "utilities.basic-image" }utilities.image-with-link or utilities.basic-image{ "type": "enumeration", "enum": ["option1", "option2"] }{ "type": "boolean", "default": false }Additional attribute options (add as needed):
description: admin UI hint (e.g. "description": "Displayed below the section title")default: default value (e.g. "default": "Click here")minLength/maxLength: string length constraints (e.g. "minLength": 3, "maxLength": 100)min/max: number constraintsprivate: hide from API response (e.g. "private": true)Reference the strapi documentation for more information on component schemas: https://docs.strapi.io/cms/backend-customization/models#model-schema
Edit apps/strapi/src/api/page/content-types/page/schema.json.
Add the new UID to the attributes.content.components array:
"content": {
"type": "dynamiczone",
"components": [
...existing,
"{category}.{name}"
]
}
Add files to apps/strapi/src/populateDynamicZone folder.
import type { Modules } from "@strapi/strapi"
import basicImagePopulate from "../utilities/basic-image"
import linkPopulate from "../utilities/link"
export default {
populate: {
links: linkPopulate,
image: basicImagePopulate,
steps: true,
},
} satisfies Modules.Documents.Params.Populate.NestedParams<"{category}.{name}">
true if the component has no nested relations/components (like "utilities.ck-editor-content": true){ populate: { fieldName: true } } for simple nested components{ populate: { fieldName: { populate: { media: true } } } } for deeply nested mediaModules.Documents.Params.Populate.NestedParams<"{category}.{name}"> so TypeScript validates field names against the generated Strapi component schema.Create apps/ui/src/components/page-builder/components/{category}/Strapi{PascalCaseName}.tsx:
import { Data } from "@repo/strapi-types"
import { removeThisWhenYouNeedMe } from "@/lib/general-helpers"
import { Container } from "@/components/elementary/Container"
export function Strapi{PascalCaseName}({
component,
}: {
readonly component: Data.Component<"{category}.{name}">
}) {
removeThisWhenYouNeedMe("Strapi{PascalCaseName}")
return (
<section>
<Container className="py-8">
<h2 className="mb-4 text-3xl font-bold">{component.title}</h2>
{/* TODO: Implement component UI */}
</Container>
</section>
)
}
Strapi{PascalCaseName}.displayName = "Strapi{PascalCaseName}"
export default Strapi{PascalCaseName}
Key patterns:
Data.Component<"{category}.{name}"> from @repo/strapi-typesremoveThisWhenYouNeedMe call — starter template placeholder, keep it for new componentsdisplayName set explicitly<Container> from @/components/elementary/Container, if it's not a root level component, or not explicit "container" component wrapping other components.Edit apps/ui/src/components/page-builder/index.tsx:
import Strapi{PascalCaseName} from "@/components/page-builder/components/{category}/Strapi{PascalCaseName}"
PageContentComponents under the matching category comment:"{category}.{name}": Strapi{PascalCaseName},
Run:
cd apps/strapi && pnpm generate:types
This updates @repo/strapi-types so the React component gets proper typing for Data.Component<"{category}.{name}">.
If expected paths are not found, search for existing similar files before reporting an error.
Example: glob for **/page-builder/**/Strapi*.tsx to find component location.
docs/page-builder.md — architecture overview, naming conventions, component props[HINT] Download the complete skill directory including SKILL.md and all related files