Skip to main content
TemplateProvider carries auth and template state. TemplateEditor renders the editing surface.
The provider creates the Template when templateId does not resolve to one, so a first render shows an empty editor rather than an error. Issue the token from your backend as describes.

Restrict the channels

routing.channels decides which channel tabs appear. Unlisted channels are hidden from the customer.
Hiding a channel does not restrict what the stored Template may contain. A channel your customer cannot see can still be present in content written through the API. There is also a bare channels prop. It is deprecated in favour of routing.channels and scheduled for removal, so pass routing. When both are set, routing.channels wins.

Switch templates

Change templateId and the provider loads the new Template. The prop sits in an effect dependency, so no remount or key is needed.
Auto-save runs against the Template that was mounted, so let a pending save settle before switching. With the default debounce that is 500ms after the last keystroke.

Publish from your own button

hidePublish removes the built-in control. useTemplateActions exposes the same action, so your own button does the same work.
Call the hook inside the provider. A component rendered outside it has no template state to act on.

Control when writes happen

Auto-save is on, debounced 500ms. Turn it off to write on your own schedule.
readOnly makes the editor read-only across every channel. It disables editing, toolbar actions, block insertion, drag and drop, and auto-save, which makes it the prop to reach for when a customer may view a Template but not change it.

Offer variables

variables populates the autocomplete a customer sees after typing {{.
Leaving variables unset disables every variable feature. The toolbar button is hidden and typing {{ creates no variable chip. Nothing reports this, so an editor that looks plain is usually an editor with no variables prop. Two props change how strict that autocomplete is:
  • disableVariablesAutocomplete drops the dropdown and lets a customer type any name.
  • variableValidation restricts which names are allowed and says what happens when one fails.
sampleData is separate. It validates the data path on a loop, and warns when the path matches no key or does not resolve to an array.

Theme the editor

theme takes a theme object or a class name. colorScheme selects light or dark.
Import @trycourier/react-designer/styles.css once in your app. Without it the editor renders unstyled, which reads as a broken layout rather than a missing import.

Render inside a modal

The editor is a regular element, so your own dialog can hold it. Two things about the surrounding app matter. Give the container a height. The editor fills its parent, and a dialog that sizes to its content gives it nothing to fill. Set renderToaster to false when your app already renders a Sonner <Toaster />. The provider renders its own by default, and two on one page means every save message appears twice.
Keep the provider mounted while the dialog is open. Unmounting it discards template state, so the next open refetches.

Provider props

TemplateProvider holds auth, identity, and the options that outlive a single editor.

Editor props

Verify

Render the page and confirm the editor appears. Type into a block, wait for the save message, then open and confirm the Tenant’s Template carries the change.

Troubleshooting

Limits & behavior

  • Issue the token server-side. A browser holding your API key exposes the whole workspace.
  • hidePublish hides a button, nothing more. Publishing still works through the hook and through .
  • routing is a UI restriction. It does not constrain what a stored Template may contain.
  • readOnly stops auto-save. A read-only editor writes nothing, by design.

FAQ

Change the templateId prop on TemplateProvider. The prop sits in an effect dependency, so the provider loads the new Template without a remount.
The variables prop is unset. Leaving it undefined disables every variable feature, including the {{ autocomplete, and nothing reports it.
A dialog can hold the editor. Give the container an explicit height, and set renderToaster to false if your app already renders a Sonner <Toaster />.
Pass routing.channels. The bare channels prop is deprecated and scheduled for removal, and routing.channels takes priority when both are set.