Define a custom component
Open the Custom tab
In the left mini-sidebar, click Custom (the puzzle icon). This opens the App Components panel, which lists every custom component defined for the app.
Create a new definition
Click New (or Create Component on the empty state). The New Custom Component dialog opens.
Set the basics
- Display Name, for example “Weight Picker”.
- Component Key is auto-generated from the name (lowercased, only
a-z,0-9, and_). This is the identifier used in code, and it cannot be changed after creation. - Description (optional).
Define Inputs
In the Inputs section, add each value your component accepts. Every input has a Key (lowercased,
a-z0-9_), a Type (Text, Number, Boolean, Color, or Image URL), an optional Description, and a Required toggle.Define Outputs
In the Outputs section, add each event your component can emit. Every output has an Output Name (label), a Key, an optional Description, and optional Payload Fields. A payload field has a key and a type (Text, Number, Boolean, or List). An output with no payload fires without data.
CustomComponentInputType (string, number, boolean, color, image). The dialog calls them Text, Number, Boolean, Color, and Image URL.
Status: draft, active, deprecated
A custom component moves through three statuses, managed from the App Components panel:- Draft: the default for a new component. Drafts can be edited and deleted freely.
- Active: promote a draft with Activate. Once active, “schema changes will create new versions and the component cannot be deleted”. The Delete action becomes “Cannot delete (not draft)”.
- Deprecated: retire an active component with Deprecate. Deprecated components are hidden from the editor but remain valid in flows that already use them.
CustomComponentRef is { key, version }). Editing the inputs or outputs of an active component bumps the version, and the save button reads Save & Create New Version.
Row actions in the App Components panel: Edit, Duplicate, Activate (drafts), Deprecate (active), and Delete (drafts only).
Use it in a flow
Once defined, a custom component appears at the bottom of the Insert > Components palette under a Custom group, alongside the built-in primitives. Drag it onto a screen (or click to add) like any other component. A custom component is a leaf node: it cannot have children. On the canvas it renders as a dashed placeholder shell with a puzzle icon, the component’s name, the subtitle “(Custom Component)”, and its input and output counts. If a required input is unbound, a “missing” badge appears on the placeholder.Bind inputs and map outputs
Select a placed custom component to open its properties panel. It has only four sections (there are no styling sections, because styling is the SDK’s job):- Overview: the component name, a visibility toggle, duplicate, and delete.
- Inputs: bind each defined input to either a static value or a variable. Inputs are value- or variable-bound only; there are no expressions or
{{ }}interpolation here. - Interactions: map each output to one or more actions (for example
goNext,setVariable,closeFlow,navigate,openUrl,haptic,trackEvent). If an output carries a payload, you can bind a payload field to a variable; that binding is stored as asetVariableaction using{{payload.fieldKey}}. - Size: Width (content, fill, or a fixed number) and Height (content or a fixed number), with optional min and max constraints.
The editor and SDK contract
This is the contract both sides must share:| Editor defines | SDK must match |
|---|---|
| Component key (immutable) | The key you register natively |
| Schema version | The version your registered component implements |
| Input keys and types | The inputs your native view reads |
| Output keys and payload | The events your native view emits |
CustomComponentRef of { key, version }. At runtime the SDK looks up the native handler you registered under that key and hands it the bound input values; when your native view emits an output, the SDK runs the actions you mapped to it. Document the native registration on the iOS SDK custom components page.
Custom screens are different. This page covers custom components (a native view embedded inside a flow screen). The iOS SDK also supports custom screens (a whole native screen registered with
registerCustomScreen). The editor has no separate “custom screen” surface; only custom components are defined here. See iOS SDK: Custom screens for that feature.Common mistakes
- Key or version mismatch. The native handler must register the same key and a matching version. A mismatch means the SDK cannot find your component.
- Input or output key typos. The SDK only delivers values for keys it recognizes. A typo means an input never arrives or an output never fires. Required inputs that are unbound show a “missing” badge in the editor.
- Expecting the editor to show the real UI. The editor always renders a placeholder shell. The real native view appears only in your app through the SDK.
- Trying to style it in the editor. There are no background, border, padding, or typography controls. Styling is handled inside your native implementation.
- Trying to nest components inside it. Custom components are black boxes with no children.
- Deleting an active component. Not allowed. Deprecate it instead, which hides it from the editor but keeps existing flows working.