> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flowx.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# React Native SDK

> The React Native renderer SDK renders processes configured in the FlowX Designer inside a React Native app, using an imperative API.

## React Native project requirements

> [!NOTE]
>
> The FlowX packages declare `react-native` **≥ 0.83.0** and `react` **≥ 19.2.0** as peer dependencies. The validated target is **React Native 0.86.3 with Expo SDK 57**, which is what FlowX builds and tests against. Your `react` must also satisfy the peer dependency of your own React Native version (`^19.2.3` for `react-native@0.86.3`). React Native 0.86.3 requires Node `^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0`.

| Platform | Minimum | Notes |
| - | - | - |
| iOS deployment target | **15.1** bare React Native, **16.4** with Expo | 15.1 is the minimum of the FlowX native module. Expo SDK 57 requires 16.4, as Expo SDK 56 did (Expo 55 used 15.1). |
| Android `minSdkVersion` | **24** (Android 7.0) | Set in `android/build.gradle`. The FlowX native module falls back to 24 when the host app does not define it. |
| Android `compileSdkVersion` / `targetSdkVersion` | RN 0.86 + Expo SDK 57 defaults | Don't override unless required. Set by `react-native-gradle-plugin` / `expo-root-project` after `expo prebuild`. The FlowX native module falls back to 35 when the host app does not define them. |

> [!TIP]
>
> To install the npm libraries provided by FlowX.AI you will need access to the private FlowX.AI Nexus registry. Consult your project DevOps.

> [!WARNING]
>
> `react` must satisfy React Native's published peer dependency. For `react-native@0.86.3` this is `^19.2.3`; newer RN 0.86.x patches may shift it; check with `npm view react-native@<your-version> peerDependencies`. Installing React Native via the official template (Bare CLI) or Expo SDK 57 picks a compatible `react` automatically; don't override it with a manual pin unless you have a reason.

## Installation

> [tab] Bare React Native CLI
>
> For new projects, bootstrap with the official React Native template so `react` and `react-native` ship as a matched pair that satisfies the peer dependency in the Warning above:
>
> ```bash theme={"dark"}
> npx @react-native-community/cli@latest init MyApp
> cd MyApp
> ```
>
> For existing projects, ensure `react` already satisfies your installed `react-native`'s declared peer (`npm view react-native@<your-version> peerDependencies`) before installing the FlowX SDK.
>
> Then install the FlowX SDK and peers:
>
> ```bash theme={"dark"}
> npm install \
>   @flowx/react-native-sdk@<version> \
>   @flowx/react-native-ui-toolkit@<version> \
>   @flowx/react-native-theme@<version> \
>   @flowx/core-sdk@<version> \
>   @flowx/core-theme@<version> \
>   react-dom \
>   @react-native-async-storage/async-storage \
>   react-native-screens \
>   react-native-safe-area-context \
>   react-native-svg \
>   react-native-enriched-markdown \
>   react-native-pdf-renderer \
>   react-native-blob-util \
>   react-native-keyboard-controller \
>   react-native-reanimated \
>   react-native-worklets \
>   react-native-image-picker \
>   @react-native-documents/picker \
>   @react-native-community/slider \
>   @react-native-segmented-control/segmented-control
> ```
>
> > [!WARNING]
> >
> > Replace `<version>` with the correct version corresponding to your platform version.
> >
> > To find the right version, navigate to: **Release Notes → Choose your platform version → Deployment guidelines → Component versions**.
>
> Then install iOS pods:
>
> ```bash theme={"dark"}
> cd ios && pod install && cd ..
> ```

> [tab] Expo (SDK 57)
>
> > [!NOTE]
> >
> > Expo Go is **not** supported. `react-native-keyboard-controller`, `react-native-reanimated@4`, and `react-native-worklets` require a custom native build. Use `expo-dev-client` with EAS builds or `expo prebuild`.
>
> This SDK release targets **Expo SDK 57** (stable).
>
> | Expo SDK | Status | React Native | React | Notes |
> | - | - | - | - | - |
> | **57** | **Recommended** | 0.86.3 | 19.2.3 (Expo SDK 57 pin; satisfies RN `^19.2.3`) | Validated target. New architecture is default. iOS 16.4 minimum. |
> | 55-56 | Installs, not validated | 0.83.x-0.85.x | 19.2.x | The peer floors reach down to the Expo 55 native-module set, so the packages install, but FlowX validates on 57 only. Move to 57 when you can. |
> | ≤54 | Not supported | - | - | Below the declared peer floors (React Native 0.83, React 19.2). |
>
> Align the project to Expo SDK 57:
>
> ```bash theme={"dark"}
> npx expo install expo --fix
> ```
>
> `--fix` aligns `react`, `react-native`, `react-native-reanimated`, `react-native-worklets`, `react-native-safe-area-context`, `react-native-screens`, and other Expo-tracked peers to the versions validated for the installed Expo SDK. Re-run after every Expo bump.
>
> Install the FlowX SDK and peers with `npx expo install` (it picks Expo-validated versions and warns on incompatibilities; use it instead of `npm install`):
>
> ```bash theme={"dark"}
> npx expo install \
>   @flowx/react-native-sdk@<version> \
>   @flowx/react-native-ui-toolkit@<version> \
>   @flowx/react-native-theme@<version> \
>   @flowx/core-sdk@<version> \
>   @flowx/core-theme@<version> \
>   react-dom \
>   @react-native-async-storage/async-storage \
>   react-native-screens \
>   react-native-safe-area-context \
>   react-native-svg \
>   react-native-enriched-markdown \
>   react-native-pdf-renderer \
>   react-native-blob-util \
>   react-native-keyboard-controller \
>   react-native-reanimated \
>   react-native-worklets \
>   react-native-image-picker \
>   @react-native-documents/picker \
>   @react-native-community/slider \
>   @react-native-segmented-control/segmented-control \
>   expo-dev-client
> ```
>
> > [!WARNING]
> >
> > Replace `<version>` with the correct version corresponding to your platform version.
> >
> > To find the right version, navigate to: **Release Notes → Choose your platform version → Deployment guidelines → Component versions**.
>
> `react`, `react-native`, `expo`, and Expo built-ins are already in a fresh Expo project - don't reinstall them. They are pinned by Expo SDK 57's compatibility matrix; let `npx expo install --fix` resolve drift instead of pinning manually.
>
> ### Expo SDK 57 reference versions
>
> These are the versions Expo SDK 57 validates. Use `npx expo install <pkg>` to install at the right pin; do not pin manually unless you must. Packages Expo does not curate show the range the FlowX packages accept.
>
> | Package | Expo SDK 57 expected |
> | - | - |
> | `expo` | `~57.0.26` |
> | `expo-dev-client` | `~57.0.19` |
> | `expo-splash-screen` | `~57.0.9` |
> | `expo-status-bar` | `~57.0.1` |
> | `babel-preset-expo` | `~57.0.13` |
> | `@expo/cli` | `57.0.27` (ships as a dependency of `expo`) |
> | `react` | `19.2.3` (Expo SDK 57 pin; satisfies `react-native@0.86.3` peer `^19.2.3`) |
> | `react-dom` | `19.2.3` |
> | `react-native` | `0.86.3` |
> | `react-native-reanimated` | `4.5.1` |
> | `react-native-worklets` | `0.10.1` |
> | `react-native-safe-area-context` | `~5.7.0` |
> | `react-native-screens` | `~4.26.0` |
> | `react-native-svg` | `15.15.4` |
> | `react-native-enriched-markdown` | `^1.0.2` |
> | `react-native-pdf-renderer` | `^2.3.0` |
> | `react-native-blob-util` | `^0.25.1` |
> | `react-native-keyboard-controller` | `1.21.9` |
> | `react-native-image-picker` | `^8.2.1` |
> | `@react-native-async-storage/async-storage` | `2.2.0` |
> | `@react-native-community/slider` | `5.2.0` |
> | `@react-native-segmented-control/segmented-control` | `2.5.7` |
> | `@react-native-documents/picker` | `^12.0.2` |
>
> Generate native projects if your Expo project does not yet have `ios/` and `android/`:
>
> ```bash theme={"dark"}
> npx expo prebuild --clean
> ```
>
> Then install iOS pods:
>
> ```bash theme={"dark"}
> cd ios && pod install && cd ..
> ```
>
> `pod install` is also run automatically by `npx expo run:ios` on first launch.
>
> ### `app.json` plugins
>
> Most listed native modules autolink without an Expo config plugin. The one to register:
>
> ```json theme={"dark"}
> {
>   "expo": {
>     "plugins": [
>       "expo-dev-client"
>     ]
>   }
> }
> ```
>
> `react-native-keyboard-controller`, `react-native-reanimated`, and `react-native-worklets` need no config plugin - the Babel plugin handles reanimated and worklets.
>
> Also register the `@flowx/react-native-ui-toolkit/app.plugin.js` config plugin. It adds `fontScale` to the main activity's `android:configChanges`, so a system text-size change re-renders the app in place instead of recreating the activity and losing in-progress forms. It also declares the microphone permissions used by chat voice input (see [Voice input](#voice-input)).
>
> ### Local build (no EAS)
>
> For development on your own machine without EAS:
>
> ```bash theme={"dark"}
> npx expo run:ios       # builds + runs on iOS simulator or device
> npx expo run:android   # builds + runs on Android emulator or device
> ```
>
> First invocation runs `expo prebuild` if `ios/` or `android/` are absent, then builds via Xcode / Gradle. Requires local Xcode (iOS) and Android SDK / JDK (Android).
>
> Useful flags:
>
> ```bash theme={"dark"}
> npx expo run:ios --device                 # pick a connected physical device
> npx expo run:ios --configuration Release  # release build
> npx expo run:android --variant release    # release build
> npx expo run:android --device <id>        # pick device
> ```
>
> After the first install, start Metro separately with the dev client:
>
> ```bash theme={"dark"}
> npx expo start --dev-client
> ```
>
> The installed app on the simulator or device connects to Metro automatically.
>
> ### EAS Build
>
> Add a development profile to `eas.json` with `developmentClient: true` so the artifact ships with the dev client:
>
> ```json theme={"dark"}
> {
>   "build": {
>     "development": {
>       "developmentClient": true,
>       "distribution": "internal"
>     }
>   }
> }
> ```
>
> Then build:
>
> ```bash theme={"dark"}
> eas build --profile development --platform ios
> eas build --profile development --platform android
> ```
>
> Install the resulting artifact on device. Run `npx expo start --dev-client` to connect Metro.
>
> ### Metro config (monorepo)
>
> The default Expo Metro config works in standard projects. For monorepos with symlinked workspaces, enable symlink resolution:
>
> ```js theme={"dark"}
> // metro.config.js
> const { getDefaultConfig } = require('expo/metro-config');
> const config = getDefaultConfig(__dirname);
> config.resolver.unstable_enableSymlinks = true;
> module.exports = config;
> ```

> [!WARNING]
>
> Some peer dependencies require additional setup beyond `npm install`: Babel plugins, Expo config plugins, native project edits (Podfile, `styles.xml`, `AndroidManifest.xml`), or app-root providers. Check each library's install guide and apply its setup steps. Examples: `react-native-worklets` needs a Babel plugin (see below), `@flowx/react-native-ui-toolkit` ships an Expo config plugin for font scaling and voice input permissions, and `react-native-keyboard-controller` / `react-native-screens` / `react-native-safe-area-context` rely on autolinking plus the providers shown later in this page.

### Babel plugin

`react-native-worklets/plugin` **must be the last plugin** in your Babel config.

> [tab] Expo
>
> No manual config needed. `babel-preset-expo` (SDK 54+) auto-adds the worklets plugin when `react-native-worklets` is in `node_modules`.
>
> ```js theme={"dark"}
> // babel.config.js
> module.exports = function (api) {
>   api.cache(true);
>   return {
>     presets: ['babel-preset-expo'],
>   };
> };
> ```

> [tab] Bare RN CLI
>
> ```js theme={"dark"}
> // babel.config.js
> module.exports = {
>   presets: ['module:@react-native/babel-preset'],
>   plugins: [
>     'react-native-worklets/plugin',
>   ],
> };
> ```

## App-root providers

The renderer expects safe-area and keyboard providers above it in the tree:

```tsx theme={"dark"}
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { KeyboardProvider } from 'react-native-keyboard-controller'

export default function App() {
  return (
    <SafeAreaProvider>
      <KeyboardProvider>
        {/* FlowX process view goes here */}
      </KeyboardProvider>
    </SafeAreaProvider>
  )
}
```

> [!NOTE]
>
> The SDK mounts its own `NavigationContainer` internally, wrapped in `NavigationIndependentTree`. A host `NavigationContainer` is **not** required. If your app already uses `@react-navigation/native`, keep its `NavigationContainer` at the root. The SDK's internal navigator stays isolated from it.

## Authorization

> [!NOTE]
>
> The client app implements the authorization flow (using the **OpenID Connect** standard). The SDK expects a bearer token to be set via `FlowX.setAccessToken(token)` before starting a process.

```ts theme={"dark"}
import { FlowX } from '@flowx/react-native-sdk'

FlowX.setAccessToken(accessToken)
```

Call `setAccessToken` again whenever the token is refreshed.

## Configuring the SDK

`FlowX.configure(cfg)` sets global SDK config. Call it once at app bootstrap, before starting any process.

```ts theme={"dark"}
import { FlowX } from '@flowx/react-native-sdk'

FlowX.configure({
  baseUrl: 'https://yourDomain.dev',
  enginePath: 'onboarding',
  organizationId: '10000001-0001-0001-8001-100000000001',
  assetsBaseUrl: 'https://yourDomain.dev/static',
  isDraft: false,
})

FlowX.changeLocaleSettings('en-US', 'en')
```

`FlowX.changeLocaleSettings(locale?, language?)` sets the language and locale for SDK sessions. It takes effect on the next `startProcess()` / `continueProcess()`; the live session keeps the values it started with. The values are kept in memory only, so persist them in your app and call `changeLocaleSettings` again after an app restart.

### `FlxConfig` parameters

| Name | Description | Type | Mandatory | Default |
| - | - | - | - | - |
| `baseUrl` | Base API URL | `string` | true | - |
| `enginePath` | Process subpath. The leading slash is optional: `onboarding` and `/onboarding` build the same URL. | `string` | true | - |
| `organizationId` | FlowX-issued organization UUID. On self-hosted deployments it matches the `ORGANIZATION_ID` configured during the backend upgrade — see [License and organization configuration](https://docs.flowx.ai/5.9/migrating-from-5.1-lts/organization-deployment.md). | `string` | true | - |
| `language` | Deprecated: use `FlowX.changeLocaleSettings(locale?, language?)`. Language used to localize enumerations | `string` | false | - |
| `locale` | Deprecated: use `FlowX.changeLocaleSettings(locale?, language?)`. Locale used to format dates, currency, numbers | `string` | false | - |
| `assetsBaseUrl` | Base path for static resources (icons, media). The SDK logs a warning if not set. | `string` | false | - |
| `themeId` | Theme identifier (reserved, not consumed by the RN renderer yet) | `string` | false | - |
| `isDraft` | Start the process in draft state | `boolean` | false | `false` |
| `components` | Self-managed custom components to register, keyed by component identifier. See [Custom components](#custom-components). | `Record<string, ComponentType>` | false | - |
| `validators` | Host-supplied named field validators, keyed by the validator name used in the process. See [Custom validators](#custom-validators). | `Record<string, ValidatorFn>` | false | - |
| `cacheDocuments` | Cache downloaded document files on disk so re-opening the same document skips the download. See [Document caching](#document-caching). | `boolean` | false | `true` |
| `updateStateEnabled` | Deprecated: use `FlowX.setUpdateStateEnabled(enabled)`. Re-sync the process state when the app returns from background to foreground. See [Foreground state sync](#foreground-state-sync). | `boolean` | false | `true` |
| `customLoaderProvider` | Custom loader components for different loading scenarios. See [Custom loader](#custom-loader). | `FlxCustomLoader` | false | - |
| `additionalHttpHeaders` | Extra HTTP headers sent on every network request the SDK makes. See [Custom headers](#custom-headers). | `Record<string, string>` | false | - |
| `requestInterceptors` | Axios request interceptors applied to the SDK's REST calls. See [Custom interceptors](#custom-interceptors). | `HttpRequestInterceptor[]` | false | - |
| `responseInterceptors` | Axios response interceptors applied to the SDK's REST calls. See [Custom interceptors](#custom-interceptors). | `HttpResponseInterceptor[]` | false | - |

## Starting a process

`FlowX.startProcess(opts)` starts a new process instance and returns a `FlxProcessHandle`. The handle exposes a `ProcessView` component that you mount inside a screen.

```tsx theme={"dark"}
import { FlowX, type FlxProcessHandle } from '@flowx/react-native-sdk'
import { useEffect, useState } from 'react'

export function OnboardingScreen() {
  const [handle, setHandle] = useState<FlxProcessHandle | null>(null)

  useEffect(() => {
    let active = true
    let pending: FlxProcessHandle | null = null

    FlowX.startProcess({
      workspaceId: '8f52744-8403-4e8d-...',
      projectId: '111111-222222-333333-44444',
      processName: 'client_identification',
      params: { firstName: 'John', lastName: 'Smith' },
    }).then((h) => {
      if (!active) {
        h.dispose()
        return
      }
      pending = h
      setHandle(h)
    })

    return () => {
      active = false
      pending?.dispose()
    }
  }, [])

  if (!handle) return null
  const { ProcessView } = handle
  return <ProcessView />
}
```

### `StartProcessOptions`

| Name | Description | Type | Mandatory | Example |
| - | - | - | - | - |
| `workspaceId` | Workspace identifier | `string` | true | `'8f52744-8403-4e8d-...'` |
| `projectId` | FlowX Project UUID. Get it from **FlowX Dashboard → Projects → copy UUID from the project actions popover**. | `string` | true | `'111111-222222-...'` |
| `appVersionId` | Pin to a specific app version | `string` | false | - |
| `processName` | Identifies the process to start | `string` | true | `'client_identification'` |
| `params` | Data passed when starting the process | `Record<string, unknown>` | false | `{ firstName: 'John' }` |
| `resourceId` | Resource identifier | `string` | false | - |
| `buildId` | Pinned build identifier | `string` | false | - |
| `isModal` | Render the process as a modal screen | `boolean` | false | `false` |
| `onDismissRequested` | Called when the user taps the close button. Receives the root process definition name. Notification only: the session stays live, and you close it by unmounting `ProcessView`. When omitted, no close button is shown. | `(processName?: string) => void` | false | - |
| `onProcessEnded` | Called when the process ends | `() => void` | false | - |
| `onProcessStarted` | Called when the main process instance is started. Receives the new `processInstanceUuid`. | `(processInstanceUuid: string) => void` | false | - |

### Getting the process identifier

Open the FlowX Designer, navigate to the process, and copy the process name from the breadcrumbs. Use this value as `processName`.

## The process handle

`startProcess` resolves to a `FlxProcessHandle`:

```typescript theme={"dark"}
interface FlxProcessHandle {
  ProcessView: ComponentType
  dispose(): void
  onProcessEnded(cb: () => void): () => void
}
```

| Member | Description |
| - | - |
| `ProcessView` | React component that renders the process screens. Mount it inside a screen in your navigation tree. |
| `dispose()` | Ends the session. Optional: unmounting `ProcessView` disposes the session. Call it for a handle that was never mounted. |
| `onProcessEnded(cb)` | Registers a callback for when the process ends. Returns an unsubscribe function. |

> [!TIP]
>
> To capture the running instance UUID, pass an `onProcessStarted` callback to `startProcess`; it receives the new `processInstanceUuid`. The handle itself does not expose it.

> [!NOTE]
>
> Unmounting `ProcessView` ends the session, so you don't need to call `dispose()` when the screen unmounts. Call `handle.dispose()` for a handle you never mounted, for example when the screen unmounts before `startProcess` resolves.

## Resuming a process

`FlowX.continueProcess(opts)` resumes an existing process instance instead of starting a new one. Pass the `processInstanceUuid` of the instance you want to reattach to. Like `startProcess`, it resolves to a `FlxProcessHandle` you mount through its `ProcessView`.

```tsx theme={"dark"}
import { FlowX, type FlxProcessHandle } from '@flowx/react-native-sdk'
import { useEffect, useState } from 'react'

export function ResumeScreen({ processInstanceUuid }: { processInstanceUuid: string }) {
  const [handle, setHandle] = useState<FlxProcessHandle | null>(null)

  useEffect(() => {
    let active = true
    let pending: FlxProcessHandle | null = null

    FlowX.continueProcess({
      processInstanceUuid,
    }).then((h) => {
      if (!active) {
        h.dispose()
        return
      }
      pending = h
      setHandle(h)
    })

    return () => {
      active = false
      pending?.dispose()
    }
  }, [processInstanceUuid])

  if (!handle) return null
  const { ProcessView } = handle
  return <ProcessView />
}
```

### `ContinueProcessOptions`

| Name | Description | Type | Mandatory | Example |
| - | - | - | - | - |
| `processInstanceUuid` | UUID of the process instance to resume | `string` | true | `'8f52744-8403-4e8d-...'` |
| `isModal` | Render the process as a modal screen | `boolean` | false | `false` |
| `onDismissRequested` | Called when the user taps the close button. Receives the root process definition name. Notification only: the session stays live, and you close it by unmounting `ProcessView`. When omitted, no close button is shown. | `(processName?: string) => void` | false | - |
| `onProcessEnded` | Called when the process ends | `() => void` | false | - |

> [!NOTE]
>
> `continueProcess` reattaches to an already-started instance, so it takes no `processName`, `params`, or `onProcessStarted`. Those apply only when starting a new process. Configure the SDK with `FlowX.configure(...)` and set the access token before calling it, exactly as for `startProcess`.

## Starting a UI Flow

UI Flows are lightweight, screen-driven flows defined in the FlowX Designer. They run client-side on top of a data model, without requiring a BPMN process instance on the engine.

> [!WARNING]
>
> Prior starting a UI Flow, make sure the [authorization](#authorization) and the [SDK configuration](#configuring-the-sdk) were correctly set up.

`FlowX.startUiFlow(opts)` starts a UI Flow and returns a `FlxUiFlowHandle`. The handle exposes a `UiFlowView` component that you mount inside a screen.

```tsx theme={"dark"}
import { FlowX, type FlxUiFlowHandle } from '@flowx/react-native-sdk'
import { useEffect, useState } from 'react'

export function CustomerPortalScreen() {
  const [handle, setHandle] = useState<FlxUiFlowHandle | null>(null)

  useEffect(() => {
    let active = true
    let pending: FlxUiFlowHandle | null = null

    FlowX.startUiFlow({
      workspaceId: '8f52744-8403-4e8d-...',
      projectId: '111111-222222-333333-44444',
      uiFlowName: 'customer_portal',
      params: { firstName: 'John' },
    }).then((h) => {
      if (!active) {
        h.dispose()
        return
      }
      pending = h
      setHandle(h)
    })

    return () => {
      active = false
      pending?.dispose()
    }
  }, [])

  if (!handle) return null
  const { UiFlowView } = handle
  return <UiFlowView />
}
```

### `StartUiFlowOptions`

| Name | Description | Type | Mandatory | Example |
| - | - | - | - | - |
| `workspaceId` | Workspace identifier | `string` | true | `'8f52744-8403-4e8d-...'` |
| `projectId` | FlowX Project UUID. Get it from **FlowX Dashboard → Projects → copy UUID from the project actions popover**. | `string` | true | `'111111-222222-...'` |
| `uiFlowName` | Identifies the UI Flow to start | `string` | true | `'customer_portal'` |
| `params` | Data passed when starting the UI Flow | `Record<string, unknown>` | false | `{ firstName: 'John' }` |
| `isModal` | Render the UI Flow as a modal screen. When set together with `onDismissRequested`, a close button is displayed on each screen. | `boolean` | false | `false` |
| `onDismissRequested` | Called when the user taps the close button. Receives the `uiFlowName` passed in. | `(uiFlowName?: string) => void` | false | - |

### Getting the UI Flow name

Open the FlowX Designer and navigate to **UI Flows**. The flow name displayed in the list is the value to pass as `uiFlowName`.

## The UI Flow handle

`startUiFlow` resolves to a `FlxUiFlowHandle`:

```typescript theme={"dark"}
interface FlxUiFlowHandle {
  UiFlowView: ComponentType
  dispose(): void
}
```

| Member | Description |
| - | - |
| `UiFlowView` | React component that renders the UI Flow screens. Mount it inside a screen in your navigation tree. |
| `dispose()` | Ends the session. Optional: unmounting `UiFlowView` disposes the session. |

> [!NOTE]
>
> Unmounting `UiFlowView` ends the session, so you don't need to call `dispose()` when the screen unmounts. Call `handle.dispose()` for a handle you never mounted, for example when the screen unmounts before `startUiFlow` resolves.

## Custom components

Register host-authored **custom components** through the `components` field of `FlowX.configure(...)`. Each key is the component identifier defined in the FlowX process; the value is the React Native component that renders it. The registration is read when the process view mounts, so configure the SDK before starting or continuing a process.

```ts theme={"dark"}
import { FlowX } from '@flowx/react-native-sdk'
import { customComponents } from './customComponents'

FlowX.configure({
  baseUrl: 'https://yourDomain.dev',
  enginePath: 'onboarding',
  organizationId: '10000001-0001-0001-8001-100000000001',
  components: customComponents,
})
```

> [!WARNING]
>
> The keys in the `components` object **MUST** match the custom component identifiers defined in the FlowX process.

> [!NOTE]
>
> React Native supports **self-managed** custom components only: components you author in your app and register by identifier. **Bundled** custom components (source compiled from the backend at runtime) are browser-only; on React Native they are skipped and a warning is logged in development. Re-author any bundled component as a self-managed one.

### Component contract

Each custom component receives the same contract as the React SDK:

```typescript theme={"dark"}
type FlxCustomComponentProps = {
  input: FlxCustomComponentData
  validate: (fn: () => boolean | Promise<boolean>) => void
  saveData: (fn: () => Record<string, unknown>) => void
  isSubmitted: boolean
}

interface FlxCustomComponentData {
  data: Record<string, any>                                                     // process data mapped via inputKeys
  actions: ProcessAction[]                                                      // metadata for each bound action
  runAction: (action: string, params?: Record<string, unknown>) => Promise<void> // run a process action by name
  runUploadAction: (
    action: string,
    file: FlxNativeFile,                                                        // { uri, name, type }
    params?: Record<string, unknown>
  ) => Promise<void>                                                            // upload a file through an upload action
}
```

| Prop | Type | Description |
| - | - | - |
| `input` | `FlxCustomComponentData` | Process data (`input.data`), bound actions (`input.actions`) and the `input.runAction` / `input.runUploadAction` functions. |
| `validate` | `(fn: () => boolean \| Promise<boolean>) => void` | Register a validation function. The SDK calls it when the user submits the process step. Return `true` to pass, `false` to block. |
| `saveData` | `(fn: () => Record<string, unknown>) => void` | Register a data extractor. The SDK calls it on submit to collect values to write back to the process model. |
| `isSubmitted` | `boolean` | `true` after the first submit attempt - use to show validation errors. |

> [!TIP]
>
> Process data mapped through `inputKeys` is available on `input.data`. Run a process action with `input.runAction(name, params?)`; it returns a `Promise` that resolves when the action completes and rejects when the component has no action with that name. Upload a file through an upload action with `input.runUploadAction(name, { uri, name, type }, params?)`; `params` are sent as extra form fields beside the file.

### Example

```tsx theme={"dark"}
import { ComponentType } from 'react'
import { View } from 'react-native'
import { type FlxCustomComponentProps } from '@flowx/react-native-sdk'
import { FlxButton, FlxText } from '@flowx/react-native-ui-toolkit'

const ClientDetailsForm = ({ input }: FlxCustomComponentProps) => (
  <View>
    <FlxText variant="p2">First name: {input?.data?.firstName}</FlxText>
    <FlxButton onPress={() => input?.runAction('submitClientDetails', { firstName: 'Jane' })}>
      Save
    </FlxButton>
  </View>
)

export const customComponents: Record<string, ComponentType<FlxCustomComponentProps>> = {
  ClientDetailsForm,
}
```

> [!WARNING]
>
> Make sure any action names you pass to `input.runAction` match the process action names bound to the component in the FlowX Designer.

## Custom validators

Define **custom validators** on your form fields in the FlowX Designer, then pass their implementations through the `validators` field of `FlowX.configure(...)`. The SDK honors the error messages configured on each field in the process.

Each key in the `validators` object is the validator name referenced in the Designer; the value is a factory that receives the validator's configured params and returns the predicate the SDK runs against the field value. Return `true` to pass or `false` to fail. When it fails, the message configured on that validator in the Designer is shown.

```typescript theme={"dark"}
type ValidatorFn = (...params: string[]) => (value: unknown) => boolean | Promise<boolean>
```

> [!WARNING]
>
> The keys in the `validators` object **MUST** match the custom validator names defined in the FlowX process. If a field references a validator name that is not registered, the SDK logs `Custom validator <name> not found` and skips it.

### Example

```ts theme={"dark"}
import { FlowX } from '@flowx/react-native-sdk'

const validators = {
  // No params: the field must equal a fixed value.
  mustBe45: () => (value) => value === '4.5',

  // Params come from the validator config in the Designer.
  minLength: (min: string) => (value) => String(value ?? '').length >= Number(min),
}

FlowX.configure({
  baseUrl: 'https://yourDomain.dev',
  enginePath: 'onboarding',
  organizationId: '10000001-0001-0001-8001-100000000001',
  validators,
})
```

## Full example

```tsx theme={"dark"}
import { useEffect, useState } from 'react'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { KeyboardProvider } from 'react-native-keyboard-controller'
import { FlowX, type FlxProcessHandle } from '@flowx/react-native-sdk'

FlowX.configure({
  baseUrl: 'https://yourDomain.dev',
  enginePath: 'onboarding',
  organizationId: '10000001-0001-0001-8001-100000000001',
  assetsBaseUrl: 'https://yourDomain.dev/static',
  validators: {
    mustBe45: () => (value) => value === '4.5',
    minLength: (min: string) => (value) => String(value ?? '').length >= Number(min),
  },
})
FlowX.changeLocaleSettings('en-US', 'en')

function OnboardingScreen({ authToken }: { authToken: string }) {
  const [handle, setHandle] = useState<FlxProcessHandle | null>(null)

  useEffect(() => {
    FlowX.setAccessToken(authToken)

    let active = true
    let pending: FlxProcessHandle | null = null

    FlowX.startProcess({
      workspaceId: '8f52744-8403-4e8d-...',
      projectId: '111111-222222-333333-44444',
      processName: 'client_identification',
      params: { firstName: 'John' },
    }).then((h) => {
      if (!active) {
        h.dispose()
        return
      }
      pending = h
      setHandle(h)
    })

    return () => {
      active = false
      pending?.dispose()
    }
  }, [authToken])

  if (!handle) return null
  const { ProcessView } = handle
  return <ProcessView />
}

export default function App({ authToken }: { authToken: string }) {
  return (
    <SafeAreaProvider>
      <KeyboardProvider>
        <OnboardingScreen authToken={authToken} />
      </KeyboardProvider>
    </SafeAreaProvider>
  )
}
```

## Custom loader

The SDK provides a mechanism for container applications to customize the loader UI displayed during process execution. This allows you to replace the default FlowX loader with your own custom implementation based on different loading scenarios.

### Configuration

To configure custom loaders, pass them through the `customLoaderProvider` prop of the `<FlxProcessRenderer />` component:

```tsx theme={"dark"}
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
import { FlxProcessRenderer } from '@flowx/react-native-sdk';

// Define your custom loader components
const StartProcessLoader = () => (
  <View style={styles.scrim}>
    <View style={styles.card}>
      <ActivityIndicator size="large" color="#ffffff" />
      <Text style={styles.label}>Starting process...</Text>
    </View>
  </View>
);

const SpecificActionLoader = () => (
  <View style={styles.scrim}>
    <View style={styles.card}>
      <ActivityIndicator size="large" color="#ffffff" />
      <Text style={styles.label}>Executing specific action...</Text>
    </View>
  </View>
);

const styles = StyleSheet.create({
  // The SDK renders your loader full-screen with no scrim or card of its own,
  // so the loader paints its own dim scrim. Use absolute fill: the SDK centers
  // the node, so a flex child would collapse to its content size instead of
  // covering the screen.
  scrim: {
    ...StyleSheet.absoluteFillObject,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'rgba(0, 0, 0, 0.25)',
  },
  card: {
    minHeight: 100,
    padding: 24,
    borderRadius: 32,
    alignItems: 'center',
    justifyContent: 'center',
    gap: 16,
    backgroundColor: '#1b1b3a',
  },
  label: {
    color: '#ffffff',
  },
});

// Register the custom loaders in your component
export function MyFlxContainer() {
  return (
    <FlxProcessRenderer
      {...otherProps}
      customLoaderProvider={{
        startProcess: <StartProcessLoader />,
        // reloadProcess: <ReloadProcessLoader />,
        // defaultAction: <DefaultActionLoader />,
        // defaultUpload: <DefaultUploadLoader />,
        actions: {
          action1: <SpecificActionLoader />,
          // action2: <SaveDataLoader />,
        },
      }}
    />
  );
}
```

If you drive the SDK through the `FlowX` config (`FlowX.startProcess()` / `FlowX.continueProcess()`) instead of rendering `<FlxProcessRenderer />` yourself, pass the same object through `FlowX.configure()`:

```tsx theme={"dark"}
import { FlowX } from '@flowx/react-native-sdk';

FlowX.configure({
  // ...other configuration
  customLoaderProvider: {
    startProcess: <StartProcessLoader />,
    actions: {
      action1: <SpecificActionLoader />,
    },
  },
});
```

### API specification

The `customLoaderProvider` prop accepts an object of type `FlxCustomLoader`:

```typescript theme={"dark"}
type FlxCustomLoader = {
  actions?: Record<string, ReactNode>
  startProcess?: ReactNode
  reloadProcess?: ReactNode
  defaultAction?: ReactNode
  defaultUpload?: ReactNode
}
```

#### Loader types

* **`startProcess`** - Displayed when starting or resuming a process
* **`reloadProcess`** - Displayed when reloading a process
* **`defaultAction`** - Default loader for actions when `loaderType` is `'action'`. Used when no specific action loader is found in the `actions` record
* **`defaultUpload`** - Default loader for file uploads when `loaderType` is `'upload'`
* **`actions`** - Record mapping specific action identifiers to custom loaders. When an action is executed, the SDK will first check for a matching entry in this record before falling back to `defaultAction`

### Rendering behavior

Custom loaders on React Native differ from the web SDK in a few important ways:

* **The host owns every pixel.** Your loader node is rendered full-screen with no SDK scrim, card, or spinner behind it. If you want a dimmed backdrop or a centered card, paint them yourself (see the `scrim` style in the example above).
* **Size your root with absolute fill.** The SDK centers your node inside the overlay, so a plain `flex: 1` root collapses to its content size. Use `StyleSheet.absoluteFillObject` on the outermost view to cover the whole screen.
* **The loader lives in a native modal.** While visible it blocks all touch input, renders above native iOS page-sheet modals and Android dialogs, and swallows the Android hardware back button — the user cannot dismiss it.
* **Android debounces the loader.** On Android the loader appears only after \~250 ms, so fast responses never flash a loader; on iOS it appears immediately.
* **Theming gate.** Until the SDK finishes loading its theme resources (right after startup), an opaque placeholder is shown instead of any loader — default or custom — to avoid an unthemed flash.

### Fallback behavior

> [!NOTE]
>
> If no custom loader is provided for a specific type, the SDK will automatically fall back to the built-in FlowX loader, themed with the active FlowX theme. This ensures your app continues to function even with partial custom loader configuration.

> [!TIP]
>
> You can use any React Native components — including Reanimated animations, Lottie views, or your own design-system components — to create rich loading experiences that match your app's design.

## Public API methods

The SDK exposes a set of imperative helpers for reading CMS resources (enumerations, substitution tags, and media library items) from your own code, for example inside a custom component. Import `getEnumerationItems` and `getMediaItemUrl` from `@flowx/react-native-sdk`, and `getTag` from `@flowx/core-sdk`:

```tsx theme={"dark"}
import { getEnumerationItems, getMediaItemUrl } from '@flowx/react-native-sdk';
import { getTag } from '@flowx/core-sdk';
```

> [!NOTE]
>
> These methods read the configuration of the active process session (API URL, language, project and build identifiers), so they can only be called after a process has been started — for example from a custom component rendered by the SDK. Results are resolved in the language the SDK was configured with and are cached for the duration of the session.

### getEnumerationItems

Fetches the values of an enumeration by name.

```typescript theme={"dark"}
function getEnumerationItems(name: string, parentName?: string | null): Promise<NomenclatorValue[]>
```

* **`name`** - The name of the enumeration to fetch
* **`parentName`** - Optional name of the parent enumeration, used for hierarchical (parent–child) enumerations. A `null` or empty value returns the first level

Returns the enumeration values sorted by their configured order. Each value contains the localized display text in `label` and the enumeration code in `id` / `value`; for hierarchical enumerations, `nomenclatorId` holds the name of the child enumeration linked to that value.

```tsx theme={"dark"}
const countries = await getEnumerationItems('countries');
// [{ id: 'AT', value: 'AT', label: 'Austria', ... }, { id: 'RO', value: 'RO', label: 'Romania', ... }]
```

### getTag

Resolves a substitution tag — a localized UI string managed in the CMS — by its key.

```typescript theme={"dark"}
function getTag(tag: string): Promise<string | undefined>
```

* **`tag`** - The substitution tag key, with or without the `@@` localization prefix

Returns the tag's value in the current SDK language, or `undefined` if the key does not exist.

```tsx theme={"dark"}
const clearLabel = await getTag('@@sys_clear');
// The prefix is optional — this resolves the same tag:
const sameLabel = await getTag('sys_clear');
```

### getMediaItemUrl

Resolves the download URL of a media library item by its key. The lookup is synchronous and reads the media items the running process has already loaded.

```typescript theme={"dark"}
function getMediaItemUrl(key: string): string | undefined
```

* **`key`** - The media item key, as defined in the project's media library

Returns the item's URL, or `undefined` if no media item matches the key. Both the bare media item key and the fully qualified key (prefixed with the owning application's UUID) are accepted.

```tsx theme={"dark"}
import { Image } from 'react-native';

const logoUrl = getMediaItemUrl('company_logo');

// e.g. render it:
{logoUrl && <Image source={{ uri: logoUrl }} style={styles.logo} />}
```

## Document caching

Documents downloaded by the SDK (for example when a process screen previews a generated file) are cached on disk, keyed by their URL, so re-renders and app relaunches skip the network round-trip. Caching is enabled by default.

* The URL → file path index is persisted to AsyncStorage; the files themselves live in the OS cache directory, so the operating system may evict them at any time — the SDK simply re-downloads on a cache miss.
* Set `cacheDocuments: false` if your documents can change behind the same URL and must always be fetched fresh.
* The flag can be toggled with a later `configure()` call and applies to document loads performed after the change.
* Mirrors `cacheDocuments` on the iOS SDK and `documentsCacheEnabled` on the Android SDK.

```tsx theme={"dark"}
import { FlowX } from '@flowx/react-native-sdk';

FlowX.configure({
  // ...other configuration
  cacheDocuments: false, // always re-download documents
});
```

## Foreground state sync

When the app returns from background to foreground, the SDK re-fetches the root process status and rebuilds the process UI, catching up on anything that changed while the app was backgrounded. This is enabled by default.

* Call `FlowX.setUpdateStateEnabled(false)` to skip the state catch-up. SSE reconnection is unaffected: the SDK still re-establishes the event stream and continues to receive live updates.
* Unlike `FlowX.changeLocaleSettings()`, this setting also applies to the **live** session: it is read on each background → foreground return, so calling `setUpdateStateEnabled()` takes effect immediately, with no process restart needed.
* Mirrors `FXConfig.updateStateEnabled` on the iOS SDK and `Config.updateStateEnabled` on the Android SDK.

```tsx theme={"dark"}
import { FlowX } from '@flowx/react-native-sdk';

FlowX.setUpdateStateEnabled(false); // rely on SSE only, skip the foreground re-fetch
```

## Custom headers

`additionalHttpHeaders` lets the container app attach its own HTTP headers (correlation IDs, API-gateway keys, WAF cookies) to the requests the SDK sends to the **FlowX backend**: REST calls, SSE event streams, and document downloads from the FlowX download endpoint. They are sent in both authenticated and anonymous mode.

* Requests to public or external hosts never carry the custom headers: image and icon fetches, native font downloads, and image/document URLs outside the FlowX download endpoint. Those requests carry no SDK auth either — custom headers travel exactly where the SDK's own auth headers travel, so a secret placed here cannot leak to third-party hosts referenced by process content.
* Custom headers are applied before the SDK-managed headers, so on a name collision the SDK wins — you cannot override `Authorization`, `flowx-platform`, and the other headers the SDK manages itself.
* The headers are live-updatable via `configure()`: REST requests and document downloads read them per request; SSE applies them to connections opened after the change.
* Each `configure()` call replaces the whole header set — pass the complete record every time, not just the headers that changed.

```tsx theme={"dark"}
import { FlowX } from '@flowx/react-native-sdk';

FlowX.configure({
  // ...other configuration
  additionalHttpHeaders: {
    'x-correlation-id': sessionId,
    'x-api-key': gatewayKey,
  },
});

// Later — e.g. after the session rotates. Replaces the record above wholly:
FlowX.configure({
  additionalHttpHeaders: {
    'x-correlation-id': newSessionId,
    'x-api-key': gatewayKey,
  },
});
```

## Custom interceptors

`requestInterceptors` and `responseInterceptors` attach your own [axios interceptors](https://axios-http.com/docs/interceptors) to the SDK's REST layer — for request/response logging, timing, custom error reporting, or per-request headers.

```typescript theme={"dark"}
type HttpRequestInterceptor = {
  onFulfilled?: (
    config: InternalAxiosRequestConfig
  ) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>
  onRejected?: (error: any) => any
}

type HttpResponseInterceptor = {
  onFulfilled?: (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>
  onRejected?: (error: any) => any
}
```

* Interceptors apply to every REST call the SDK makes through its shared axios instance. They do **not** cover SSE event streams or the SDK's non-axios fetches (images, icons, documents, native fonts) — use [Custom headers](#custom-headers) to reach those.
* Like `FlowX.changeLocaleSettings()`, they take effect on the **next** `startProcess()` / `continueProcess()`: interceptors are registered per session and cleared when the session is disposed; the live session keeps the ones it started with.
* Ordering relative to the SDK's built-in interceptors (auth and platform headers, logging, error toasts) is not guaranteed — don't rely on reading or overriding SDK-managed headers such as `Authorization`.
* Mirrors `requestInterceptors` / `responseInterceptors` on the web SDK's `<FlxProcessRenderer />`.

```tsx theme={"dark"}
import {
  FlowX,
  type HttpRequestInterceptor,
  type HttpResponseInterceptor,
} from '@flowx/react-native-sdk';

const requestTagging: HttpRequestInterceptor = {
  onFulfilled: (config) => {
    config.headers['x-request-id'] = generateRequestId();
    return config;
  },
};

const errorReporting: HttpResponseInterceptor = {
  onRejected: (error) => {
    crashReporting.recordError(error);
    return Promise.reject(error);
  },
};

FlowX.configure({
  // ...other configuration
  requestInterceptors: [requestTagging],
  responseInterceptors: [errorReporting],
});
```

## Flowx callbacks and logging

The `FlowX` config exposes hooks for observing what happens inside a process session: analytics events, new processes started by the backend, and a structured log stream. All three are config-level and session-independent: registrations survive `dispose()` and process restarts, so you can register them once at app startup.

### Analytics collector

`FlowX.analyticsCollector()` subscribes to analytics events. An event fires whenever an executed action has `analytics` defined in its params, with the resolved payload:

```typescript theme={"dark"}
type AnalyticsData = {
  type: 'SCREEN' | 'ACTION'
  value: string                                 // the analytics expression from the action params
  screen?: string
  component?: string
  label?: string
  customPayload?: Record<string, unknown> | null // the parsed analyticsCustomPayload
}
```

The method returns an unsubscribe function. Multiple collectors may be registered — each one receives every event.

```tsx theme={"dark"}
import { FlowX, type AnalyticsData } from '@flowx/react-native-sdk';

const unsubscribe = FlowX.analyticsCollector((data: AnalyticsData) => {
  analytics.track(data.value, {
    screen: data.screen,
    component: data.component,
    label: data.label,
    ...data.customPayload,
  });
});

// Stop collecting, e.g. on logout:
unsubscribe();
```

### New process started

Assign a callback to `FlowX.newProcessStarted` to be notified when the backend starts a new process out of the live session: a `START_PROJECT` action, signaled by the `newProcessStarted` SSE event. The callback receives the new process instance UUID.

This is a notification only. The SDK keeps rendering the current process. Showing the new one is up to the host app:

1. Call `dispose()` on the current process handle.
2. Call `FlowX.continueProcess({ processInstanceUuid })` with the UUID the callback received, and mount the new handle's `ProcessView`.

```tsx theme={"dark"}
import { FlowX, type FlxProcessHandle } from '@flowx/react-native-sdk'
import { useEffect, useState } from 'react'

export function OnboardingScreen() {
  const [handle, setHandle] = useState<FlxProcessHandle | null>(null)

  useEffect(() => {
    let active = true
    let current: FlxProcessHandle | null = null

    const show = (next: Promise<FlxProcessHandle>) =>
      next.then((h) => {
        if (!active) {
          h.dispose()
          return
        }
        current = h
        setHandle(h)
      })

    // The backend started a new process: drop the current one, continue the new one.
    FlowX.newProcessStarted = (processInstanceUuid) => {
      current?.dispose()
      current = null
      setHandle(null)
      show(FlowX.continueProcess({ processInstanceUuid }))
    }

    show(
      FlowX.startProcess({
        workspaceId: '8f52744-8403-4e8d-...',
        projectId: '111111-222222-333333-44444',
        processName: 'client_identification',
      })
    )

    return () => {
      active = false
      // Single listener: assigning replaces the previous one; clear it with undefined.
      FlowX.newProcessStarted = undefined
      current?.dispose()
    }
  }, [])

  if (!handle) return null
  const { ProcessView } = handle
  return <ProcessView />
}
```

If the callback throws, the SDK logs the error and continues; the exception does not propagate into the SDK's event handling.

### Logging

`FlowX.logSink()` subscribes to the SDK's structured log-event stream — useful for debugging and for feeding your own telemetry. Events cover four categories:

* **`rest`** - API traffic, including the SDK's image, icon, and document fetches
* **`sse`** - SSE connection lifecycle and received events
* **`expression`** - expression evaluations (hide, disabled, computed, conditional, validator, local script)
* **`cms`** - CMS font registration

```typescript theme={"dark"}
type FlxLogEvent = {
  timestamp: string // ISO 8601 with milliseconds, UTC
  category: 'rest' | 'sse' | 'expression' | 'cms'
  type: string      // e.g. 'request' | 'response' | 'error' for the rest category
  payload: Record<string, unknown>
}
```

It accepts a single logger or an array of loggers, plus an optional `categories` filter, and returns an unsubscribe function. Secrets in headers (`Authorization` & co.) are redacted before events are emitted. Pass the exported `formattedLogger` to pretty-print events to the console.

```tsx theme={"dark"}
import { FlowX, formattedLogger } from '@flowx/react-native-sdk';

// Pretty-print every SDK event to the console:
const unsubscribe = FlowX.logSink(formattedLogger);

// Or send only network traffic to your own telemetry:
FlowX.logSink((event) => telemetry.log(event), { categories: ['rest', 'sse'] });
```

## Chat component

To use the `FlxChatRenderer` component, import it from the React Native SDK:

```tsx theme={"dark"}
import { FlxChatRenderer } from '@flowx/react-native-sdk';
```

#### Usage

The chat fills its parent, so give it a flexed container and let your screen own the safe area:

```tsx theme={"dark"}
import { SafeAreaView } from 'react-native-safe-area-context';

<SafeAreaView style={{ flex: 1 }} edges={['top', 'bottom']}>
  <FlxChatRenderer
    baseUrl={baseUrl}
    accessToken={accessToken}
    assetsBaseUrl={assetsBaseUrl}
    language={language}
    locale={locale}
    themeId={themeId}
    organizationId={organizationId}
    projectId={projectId}
    workspaceId={workspaceId}
    source={source}
    chatConfig={chatConfig}
    cache={cache}
  />
</SafeAreaView>
```

Parameters:

| Name | Description | Type | Mandatory | Example |
| - | - | - | - | - |
| `baseUrl` | Your base API URL | `string` | ✅ | `https://yourDomain.dev` |
| `accessToken` | Authorization token | `string` | ✅ | (retrieved from auth provider) |
| `assetsBaseUrl` | Path for static resources | `string` | ✅ | (set via environment) |
| `enginePath` | Process subpath. The leading slash is optional | `string` | ✅ | `onboarding` |
| `language` | Language used to localize the chat interface | `string` | ✅ | `en` |
| `locale` | Locale setting for date and number formatting | `string` | ✅ | `en-US` |
| `themeId` | Theme identifier for styling | `string` | ✅ | (retrieved dynamically) |
| `organizationId` | Organization ID — fonts resolve under `/cms/api/organizations/{organizationId}/fonts` | `string` | ✅ | (retrieved dynamically) |
| `projectId` | The FlowX project ID | `string` | ✅ | (retrieved dynamically) |
| `workspaceId` | The workspace ID | `string` | ✅ | (retrieved dynamically) |
| `source` | Source object with type and id | `object` | ✅ | `{ type: 'WORKFLOW', id: '...' }` |
| `chatConfig` | Chat configuration object | `object` | ❌ | `{ welcomeMessage: '...', ... }` |
| `cache` | Cache the fetched resources for this build | `boolean` | ❌ | `true` |
| `buildId` | Pinned build identifier | `string` | ❌ | (retrieved dynamically) |
| `voiceInputEnabled` | Offer the microphone in the composer | `boolean` | ❌ | `false` |
| `testID` | Test identifier forwarded to the chat column | `string` | ❌ | `chat-screen` |

#### chatConfig parameters

The `chatConfig` object accepts the following properties:

| Name | Description | Type | Default value |
| - | - | - | - |
| `welcomeMessage` | Message displayed when chat is first opened | `string` | `'Welcome to the chat!'` |
| `thinkingMessage` | Message displayed while waiting for a response | `string` | - |
| `title` | Chat header title | `string` | - |
| `subtitle` | Chat header subtitle | `string` | - |
| `showChatIcon` | Whether to display the header avatar | `boolean` | `true` |
| `showAgentAvatar` | Whether agent replies carry an avatar | `boolean` | `true` |
| `showSeparator` | Whether to show a separator under the header | `boolean` | `true` |
| `hideHeader` | Render the transcript and composer with no header at all | `boolean` | `false` |
| `hideNewConversation` | Hide the new-chat action in the header | `boolean` | `false` |
| `hideHistory` | Hide the conversation-history action in the header | `boolean` | `false` |
| `newChatLabel` | Accessible label for the new chat button | `string` | `'New chat'` |
| `historyTitle` | Title displayed on the chat history panel | `string` | - |
| `inputPlaceholder` | Placeholder text for the message input field | `string` | - |
| `maxInputRows` | Maximum number of rows the input grows to | `number` | `10` |
| `errorMessage` | Message shown when an error occurs | `string` | `'Error message'` |
| `canRegenerate` | Show a regenerate action on failed responses | `boolean` | `true` |
| `regenerateLabel` | Label for the regenerate action | `string` | `'Regenerate'` |
| `suggestedPrompts` | Prompt pills offered before the first message | `{ label: string; prompt: string \| null }[]` | `[]` |
| `suggestedPromptsTitle` | Title shown above the suggested prompts | `string` | - |
| `headerIcon` | Overrides the themed header icon | `content icon` | (from theme) |
| `agentIcon` | Overrides the themed agent avatar | `content icon` | (from theme) |
| `sendIcon` | Overrides the themed send icon | `content icon` | (from theme) |
| `chatAvatar` | Header avatar used when no `headerIcon` resolves | `avatar` | - |

#### Voice input

Voice messages need a recorder engine and a microphone declaration in the **app**, which a library cannot add on its own:

1. Install the optional peer dependencies `react-native-nitro-sound` and `react-native-nitro-modules`. Without them the SDK installs no recorder and the microphone button is not drawn.
2. Add the toolkit's Expo config plugin, which declares `RECORD_AUDIO` on Android and fills `NSMicrophoneUsageDescription` on iOS (your own copy is kept if you already wrote one):

```json theme={"dark"}
{
  "expo": {
    "plugins": ["@flowx/react-native-ui-toolkit/app.plugin.js"]
  }
}
```

Set `voiceInputEnabled={false}` to hide the microphone even where an engine is installed. Voice messages recorded on other clients still play back either way.


## Related topics

- [React Native SDK](https://docs.flowx.ai/release-notes/v6.x/v6.0.0-october-2026/migrating-from-v5.9-to-v6.0/react-native.md)
- [FlowX.AI 5.9.0 Release Notes](https://docs.flowx.ai/release-notes/v5.x/v5.9.0-june-2026/v5.9.0-june-2026.md)
- [SDKs overview](https://docs.flowx.ai/6.x/sdks/sdks-overview.md)
- [FlowX.AI architecture: services, layers, and data flow](https://docs.flowx.ai/6.x/docs/platform-overview/flowx-architecture.md)
- [Overview](https://docs.flowx.ai/release-notes/v6.x/v6.0.0-october-2026/migrating-from-v5.9-to-v6.0/migration-overview.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.