<!--
  Formcentric Documentation
  @formcentric/client: 4.6.0
  @formcentric/client-react: 1.3.1-beta
  @formcentric/client-vue: 1.3.1-beta
-->

## Table of Contents

- [1.0 Description](#10-description)
- [2.0 Embedding](#20-embedding)
  - [2.1 Installation](#21-installation)
  - [2.2 SDK Integration via mount()](#22-sdk-integration-via-mount)
  - [2.3 Including Bundled or Local Theme Assets](#23-including-bundled-or-local-theme-assets)
  - [2.4 Return Value and Lifecycle](#24-return-value-and-lifecycle)
  - [2.5 Reinitialization and Configuration Changes](#25-reinitialization-and-configuration-changes)
  - [2.6 Events and Error Handling](#26-events-and-error-handling)
- [3.0 Configuration](#30-configuration)
  - [3.1 Target Element](#31-target-element)
  - [3.2 Identification and Form Source](#32-identification-and-form-source)
  - [3.3 Theme and Asset Configuration](#33-theme-and-asset-configuration)
  - [3.4 Pre-population, Request Context and Metadata](#34-pre-population-request-context-and-metadata)
  - [3.5 Language, Locale and Translations](#35-language-locale-and-translations)
  - [3.6 Layout and Debugging](#36-layout-and-debugging)
  - [3.7 Parent URL / Double-Opt-in](#37-parent-url--double-opt-in)
  - [3.8 Complete MountConfig Reference](#38-complete-mountconfig-reference)
  - [3.9 Important Behavioral Rules and Defaults](#39-important-behavioral-rules-and-defaults)
- [4.0 Troubleshooting](#40-troubleshooting)
  - [4.1 mount() throws an error immediately](#41-mount-throws-an-error-immediately)
  - [4.2 The form is not displayed](#42-the-form-is-not-displayed)
  - [4.3 The form is unstyled](#43-the-form-is-unstyled)
  - [4.4 Configuration changes do not affect the running form](#44-configuration-changes-do-not-affect-the-running-form)
  - [4.5 Conflict error for the same embedId](#45-conflict-error-for-the-same-embedid)

# Formcentric Client SDK

## 1.0 Description

The Formcentric Client SDK is intended for programmatic integrations in single-page applications and modular frontends. It is based on `mount()` from `@formcentric/client`.

Unlike the classic static embedding via `formcentric.js`, `formapp.js`, `data-fc-*` attributes and `window.formcentric.initFormcentric()`, SDK integration is entirely configuration-based. Target element and configuration are passed explicitly to `mount()`.

This makes the SDK the recommended integration path for framework adapters, project-specific wrappers, and direct app integrations.

Important: The SDK is restart-based. Many configuration values only take effect when initializing a form instance. If a configuration object changes in your application, a running form is not automatically updated.

## 2.0 Embedding

### 2.1 Installation

First, install the Formcentric client in your project:

```bash
npm install @formcentric/client
```

If you want to bundle theme assets locally, you can also use the included dist files from the same package.

### 2.2 SDK Integration via mount()

Embedding is done via `mount(element, config)` from `@formcentric/client`.

A simple example:

```ts
import { mount } from '@formcentric/client'

const form = mount('#my-form', {
    embedId: 'ihre-embed-id',
    srcUrl: 'https://form.formcentric.com',
})

await form.ready
```

The same with a DOM element:

```ts
import { mount } from '@formcentric/client'

const container = document.querySelector<HTMLDivElement>('#my-form')

if (!container) {
    throw new Error('Container nicht gefunden')
}

const form = mount(container, {
    embedId: 'ihre-embed-id',
    srcUrl: 'https://form.formcentric.com',
})
```

Important notes:

- `element` can be an `HTMLElement` or a CSS selector string.
- At least one of `embedId` or `formDefinition` must be provided in the configuration.
- SDK integrations are config-authoritative. `data-fc-*` attributes on the target element are not part of the SDK contract.
- `data-fc-watch` is static-only and is ignored for SDK mounts.
- The runtime is loaded via the resolved formapp path. A direct import of `formcentric.js` is not required.
- Alternatively, a `formDefinition` can be passed directly instead of `embedId`.

#### 2.2.1 Browser-global Defaults

In addition to the local `mount(element, config)` configuration, the SDK can evaluate browser-global defaults. These defaults are not static-specific, but are also used by SDK-based integrations.

| Mechanism                                    | Description                                                                                                        |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `configure({...})` from `@formcentric/client` | Validates browser-global defaults and replaces previously set default values.                                      |
| direct mutation of `window.formcentric`      | Sets browser-global defaults directly on the window object. This also works without a prior `configure(...)` call. |

The following applies:

| Topic                     | Behavior                                                                                                                                                                             |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Priority                  | Local `mount()` configuration overrides browser-global defaults.                                                                                                                     |
| Object values             | `requestHeaders`, `themeVariables` and `configuration` are merged between global and local configuration. For duplicate keys, local configuration takes precedence.                 |
| Snapshot behavior         | Browser-global defaults are read at the time of the `mount()` call. Later changes do not retroactively affect already mounted instances.                                             |
| repeated `configure(...)` | Another call replaces the previously set browser-global defaults instead of extending them incrementally.                                                                            |
| Static-only               | `dynamicInit` can be set browser-globally, but is not evaluated by SDK mounts themselves.                                                                                            |

The complete list of shared browser-global default keys and their semantic meaning is documented centrally in [general.md](./01-general.md).

Not browser-global in the SDK are in particular `embedId`, `formDefinition`, `vars`, `params`, `refs`, `formName`, `instanceId` and `conflictBehavior`.

Example:

```ts
import { configure, mount } from '@formcentric/client'

configure({
    srcUrl: 'https://form.formcentric.com',
    requestHeaders: {
        'X-App': 'sdk-app',
    },
})

const form = mount('#my-form', {
    embedId: 'ihre-embed-id',
    requestHeaders: {
        'X-Form': 'contact',
    },
})
```

### 2.3 Including Bundled or Local Theme Assets

If your application bundles theme assets itself, import them before mounting and disable runtime reloading of theme and templates.

Example with dist assets from `@formcentric/client`:

```ts
import { mount } from '@formcentric/client'
import '@formcentric/client/dist/formapp.js'
import '@formcentric/client/dist/themes/geneva/styles.css'
import '@formcentric/client/dist/themes/geneva/script.js'
import themeVariables from '@formcentric/client/dist/themes/geneva/_variables.json'

const form = mount('#my-form', {
    embedId: 'ihre-embed-id',
    srcUrl: 'https://form.formcentric.com',
    themeVariables: themeVariables as Record<string, unknown>,
    skipThemeLoad: true,
    skipTemplatesLoad: true,
})
```

Note the following:

- Theme styles must be loaded by your application itself.
- The theme's JavaScript templates must also be loaded by your application.
- `skipThemeLoad: true` and `skipTemplatesLoad: true` prevent double loading at runtime.
- `themeVariables` can be passed directly as an object if you want to avoid the runtime request for variables.

If you want to use runtime loading, configure either `themeDir` and `theme` or explicit URLs like `themeUrl`, `templateUrl` and `themeVariableUrl`.

### 2.4 Return Value and Lifecycle

`mount()` returns a `FormInstance` with lifecycle methods and a ready promise.

Example:

```ts
import { mount } from '@formcentric/client'

const form = mount('#my-form', {
    embedId: 'ihre-embed-id',
    srcUrl: 'https://form.formcentric.com',
})

await form.ready

// Later:
// await form.reload()
// await form.stop()
// await form.unmount()
```

The following applies:

- `ready` resolves after successful initialization.
- `reload()` reinitializes the form in the same container.
- `stop()` terminates the running instance but leaves the target element in the DOM.
- `unmount()` removes the form and fully cleans up the instance.

For stricter lifecycle sequencing in SPA code, `reload()`, `stop()` and `unmount()` should each be `await`ed.

### 2.5 Reinitialization and Configuration Changes

SDK integration is restart-based. Configuration values are applied during initialization and are not automatically transferred live to a running instance.

When configuration values change, the embedding should be explicitly rebuilt, for example via `await form.unmount()` followed by a new `mount()`, or via `await form.reload()` if container and identity are intentionally kept the same.

The following applies:

- Changes to `embedId` or `formDefinition` should typically result in a fresh mount.
- If instance handovers are intentionally used, `embedId` should remain stable and `conflictBehavior: 'stop-existing'` should be set.
- For other configuration changes, a controlled remount should be performed instead of expecting live updates.

### 2.6 Events and Error Handling

For simple integration cases, optional callbacks can be passed directly in the configuration:

- `onReady`: called after successful initialization
- `onError`: called on initialization or runtime errors

Example:

```ts
const form = mount('#my-form', {
    embedId: 'ihre-embed-id',
    srcUrl: 'https://form.formcentric.com',
    onReady: () => console.log('Formcentric bereit'),
    onError: error => console.error('Formcentric-Fehler', error),
})
```

`onReady` signals that initialization has completed successfully. It is not a dedicated signal for a first visible render time.

## 3.0 Configuration

The semantic meaning of shared Formcentric keys is documented centrally in [general.md](./01-general.md). This page therefore primarily describes SDK notation, the behavior of `mount()`, and SDK-specific options.

### 3.1 Target Element

The first parameter of `mount()` is the target element.

Supported are:

- a CSS selector string, for example `'#my-form'`
- an actual `HTMLElement`

The target element must be present in the browser DOM when `mount()` is called.

### 3.2 Identification and Form Source

The shared semantics of these fields are described in [general.md](./01-general.md). In the SDK, the corresponding keys are:

| SDK Key           |
| ----------------- |
| `embedId`         |
| `formDefinition`  |
| `srcUrl`          |
| `dataUrl`         |
| `formappUrl`      |
| `designUrl`       |

### 3.3 Theme and Asset Configuration

The shared semantics of these fields are described in [general.md](./01-general.md). In the SDK, the corresponding keys are:

| SDK Key              |
| -------------------- |
| `themeUrl`           |
| `themeVariableUrl`   |
| `themeVariables`     |
| `templateUrl`        |
| `themeDir`           |
| `theme`              |
| `skipThemeLoad`      |
| `skipTemplatesLoad`  |
| `skipFormLoad`       |

### 3.4 Pre-population, Request Context and Metadata

The shared semantics of these fields are described in [general.md](./01-general.md). In the SDK, the corresponding keys are:

| SDK Key           |
| ----------------- |
| `vars`            |
| `params`          |
| `refs`            |
| `token`           |
| `requestHeaders`  |
| `formName`        |
| `instanceId`      |
| `env`             |
| `configuration`   |

### 3.5 Language, Locale and Translations

The shared semantics of language, locale and translations are described in [general.md](./01-general.md). In the SDK, the corresponding keys are `language`, `locale`, `localesPath`, and `locales`.

Example:

```ts
const form = mount('#my-form', {
    embedId: 'ihre-embed-id',
    srcUrl: 'https://form.formcentric.com',
    language: 'de-DE',
    locale: 'de_DE',
    localesPath: '/locales/custom.js',
})
```

Bundled SDK integrations can alternatively pass an in-memory `locales` object, which is the recommended path when you want to supply `date-fns` and upload-locale objects directly:

```ts
import { sv as dateLocale } from 'date-fns/locale/sv'
import uploadLocale from '@uppy/locales/lib/sv_SE'

const form = mount('#my-form', {
    embedId: 'your-embed-id',
    srcUrl: 'https://form.formcentric.com',
    language: 'sv-SE',
    locales: {
        'sv-SE': {
            resources: {
                page: 'Sida',
                cancel_label: 'Avbryt',
            },
            dateLocale,
            uploadLocale,
        },
    },
})
```

### 3.6 Layout and Debugging

The shared semantics of `maxWidth`, `height` and `debug` are described in [general.md](./01-general.md).

Example:

```ts
const form = mount('#my-form', {
    embedId: 'ihre-embed-id',
    srcUrl: 'https://form.formcentric.com',
    height: '800px',
    maxWidth: '600px',
    debug: true,
})
```

### 3.7 Parent URL / Double-Opt-in

The semantic meaning of `parentUrl` is described in [general.md](./01-general.md). In the SDK, the same shared key is used directly as `parentUrl`.

Example:

```ts
const form = mount('#my-form', {
    embedId: 'ihre-embed-id',
    srcUrl: 'https://form.formcentric.com',
    parentUrl: 'https://meine-website.de/?form=open',
})
```

If `parentUrl` is not explicitly set, the current page URL is used as the return context.

### 3.8 Complete MountConfig Reference

The SDK supports the following mount configuration fields:

```ts
type MountConfig = {
    embedId?: string
    formDefinition?: string | object
    srcUrl?: string
    dataUrl?: string
    formappUrl?: string
    designUrl?: string
    themeUrl?: string
    themeVariableUrl?: string
    themeVariables?: Record<string, unknown>
    templateUrl?: string
    themeDir?: string
    theme?: string
    vars?: Record<string, unknown>
    params?: Record<string, unknown>
    refs?: string
    token?: string
    requestHeaders?: Record<string, string>
    locale?: string
    language?: string
    debug?: boolean
    formName?: string
    instanceId?: string
    env?: 'preview' | 'live' | 'vestibule_live'
    parentUrl?: string
    skipFormLoad?: boolean
    skipTemplatesLoad?: boolean
    skipThemeLoad?: boolean
    configuration?: Record<string, unknown>
    localesPath?: string
    locales?: Record<string, unknown>
    maxWidth?: string
    height?: string
    conflictBehavior?: 'throw' | 'stop-existing'
    onReady?: () => void
    onError?: (error: Error) => void
}
```

### 3.9 Important Behavioral Rules and Defaults

- At least one of `embedId` or `formDefinition` must be set.
- SDK integrations are config-authoritative. `data-fc-*` attributes on the target element are not used as a live configuration source.
- Most fields are initialization-only props. Changes to them are not automatically applied to a running instance.
- `conflictBehavior` defaults to `'throw'` for direct SDK mounts.
- If an instance handover for the same `embedId` is intentionally desired, `conflictBehavior: 'stop-existing'` should be set.
- `skipThemeLoad` and `skipTemplatesLoad` are only considered if explicitly set.
- Object-based configurations like `vars`, `params`, `themeVariables` and `configuration` should be passed as serializable objects. `locales` is the exception: it is expected to carry `date-fns` and upload-locale objects by reference.
- `data-fc-watch` is static-only and is ignored for SDK mounts.

## 4.0 Troubleshooting

### 4.1 mount() throws an error immediately

Check:

- whether the code is actually running in a browser environment
- whether the passed selector points to an existing element
- whether a configuration object is actually being passed
- whether at least `embedId` or `formDefinition` is set

### 4.2 The form is not displayed

Check:

- whether `embedId` is correct
- whether the form is published
- whether `srcUrl` is set correctly
- whether your domain is registered in Formcentric
- whether `dataUrl`, `formappUrl` or `designUrl` point to the correct endpoints
- whether theme and template resources are configured correctly if a local theme is used

### 4.3 The form is unstyled

Check:

- whether the theme CSS is loaded
- whether the theme templates are loaded
- whether `skipThemeLoad` and `skipTemplatesLoad` are only set if you are actually bundling these assets yourself
- whether `themeDir` and `theme` or the explicit asset URLs are fully configured

### 4.4 Configuration changes do not affect the running form

The SDK is restart-based. Many configuration values only take effect during initialization.

Check:

- whether a remount is performed explicitly when relevant configuration changes
- whether you are not expecting init-time fields to be applied live to a running instance
- whether `embedId` remains stable if instance handovers are intentionally used

### 4.5 Conflict error for the same embedId

If the same `embedId` is mounted multiple times, the SDK enforces stricter conflict behavior than the static path.

Check:

- whether the same `embedId` is already active on another element
- whether `conflictBehavior` should be set to `'stop-existing'` for your use case
- whether `stop()` or `unmount()` are correctly `await`ed before a remount
