Contents

Mount Your Own Components

Maya already has a BookingForm in her Next.js app. She wants that form on the homepage hero, and a marketer should be able to place it and set variant or seats without a RuleCMS enablement. Drop Custom, type booking-form, and register mounts on RuleCMSWidget. The composer always shows the dashed box — her site is where the form appears.

What you can do

She already embeds the Development homepage hero from Development Integration. The hero is Text and Image today — two of the ten built-in cards: Text, Image, Video, Icon, Button, Divider, Embed, List, Accordion, Custom. She also has a BookingForm in her Next.js app. That form talks to her API and uses her auth. She wants it on the hero.

A marketer should be able to place it and set variant or seats without a RuleCMS enablement. She should not have to rebuild the form as Text plus Button, and she should not have to ship a palette package for one control that already lives in the app.

mounts is that handshake. Her developer registers BookingForm on RuleCMSWidget under a short name. The marketer drops Custom and types that same name. The published page looks the name up and renders her form. The composer always shows the dashed box — her site is where it appears.

Two everyday jobs:

  • Put a host component on the hero. A booking form, a pricing calculator, a seat picker. The marketer places the slot and edits the knobs. You keep the code.
  • Leave a design-system card as a library. A React hero that needs a composer preview and its own attributes is Component Libraries — a published package and a RuleCMS enablement. That is a different door.

Custom is one of the ten built-ins. It is not a library. It is not Embed — YouTube, Maps, and Calendly stay on that card.

You wantUse
A component that already lives in her app, configured by a marketer, no RuleCMS enablementCustom + mounts (this page)
A click handler or other behaviour on that slot — onBooked, onClickCustom Props and Click Handlers (componentProps, keyed by the Custom column's Component id)
First-class palette cards, a composer preview, and your own attribute editorsComponent Libraries
YouTube, Vimeo, Google Maps, or CalendlyEmbed
Hide the slot on phonesColumn Hide — not a Custom setting

How it thinks

The name is a map key, not code. She types booking-form. The live page does mounts['booking-form'] and renders whatever component you registered. There is no import(name), and there is no URL in the widget JSON. A typo is a dashed box. Publishing does not invent a registration.

RuleCMS never receives mounts. The composer cannot preview her form and cannot offer a list of names. The canvas is always the placeholder. That is the product, not a gap.

Here is the case that surprises people. She publishes the hero. The live production page still shows the dashed box. The name is spelled right. Production is still on a build that does not pass mounts. You have to publish the widget and deploy the app that has the map. Register the same names on every RuleCMSWidget that should resolve them. A widget you did not pass the map to cannot fill the slot.

RuleCMSWidgetServer can take mounts. That is the opposite of componentProps, which cannot — a function does not cross the server boundary. If BookingForm needs hooks or click handlers, mark it 'use client' and register that module. The samples live on Props and names.

The HTML Custom Element Embed cannot do this. The script tag has no way to receive a React component. A WordPress page that only loads that embed will keep the dashed box.

You need @rulecms/widget-react 22.16.0 or later and @rulecms/source-components-react 20.19.0 or later, plus a name you will actually tell a human to type. Write the name down for them, with the props list. The composer has no autocomplete for this.

Put Maya's BookingForm on the hero

She does this after the hero is on her local site from Development Integration: a dev. token and the draft widget-… key from Integrate. The Default Widget is Production-only. She created this hero in Development.

  1. Write (or keep) BookingForm so it accepts the knobs a marketer should own. Treat that list as a public configuration surface. Keep secrets out of it. Event handlers do not come from the composer — pass those with componentProps at the Custom column's Component id.
type BookingFormProps = {
  variant?: 'full' | 'compact';
  seats?: number;
  showWaitlist?: boolean;
  theme?: { accent?: string };
  onBooked?: (id: string) => void;
};

export function BookingForm({
  variant = 'full',
  seats = 8,
  showWaitlist = false,
  theme,
  onBooked,
}: BookingFormProps) {
  // her existing form — talks to her API, uses her auth, her styles
  return (
    <form data-variant={variant} style={{ ['--accent' as string]: theme?.accent }}>
      {/* … */}
    </form>
  );
}
  1. Register it on every RuleCMSWidget that should be able to render it. The key is the name she will type.
'use client';

import { RuleCMSWidgetProvider, RuleCMSWidget } from '@rulecms/widget-react';
import * as sourceComponents from '@rulecms/source-components-react';
import { BookingForm } from './BookingForm';

const token = process.env.NEXT_PUBLIC_RULECMS_DEV_TOKEN; // must start with "dev."
const widgetKey = 'widget-…';

export function HomepageHero() {
  return (
    <RuleCMSWidgetProvider
      token={token}
      libraries={{ default: sourceComponents }}
    >
      <RuleCMSWidget
        publishedKey={widgetKey}
        mounts={{ 'booking-form': BookingForm }}
      />
    </RuleCMSWidgetProvider>
  );
}

Register every name the marketer might type, including a compact variant on phones:

mounts={{
  'booking-form': BookingForm,
  'booking-form-compact': CompactBookingForm,
}}
  1. Give her a card she can follow. She has no schema and no autocomplete. The exact name, the props it reads, and the type of each prop — text, number, yes/no, JSON.
Component Name: booking-form
(optional on phones: booking-form-compact)

Props
  variant        Text      "full" or "compact"
  seats          Number    how many seats to show
  showWaitlist   Yes / no  true or false, exactly
  theme          JSON      {"accent":"#0af"}

Do not put secrets in Props. They ship with the page.
Hide the slot on phones with Hide on the column, not with a prop.
  1. She opens the homepage hero in the composer. On the left, stay on Components. Drop Custom. In the Modify drawer, type booking-form in Component Name. Fill Props from the card — or leave them empty if the defaults are fine.
  2. Publish the widget. Deploy the app that contains the registration. Refresh her site. The form appears inside the column box. The composer still shows the dashed box. That is a safe stop.

Point her at Custom Component for the editor side: what she types, what the box means.

Custom is a leaf. She cannot nest other cards inside the form. Prop values are in the published widget JSON — not secrets. Event names cannot be authored; use Custom Props and Click Handlers at that column's Component id.

What to read next

This page is the story and the first walkthrough. The prop types, the name grammar, and the surfaces that stay a dashed box live on the page below.

  • Props and namesText / Number / Yes/no / JSON, keys RuleCMS drops, the name grammar, unmatched names, what her component receives, server render, and why the HTML embed cannot.
  • Custom ComponentThe marketer's page: what they type, what the box means.
  • Custom Props and Click HandlersonBooked / onClick by the Custom column's Component id.
  • Component LibrariesA first-class palette card and a composer preview. Needs a RuleCMS enablement.
  • Development IntegrationEmbed the Development hero first: the dev. token and the draft widget-… key from Integrate.
  • HTML Custom Element EmbedCannot receive mounts. Use @rulecms/widget-react in a React host.
  • PublishingDraft versus the snapshot a token fetches. Promote copies the draft; it does not publish.

How this fits

This page does not replace an embed. Development Integration is how she puts the hero on the page. This page is how a component she already owns lands in a Custom slot on that hero.

It is also not a library. Component Libraries adds a real palette card after we enable a package. Custom stays a typed name and a dashed box. Use this door when the component should stay in the app. Use a library when editors need a preview and attributes of their own.

It is also not extra props by column id. Custom Props and Click Handlers addresses onBooked at the Custom column's Component id. A mounted form can still receive handlers that way. Do not mix the two jobs.

The Default Widget is in Production. She built this hero in Development. Promote copies the draft to the next environment; it does not publish. When she publishes, she switches the app to a Staging or Production token — no dev. prefix — and that published key. The name booking-form does not change. Do not mix a dev. token with a published key, or a published token with a draft widget-… key.