Agent Skillswebiny/webiny-js › webiny-new-entry-wizard

webiny-new-entry-wizard

GitHub

指导如何在Headless CMS中构建新条目向导,拦截创建流程以收集初始值并预填表单。涵盖组件注册、DI抽象定义及Feature集成等核心开发实现步骤。

skills/user-skills/admin/new-entry-wizard/SKILL.md webiny/webiny-js

Trigger Scenarios

需要为CMS内容模型添加自定义的创建前向导UI 希望在进入主编辑器前收集标题或分类等前置字段 集成Webiny Admin扩展以实现新的EntryWizard流程

Install

npx skills add webiny/webiny-js --skill webiny-new-entry-wizard -g -y
More Options

Non-standard path

npx skills add https://github.com/webiny/webiny-js/tree/next/skills/user-skills/admin/new-entry-wizard -g -y

Use without installing

npx skills use webiny/webiny-js@webiny-new-entry-wizard

指定 Agent (Claude Code)

npx skills add webiny/webiny-js --skill webiny-new-entry-wizard -a claude-code -g -y

安装 repo 全部 skill

npx skills add webiny/webiny-js --all -g -y

预览 repo 内 skill

npx skills add webiny/webiny-js --list

SKILL.md

Frontmatter
{
    "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."
}

New Entry Wizard

TL;DR

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

Registration

// 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;

Props

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.

Enabling the extension

// webiny.config.tsx
<Admin.Extension src={"@/extensions/myWizard/index.tsx"} />

Abstractions

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");

Feature

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) };
  }
});

Presenter

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.

View

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
import React, { useCallback } from "react";
import { useFeature, createReactiveComponent } from "webiny/admin";
import { useContentEntryFormPresenter } from "webiny/admin/cms/entry/editor";
import { FormView, FormErrors } from "webiny/admin/form";
import { Button } from "webiny/admin/ui";
import { MyWizardFeature } from "./feature.js";

export const MyWizardForm = createReactiveComponent(() => {
  const { presenter } = useFeature(MyWizardFeature);
  const formPresenter = useContentEntryFormPresenter();

  const handleSubmit = useCallback(async () => {
    const data = await presenter.submit();
    if (data !== false) {
      formPresenter.newEntry({
        title: data.title,
        settings: {
          general: {
            slug: data.slug
          }
        }
      });
    }
  }, [presenter, formPresenter]);

  const { form } = presenter.vm;

  return (
    <div className="flex justify-center pt-xl">
      <div className="bg-neutral-base rounded-lg p-lg flex flex-col gap-md" style={{ width: 600 }}>
        <h3 className="text-lg font-semibold">Create New Article</h3>
        <FormErrors form={form} />
        <FormView name="MyWizard" form={form} />
        <div className="flex justify-end gap-sm">
          <Button variant="primary" onClick={handleSubmit}>
            Create
          </Button>
        </div>
      </div>
    </div>
  );
});

Key APIs

formPresenter.newEntry(initialValues?)

Called from the wizard view to transition to the entry editor. When initialValues is provided, the entry form is pre-filled and marked as dirty (unsaved changes). The values map to the content model's field structure.

formPresenter.newEntry({
  title: "My Article",
  description: "A short description",
  settings: {
    general: {
      slug: "my-article"
    }
  }
});

createReactiveComponent

Wraps a component so it re-renders when MobX observables (like presenter.vm) change. Always use this for wizard views that read from a presenter.

useContentEntryFormPresenter()

Returns the content entry form presenter. Used inside the wizard to call newEntry() after the wizard form is submitted.

useFeature(feature)

Resolves the feature from the DI container. Returns the object produced by the feature's resolve() callback.

How It Works

  1. User clicks "New Entry" -- the route sets id=new
  2. If a NewEntryWizard is configured for the current model, the wizard element renders instead of the entry form
  3. The wizard collects data via its own FormModel and validates on submit
  4. On successful submit, the wizard calls formPresenter.newEntry(initialValues)
  5. This creates the entry form, pre-fills it with setData(initialValues, { dirty: true }), and the view switches from the wizard to the entry editor
  6. The entry form shows as dirty -- the user can review and save

File Structure

extensions/myWizard/
  index.tsx              -- registration (NewEntryWizard config + RegisterFeature)
  abstractions.ts        -- DI token + presenter interface (ALWAYS a standalone file)
  feature.ts             -- createFeature wiring (ALWAYS a standalone file)
  MyWizardPresenter.ts   -- presenter implementation (FormModel, business logic)
  MyWizardForm.tsx       -- view (createReactiveComponent, FormView, submit handler)

Iron rule: abstractions.ts and feature.ts are ALWAYS separate, standalone files. Never inline abstractions or feature definitions into the presenter or view.

Version History

  • 80eb1c5 Current 2026-08-20 10:06

Same Skill Collection

.claude/skills/grill-me/SKILL.md
.claude/skills/prd-to-plan/SKILL.md
.claude/skills/preflight/SKILL.md
.claude/skills/tester/SKILL.md
.claude/skills/write-a-prd/SKILL.md
skills/repo-skills/add-feature-flag/SKILL.md
skills/user-skills/admin/admin-architect/SKILL.md
skills/user-skills/admin/admin-permissions/SKILL.md
skills/user-skills/admin/form-model/SKILL.md
skills/user-skills/admin/website-builder/page-settings/SKILL.md
skills/user-skills/admin/website-builder/wb-preview-url-modifier/SKILL.md
skills/user-skills/api-bundle-size-limit/SKILL.md
skills/user-skills/api/api-architect/SKILL.md
skills/user-skills/api/cms-bulk-actions/SKILL.md
skills/user-skills/api/custom-field-type/SKILL.md
skills/user-skills/api/event-handler-pattern/SKILL.md
skills/user-skills/api/graphql-api/SKILL.md
skills/user-skills/api/http-route/SKILL.md
skills/user-skills/api/permissions/SKILL.md
skills/user-skills/api/use-case-pattern/SKILL.md
skills/user-skills/api/v5-to-v6-migration/SKILL.md
skills/user-skills/api/websocket-notifications/SKILL.md
skills/user-skills/cli-extensions/SKILL.md
skills/user-skills/configure-auth0/SKILL.md
skills/user-skills/configure-entraid/SKILL.md
skills/user-skills/configure-okta/SKILL.md
skills/user-skills/dependency-injection/SKILL.md
skills/user-skills/full-stack-architect/SKILL.md
skills/user-skills/generated/api/aco/SKILL.md
skills/user-skills/generated/api/cms/SKILL.md
skills/user-skills/generated/api/file-manager/SKILL.md
skills/user-skills/generated/api/scheduler/SKILL.md
skills/user-skills/generated/api/security/SKILL.md
skills/user-skills/generated/api/system/SKILL.md
skills/user-skills/generated/api/tenancy/SKILL.md
skills/user-skills/generated/api/tenant-manager/SKILL.md
skills/user-skills/generated/api/website-builder/SKILL.md
skills/user-skills/generated/infra/SKILL.md
skills/user-skills/infrastructure-extensions/SKILL.md
skills/user-skills/local-development/SKILL.md
skills/user-skills/mailer-smtp/SKILL.md
skills/user-skills/project-structure/SKILL.md
.claude/skills/webiny-skill-creator/SKILL.md
skills/user-skills/admin/ui-extensions/SKILL.md
skills/user-skills/api/ai-powerups-content/SKILL.md
skills/user-skills/cognito-federation/SKILL.md
skills/user-skills/content-models/SKILL.md
skills/user-skills/generated/admin/aco/SKILL.md
skills/user-skills/generated/admin/ai-powerups/SKILL.md

Metadata

Files
0
Version
80eb1c5
Hash
b4cc9d93
Indexed
2026-08-20 10:06

inicio - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-22 07:13
浙ICP备14020137号-1 $mapa de visitantes$