Skip to main content
A custom component lets a flow use a piece of UI that the flow engine does not provide: a native date picker, a chart, a map, a camera preview, anything you build in your app. You define its contract in the editor (a name, the inputs it accepts, and the outputs it can emit), and the iOS SDK renders the real native view at runtime. In the editor itself, a custom component shows as a placeholder shell, never the real UI. Custom components are app-scoped: a component you define is available across every flow in that app.
The editor side and the native side must agree. The flow references your component by key and version; the SDK matches it to the handler you register natively. If the key, version, input keys, or output keys do not line up, the component will not render or receive data. This is the single most common source of bugs. See the native side at iOS SDK: Custom components.

Define a custom component

1

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.
2

Create a new definition

Click New (or Create Component on the empty state). The New Custom Component dialog opens.
3

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).
4

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.
5

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.
6

Create the component

Click Create Component. It is saved as a draft.
The input types map to the schema type 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.
Because of this, the reference to a component carries a version (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 a setVariable action 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 definesSDK must match
Component key (immutable)The key you register natively
Schema versionThe version your registered component implements
Input keys and typesThe inputs your native view reads
Output keys and payloadThe events your native view emits
The flow stores a 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.