Bridge Builder.io Fusion (code generation) and Publish (visual CMS) by registering React components for the visual editor.
What You'll Do
- Set up Publish integration — scaffold the catch-all route, registry, SDK config, and .builderrules
- Register a component — read TypeScript props, map to Builder input types, add to registry
- Bulk-register components — scan a directory and register all components at once
Workflow 1: Set Up Publish Integration
Run this once per project. Each step is idempotent — skip what already exists.
Prerequisites
- A Next.js App Router project (with
app/orsrc/app/) - A Builder.io Publish space with a public API key
Steps
Check each item. Create only what is missing. If a file exists but differs from the expected pattern, show the user and ask how to proceed.
-
Detect project structure. Look for
src/app/first, thenapp/. If neither exists, checkapps/*/app/and ask the user if multiple matches. Note the app root for all subsequent steps. -
Ensure SDK is installed. Check
package.jsonfor@builder.io/react. If missing, install it along with@builder.io/dev-tools:npm install @builder.io/react @builder.io/dev-tools -
Configure the API key. Check
.env.localforNEXT_PUBLIC_BUILDER_API_KEY. If not found, check.env. If in.envbut not.env.local, warn the user: "Your API key is in.env, which may be committed to version control. Consider moving it to.env.local." If not found anywhere, ask the user for their Builder.io public API key. Write it to.env.local. Ensure.gitignoreincludes.env*.local. Create.env.examplewithNEXT_PUBLIC_BUILDER_API_KEY=your-builder-public-api-key. -
Create
builder-registry.tsat the project root. This is the central registration file. See references/scaffolding-templates.md for the exact template. Key points:- Must have
"use client"directive - Initializes the client-side SDK via
@builder.io/react - Uses a runtime guard for the API key (no
!assertion)
- Must have
-
Create the catch-all route at
app/[...page]/page.tsx(orsrc/app/[...page]/page.tsx). This is a server component that fetches Builder content. See references/scaffolding-templates.md. Key points:- Uses
@builder.io/sdk(NOT@builder.io/react) for server-side fetching - Has its own
builder.init()— this is separate from the client-side init in the registry - Uses
[...page](required catch-all), NOT[[...page]](optional), to preserve the user's homepage - Detect the Next.js version from
package.jsonfor the correctparamspattern (Promise in 15+, direct in 14)
Before creating, check for existing catch-all or dynamic routes (
[...page],[[...page]],[...slug]). If found, show the user and ask how to proceed. - Uses
-
Create
components/builder.tsx(theRenderBuilderContentwrapper). See references/scaffolding-templates.md. Key points:- Must have
"use client"directive - Imports
@/builder-registryas a side-effect (triggers all component registrations) - Accepts an optional
modelprop (defaults to"page")
- Must have
-
Wrap
next.configwith@builder.io/dev-tools. Check if already wrapped by searching for@builder.io/dev-toolsorBuilderDevTools. If found, skip. If the file hasBuilderDevTools()(BuilderDevTools()((double wrapping), fix it. Handle.ts,.js, and.mjsvariants. -
Create or update
.builderruleswith component creation conventions. Adapt the template to match existing project conventions (e.g., Tailwind vs CSS Modules,src/vs root). See references/scaffolding-templates.md for the default template.
After Scaffolding
Tell the user: "Publish integration is set up. Deploy your app to a public URL (Vercel, Netlify, etc.) — Builder's visual editor cannot work with localhost. Then use this skill to register components."
Workflow 2: Register a Component
When the user wants to register a component for Publish's visual editor.
Prerequisites
Verify scaffolding is complete: builder-registry.ts exists, the catch-all route exists, SDK is installed. If anything is missing, offer to run Workflow 1 first.
Steps
-
Read the component file. Find the exported props interface or type. If the type is imported from another file, follow the import to resolve it. Skip React-internal types (
ReactNode,CSSProperties,MouseEventHandler,HTMLAttributes, etc.). -
Map props to Builder inputs. Use the type mapping in references/sdk-reference.md. Key rules:
- Evaluate TypeScript type first, then apply name heuristics only for plain
stringprops - String literal unions (
'a' | 'b') →type: "text"withenumarray (NOTtype: "enum") listinputs MUST havedefaultValue: []objectinputs MUST havedefaultValue: {}children: ReactNode→ skip the prop, addcanHaveChildren: trueto the registration- Generate
friendlyNamefrom PascalCase props (backgroundColor→"Background Color") - Generate
helperTextfor non-obvious inputs - Use
advanced: truefor typically-defaulted props (className,id)
- Evaluate TypeScript type first, then apply name heuristics only for plain
-
Generate the registration. Add a
Builder.registerComponent()call tobuilder-registry.tsusingdynamic(() => import(...))with the correct relative path from the registry file (or use the@/alias). See references/examples.md for complete examples. -
Optionally update the showcase page. If
app/page.tsxhas a "Component Showcase" pattern, add the component with sample data. If the homepage is not a showcase page, skip this step. -
Guide verification. Tell the user:
- Deploy the updated app
- Open Builder.io → Publish space → Visual Editor
- Check the Insert tab → Custom Components → look for the registered component
- Drag it onto the canvas and verify inputs appear correctly
- If the component doesn't appear: check that
builder-registry.tsis imported incomponents/builder.tsx, the dynamic import path is correct, and the app is deployed
Workflow 3: Bulk Register Components
Scan a directory and register all unregistered components. Use parallel sub-agents to analyze multiple components simultaneously.
Step 1: Scan
Find .tsx/.jsx component files. Skip *.test.*, *.spec.*, *.stories.*, *.story.*, *.mock.*, *.d.ts, and barrel files (index.ts/index.tsx that only re-export). Check builder-registry.ts to identify which components are already registered.
Step 2: Analyze in parallel (sub-agents)
For each UNREGISTERED component, spawn a sub-agent to analyze it. All sub-agents run in parallel.
Each sub-agent receives:
- The component file path
- The type mapping rules from references/sdk-reference.md
- The examples from references/examples.md
Each sub-agent does:
- Read the component file
- Parse the exported props interface (follow imports if needed)
- Map each prop to a Builder input using the type mapping rules
- Generate the complete
Builder.registerComponent()code block - Return the registration code and a summary (component name, input count, any warnings)
Spawn all sub-agents in a single message so they run in parallel.
Step 3: Assemble registrations (serial)
After all sub-agents complete, collect their registration code blocks and:
- Review each registration for correctness (check for warnings from sub-agents)
- Add all registrations to
builder-registry.ts— append them sequentially. Addimport dynamic from "next/dynamic"at the top if not already present.
This step must be serial because all registrations write to the same file.
Step 4: Report results
Summarize: how many registered, skipped (already registered), and failed (with reasons from sub-agents).
If a sub-agent fails to analyze a component, report the failure but don't block other registrations.
Gotchas
These are the most common failure modes. Check here first when debugging.
-
listandobjectinputs requiredefaultValue. WithoutdefaultValue: [](list) ordefaultValue: {}(object), Builder errors when adding the component to a page. Non-obvious and causes "component won't load" debugging. -
enumrequirestype: "text"with anenumarray. There is notype: "enum"in Builder. Using it causes the input to silently not render. -
Two separate
builder.init()calls are correct. The catch-all route uses@builder.io/sdk(server-side), andbuilder-registry.tsuses@builder.io/react(client-side). These are different packages. Both need initialization. Do NOT "fix" this by removing one. -
API key must be in the deployment environment.
.env.localworks locally but is not deployed. Users must addNEXT_PUBLIC_BUILDER_API_KEYin their hosting platform's environment settings. This is the #1 "it doesn't work in production" cause. -
Side-effect import path must be correct.
import "@/builder-registry"incomponents/builder.tsxis what triggers component registrations. If this path is wrong, the build succeeds but no custom components appear in the editor. Verify this import first when debugging missing components. -
Dynamic import path is relative to
builder-registry.ts. If the registry is at the project root and the component is atcomponents/Hero/Hero.tsx, the import must be./components/Hero/Hero. Wrong paths cause the component to register but render blank. -
Double
BuilderDevToolswrapping. The f2p template has a bug:BuilderDevTools()(BuilderDevTools()(nextConfig)). Always check for existing wrapping before adding it. -
Next.js 15+
paramsis a Promise. Must be awaited. Next.js 14 uses params directly. Check the version inpackage.json. -
[...page]does not handle the root path/. If the user needs Builder to manage the homepage, they need to add Builder content fetching toapp/page.tsxseparately, or switch to[[...page]]and removeapp/page.tsx. -
CSP headers may block the visual editor. Builder's editor loads the deployed site in an iframe. If the app sets
X-Frame-Optionsor CSPframe-ancestors, it must allowhttps://*.builder.io.
When to Use What
| Need | Tool | Why |
|---|---|---|
| Register components for Publish visual editor | This skill | Automates TypeScript → Builder input mapping and scaffolding |
| Help Fusion's AI understand your design system | Component Indexing (npx @builder.io/dev-tools index-repo) |
Different purpose: improves Fusion code generation, not Publish registration |
| Project-wide coding conventions | AGENTS.md | Always loaded, good for conventions all AI tools should follow |
| Directory-scoped rules | .builderrules or .builder/rules/ | Proximity-based, Builder-specific |
| Register components manually | @builder.io/dev-tools UI | Visual registration without this skill |
Anti-Patterns
- Never use
type: "enum". Usetype: "text"with anenumarray. - Never skip
defaultValueon list/object inputs. Builder will error. - Never use
[[...page]]if the user has a homepage. It conflicts withapp/page.tsx. - Never write API keys to
.env(only.env.local). - Never wrap
BuilderDevTools()twice. - Never register event handlers, CSSProperties, or HTML attributes as Builder inputs.
- Never remove either
builder.init()call. Server and client inits serve different purposes.
Audit Checklist
After registering a component, verify:
-
builder-registry.tshas"use client"directive - Dynamic import path resolves to the correct component file
- All
listinputs havedefaultValue: [] - All
objectinputs havedefaultValue: {} - Enum props use
type: "text"withenumarray - No event handlers, CSSProperties, or HTML attributes in inputs
-
childrenprop is handled viacanHaveChildren: true, not as an input - Required props have
required: true - API key is set in both
.env.localand deployment environment
Reference Files
For complete SDK API reference and type mapping table, see references/sdk-reference.md.
For exact scaffolding file templates, see references/scaffolding-templates.md.
For end-to-end component registration examples, see references/examples.md.