custom component, the SDK builds your view, hands it the resolved inputs, and routes the events your view emits back into the flow’s actions.
This is the supported path for native code inside a flow: a paywall, a date picker, a chart, a camera view, anything SwiftUI can draw.
How it works
The editor and the SDK meet at a contract keyed by component key + version:- In the editor you define a custom component: a stable Component Key, a version, typed inputs, and named outputs (events). See Custom components in the editor.
- A flow places an instance of that component and binds its inputs (to constants or variables) and its outputs (to actions).
- In your app you call
registerCustomComponent(key:version:definition:)with a factory that returns a SwiftUI view. - At render time the SDK looks up your factory by key and version, resolves the bound inputs, and calls your factory. Your view emits events through a context object, and the SDK runs the actions the editor wired to those events.
Register a component
Register after the SDK is configured and before you present any flow that uses the component.CustomComponentDefinition carries the input types, the output schemas, and the factory:
inputs: declared input keys and their types (.string,.number,.boolean,.list). Used for validation; the keys must match the input keys you defined in the editor.outputs: a map of event name toOutputSchema, declaring which events the view can emit and the shape of each event’s payload.factory: a@MainActorclosure that returns your view wrapped inAnyView. It receives the resolved inputs (CustomComponentProps) and an emit context (CustomComponentContext).
OutputSchema declares one event:
Read inputs
The factory’s first argument,CustomComponentProps, gives typed, read-only access to the resolved inputs. Each accessor has an optional form and a form with a default:
default: form so your view always has a value. If a key is missing or has the wrong type, the SDK logs a warning in debug builds and returns your default. The keys you read must match the input keys defined in the editor.
Emit events
The factory’s second argument,CustomComponentContext, is how your view tells the flow that something happened:
eventName must match an output you declared in the editor (and in the definition’s outputs). When you emit, the SDK runs whatever actions the editor wired to that event in the component’s Interactions. The payload is available to those actions as {{payload.<key>}} (for example to write a value into a variable). See Interactions and actions.
If you emit an event name that is not declared, the SDK still forwards it but logs a warning. If a payload does not match the declared schema, it is still forwarded with a warning. Treat both warnings as a sign your editor definition and SDK code have drifted.
Container info
The context also exposes the space the component was given:Versioning
Registration is keyed by key + version so a component can evolve without breaking older flows. Theversion defaults to 1. When a flow references a component, the SDK looks up key at that exact version. For version 1, the SDK also registers the component under the bare key for backward compatibility, so older flows that omit a version still resolve.
The version you register must match CustomComponentRef.version in the flow. If you ship a v2 of a component, register both versions while flows that pin v1 are still live.
Example
Adate_picker component with one input (label) and one output (date_selected) carrying a date string and a timestamp number.
date_selected event can read the payload, for example writing {{payload.date}} into a variable with a Set Variable action.
To remove a registration (for example in tests):
The contract
The editor definition and the SDK registration must agree on four things. A mismatch on any of them breaks rendering or events:Custom components (this page) are different from custom actions. A custom component emits editor-defined events, which is fully supported. A
custom action (an action of kind custom defined in the editor) has no public native registration API on the SDK today. If you need native behavior, model it as a custom component that emits an event, and wire the action to that event. See Error handling for the current state of custom actions.Common mistakes
- Key or version mismatch. The flow asks for
date_pickerv2 but you registered v1 (or a different key). The SDK cannot find your factory and shows a placeholder instead. - Registering after presenting. Register the component before you call
presentPlacementorcreateSession. A flow that renders before the factory is registered shows the placeholder. - Reading an input key the editor did not define.
props.string("titel")(typo) returns your default and logs a warning. Match the editor’s input keys exactly. - Emitting an event the editor did not define. The event is forwarded but nothing is wired to it, so nothing happens. Declare the output in the editor and in your
outputs. - Trusting
containerSize. It is a fixed placeholder in the current build (see the warning above). UseGeometryReaderif you need the real size.