grill-me
Interview the user relentlessly about a plan or design until reaching shared understanding,…
Building a New Entry Wizard for the Headless CMS. Use this skill when the developer wants to show a custom wizard UI before the entry form when creating new CMS entries -- collecting fields like title, slug, or category upfront, then pre-filling the entry form. Covers
$ npx -y skills add webiny/webiny-js --skill new-entry-wizard --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/new-entry-wizardContext preview
The summary Claude sees to decide when to auto-load this skill.
Building a New Entry Wizard for the Headless CMS. Use this skill when the developer wants to show a custom wizard UI before the entry form when creating new CMS entries -- collecting fields like title, slug, or category upfront, then pre-filling the entry form. Covers
name: webiny-new-entry-wizard description: > Building a New Entry Wizard for the Headless CMS. Use this skill when the developer wants to show a custom wizard UI before the entry form when creating new CMS entries -- collecting fields like title, slug, or category upfront, then pre-filling the entry form. Covers ContentEntryEditorConfig.NewEntryWizard, createFeature, FormModelFactory, createReactiveComponent, useContentEntryFormPresenter, and the dirty-flag on setData.
A New Entry Wizard intercepts the "create new entry" flow in the Headless CMS and shows a custom form before the full entry editor. The wizard collects initial values (title, slug, category, etc.), then calls `formPresenter.newEntry(initialValues)` to pre-fill the entry form and transition to the editor. The form is automatically marked as dirty so the user sees unsaved changes.
A wizard extension has five files:
1. **`abstractions.ts`** -- DI token and presenter interface (ALWAYS a standalone file) 2. **`feature.ts`** -- `createFeature` wiring (ALWAYS a standalone file) 3. **Presenter** -- owns the wizard form (fields, layout, validation) via `FormModelFactory` 4. **View** -- renders the wizard UI using `FormView` and `FormErrors` 5. **Registration** -- plugs the wizard into specific content models via `ContentEntryEditorConfig.NewEntryWizard`
// extensions/myWizard/index.tsx
import React from "react";
import { RegisterFeature } from "webiny/admin";
import { ContentEntryEditorConfig } from "webiny/admin/cms/entry/editor";
import { MyWizardFeature } from "./feature.js";
import { MyWizardForm } from "./MyWizardForm.js";
const MyWizardExtension = () => {
return (
<>
<RegisterFeature feature={MyWizardFeature} />
<ContentEntryEditorConfig>
<ContentEntryEditorConfig.NewEntryWizard
element={<MyWizardForm />}
modelIds={["article", "blogPost"]}
/>
</ContentEntryEditorConfig>
</>
);
};
export default MyWizardExtension;| Prop | Type | Description | | ---------- | -------------------- | ----------------------------------------------------------------------------------- | | `element` | `React.ReactElement` | The wizard component to render | | `modelIds` | `string[]` | Content model IDs this wizard applies to. Omit or pass `[]` to apply to all models. |
// webiny.config.tsx
<Admin.Extension src={"@/extensions/myWizard/index.tsx"} />Abstractions MUST live in a dedicated `abstractions.ts` file. This file defines the DI token for the presenter. It is imported by both the feature and the view.
// extensions/myWizard/abstractions.ts
import { createAbstraction } from "webiny/admin";
import type { FormModel } from "webiny/admin/form";
export interface MyWizardVM {
form: FormModel.FormVM;
data: Record<string, unknown>;
}
export interface IMyWizardPresenter {
vm: MyWizardVM;
submit(): Promise<Record<string, unknown> | false>;
reset(): void;
}
export const MyWizardPresenter = createAbstraction<IMyWizardPresenter>("MyWizardPresenter");The feature MUST live in a dedicated `feature.ts` file. It wires the presenter implementation to the abstraction and defines how the feature is resolved from the DI container.
// extensions/myWizard/feature.ts
import { createFeature, FormModelFactory } from "webiny/admin";
import { MyWizardPresenter } from "./abstractions.js";
import { MyWizardPresenterImpl } from "./MyWizardPresenter.js";
export const MyWizardFeature = createFeature({
name: "MyWizard",
register(container) {
container.register(
MyWizardPresenter.createImplementation({
implementation: MyWizardPresenterImpl,
dependencies: [FormModelFactory]
})
);
},
resolve(container) {
return { presenter: container.resolve(MyWizardPresenter) };
}
});The presenter owns a `FormModel` for the wizard's own fields. It is a plain class -- the DI token lives in `abstractions.ts` and the feature wiring lives in `feature.ts`.
// extensions/myWizard/MyWizardPresenter.ts
import type { FormModel } from "webiny/admin/form";
import { makeAutoObservable, toJS } from "mobx";
import { FormModelFactory } from "webiny/admin";
import type { IMyWizardPresenter, MyWizardVM } from "./abstractions.js";
export class MyWizardPresenterImpl implements IMyWizardPresenter {
private form: FormModel.Interface;
constructor(formFactory: FormModelFactory.Interface) {
this.form = formFactory.create({
fields: fields => ({
title: fields.text().label("Title").required("Title is required"),
slug: fields.text().label("Slug").required("Slug is required"),
category: fields
.text()
.label("Category")
.options([
{ label: "News", value: "news" },
{ label: "Tutorial", value: "tutorial" }
])
}),
layout: layout => [layout.row("title"), layout.row("slug"), layout.row("category")]
});
makeAutoObservable(this);
}
get vm(): MyWizardVM {
return {
form: this.form.vm,
data: toJS(this.form.getData())
};
}
async submit(): Promise<Record<string, unknown> | false> {
return this.form.submit();
}
reset(): void {
this.form.reset();
}
}The presenter uses the same `FormModel` system as all Webiny forms. See the `webiny-form-model` skill for field types, renderers, layout options, and validation.
The view renders the wizard form and handles submission. On submit, it calls `formPresenter.newEntry(initialValues)` to transition from the wizard to the entry editor with pre-filled values.
// extensions/myWizard/MyWizardForm.tsx imp
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…