# Référence de la définition de formulaire

Une définition de formulaire est le JSON qui décrit un formulaire : ses champs, ses étapes, ses conditions, ses
limites et ses réglages antipourriel. Le constructeur l’écrit pour vous, et le serveur l’applique à chaque soumission.
Utilisez cette page quand vous avez besoin du nom ou de la valeur exacte d’un réglage.

Chaque exemple ci-dessous est un correctif de définition. Chaque clé de premier niveau que vous envoyez remplace toute
cette clé, et les clés que vous omettez restent telles quelles. Pour ajouter un champ, envoyez tous les champs existants
et le nouveau.

Ces clés de premier niveau peuvent être modifiées : `fields`, `steps`, `contentBlocks`, `conditions`, `visibilityRules`,
`validationRules`, `limits`, `spam`, `verification`, `settings`, `allowedOrigins` et `primaryPublicTarget`.

Les destinataires, les expéditeurs et les courriels de notification ne font pas partie de ce correctif. Réglez-les dans les
onglets **Livraison** et **Modèles** du formulaire. Consultez [Premiers pas](/fr/guides/getting-started/).

## Noms et chemins des champs

`fields` associe un nom de champ à sa définition. Les noms utilisent des lettres, des chiffres, `_` et `-`.

Les autres réglages désignent un champ par son chemin :

| Chemin | Désigne |
| --- | --- |
| `email` | Un champ de premier niveau |
| `contact.email` | Un champ dans le groupe `contact` |
| `guests[]` | Chaque élément de la liste `guests`, dans les étapes, les blocs de contenu et les règles de visibilité |
| `guests[0].name` | Un élément de la liste, dans les conditions et les règles de validation |

Un chemin qui n’existe pas échoue avec `FORM_CONFIG_FIELD_REF_UNKNOWN`.

## Types de champs

`type` prend l’une de ces douze valeurs. Toute autre valeur, comme `file` ou `signature`, échoue avec
`FORM_CONFIG_FIELD_TYPE_INVALID`.

| `type` | Recueille | Soumis comme |
| --- | --- | --- |
| `string` | Un texte court | Chaîne |
| `textarea` | Un texte long | Chaîne |
| `email` | Une adresse courriel, toujours vérifiée | Chaîne |
| `url` | Une adresse Web, toujours vérifiée | Chaîne |
| `phone` | Un numéro de téléphone | Chaîne |
| `number` | Un nombre | Nombre, ou chaîne qui se convertit en nombre |
| `boolean` | Oui ou non | `true` ou `false` |
| `date` | Une date au format `YYYY-MM-DD`, toujours vérifiée | Chaîne |
| `select` | Un choix dans une liste | Chaîne |
| `checkbox` | Une case à cocher, ou plusieurs choix | `true` ou `false`, ou une chaîne ou un tableau de chaînes avec `allowedValues` |
| `object` | Un groupe de champs | Objet |
| `array` | Un élément répété | Tableau |

Les questions d’évaluation et les curseurs du constructeur sont des champs `number`. Il n’y a pas de champ de téléversement de
fichier ni de signature.

## Options de tous les champs

| Option | Valeur | Effet |
| --- | --- | --- |
| `label` | Chaîne | Le nom du champ sur le formulaire |
| `description` | Chaîne | Le texte d’aide sous le champ |
| `required` | `true` ou `false` | Une réponse vide échoue avec `FIELD_REQUIRED` |
| `placeholder` | Chaîne | L’indication dans le champ. Pas sur `object` ni `array` |
| `defaultValue` | Dépend du `type` | La réponse de départ. Pas sur `object` ni `array` |
| `messages` | Objet de chaînes | Vos propres textes d’erreur, par règle ou par code d’erreur |

```json
{
  "fields": {
    "name": {
      "type": "string",
      "label": "Your name",
      "description": "As it appears on your invoice.",
      "placeholder": "Jane Example",
      "required": true,
      "messages": { "required": "Enter your name." }
    }
  }
}
```

Une `defaultValue` doit convenir au champ : un nombre fini sur `number`, `true` ou `false` sur `boolean`, une vraie date
`YYYY-MM-DD` sur `date`, et une chaîne sur les autres types de texte. Avec `allowedValues`, elle doit en faire partie.

## Champs de texte

Ces options fonctionnent sur `string`, `textarea`, `email`, `url` et `phone`, ainsi que sur `date` et `select` là où le tableau
l’indique.

| Option | Valeur | Fonctionne sur | Code d’erreur |
| --- | --- | --- | --- |
| `min`, `max` | Longueur, 0 ou plus | Tous les types de texte | `FIELD_STRING_TOO_SHORT`, `FIELD_STRING_TOO_LONG` |
| `exactLength` | Longueur, 0 ou plus. Ne se combine pas avec `min` ni `max` | Tous les types de texte, `date`, `select` | `FIELD_STRING_LENGTH_INVALID` |
| `pattern`, `flags` | Expression régulière d’au plus 500 caractères. `flags` accepte `i`, `m` et `u` | Tous les types de texte | `FIELD_PATTERN_INVALID` |
| `allowedValues` | Tableau de chaînes | Tous les types de texte | `FIELD_VALUE_NOT_ALLOWED` |
| `startsWith`, `endsWith` | Chaîne non vide | `string`, `textarea`, `email`, `url`, `phone` | `FIELD_STRING_PREFIX_INVALID`, `FIELD_STRING_SUFFIX_INVALID` |
| `contains`, `notContains` | Chaîne non vide | `string`, `textarea`, `email`, `url`, `phone` | `FIELD_STRING_CONTAINS_REQUIRED`, `FIELD_STRING_CONTAINS_FORBIDDEN` |

```json
{
  "fields": {
    "reference": {
      "type": "string",
      "label": "Reference",
      "min": 4,
      "max": 20,
      "startsWith": "REF-",
      "pattern": "^REF-[0-9]+$"
    },
    "message": {
      "type": "textarea",
      "label": "Message",
      "required": true,
      "max": 5000,
      "trim": true
    }
  }
}
```

### Texte masqué ou caché

Sur `string` seulement, `inputType` vaut `password` ou `hidden`. Il change l’affichage du champ. La validation le traite quand même
comme une chaîne.

```json
{
  "fields": {
    "access_code": { "type": "string", "label": "Access code", "inputType": "password" },
    "campaign": { "type": "string", "inputType": "hidden", "defaultValue": "spring" }
  }
}
```

### Vérifications de format sur le texte

Ces options fonctionnent sur les champs `string`. `countryCode` et `regionCodeCountry` fonctionnent aussi sur `select`.

| Option | Valeur | Code d’erreur |
| --- | --- | --- |
| `uuidVersion` | `any`, `v4` ou `v7` | `FIELD_UUID_INVALID` |
| `countryCode` | `iso2` | `FIELD_COUNTRY_CODE_INVALID` |
| `postalCodeCountry` | `CA` ou `US` | `FIELD_POSTAL_CODE_INVALID` |
| `regionCodeCountry` | `CA` ou `US` | `FIELD_REGION_CODE_INVALID` |
| `timeFormat` | `HH:mm` ou `HH:mm:ss` | `FIELD_TIME_INVALID` |
| `dateTimeFormat` | `iso8601`, qui exige un fuseau horaire | `FIELD_DATETIME_INVALID` |

```json
{
  "fields": {
    "postal_code": { "type": "string", "label": "Postal code", "postalCodeCountry": "CA" },
    "province": { "type": "select", "label": "Province", "regionCodeCountry": "CA", "allowedValues": ["QC", "ON"] },
    "pickup_time": { "type": "string", "label": "Pickup time", "timeFormat": "HH:mm" }
  }
}
```

## Champs de courriel

Les réponses `email` sont toujours vérifiées, et échouent avec `FIELD_EMAIL_INVALID`. `normalizeEmail` met l’adresse en minuscules
avant de la vérifier et de la conserver.

```json
{
  "fields": {
    "email": { "type": "email", "label": "Email", "required": true, "max": 254, "trim": true, "normalizeEmail": true }
  }
}
```

## Champs d’URL

Les réponses `url` sont toujours vérifiées, et acceptent `http` et `https` par défaut. Utilisez `allowedProtocols` pour changer cela.
`javascript`, `data` et `vbscript` ne sont pas permis. Une mauvaise adresse échoue avec `FIELD_URL_INVALID`, et un autre protocole avec
`FIELD_URL_PROTOCOL_NOT_ALLOWED`.

```json
{
  "fields": {
    "website": { "type": "url", "label": "Website", "allowedProtocols": ["https"] }
  }
}
```

## Champs de téléphone

Un champ `phone` accepte n’importe quel texte par défaut. `phoneFormat` accepte `e164`, qui exige un numéro comme `+15145550123`, et
échoue avec `FIELD_PHONE_INVALID`.

```json
{
  "fields": {
    "phone": { "type": "phone", "label": "Phone", "phoneFormat": "e164" }
  }
}
```

## Champs numériques

| Option | Valeur | Code d’erreur |
| --- | --- | --- |
| `min`, `max` | La valeur la plus basse et la plus haute, 0 ou plus | `FIELD_NUMBER_TOO_SMALL`, `FIELD_NUMBER_TOO_LARGE` |
| `integer` | `true` pour des nombres entiers seulement | `FIELD_INTEGER_REQUIRED` |
| `maxDecimalPlaces` | Nombre entier, 0 ou plus | `FIELD_DECIMAL_PLACES_INVALID` |

Une valeur qui ne se lit pas comme un nombre échoue avec `FIELD_TYPE_INVALID`. `min` et `max` ne peuvent pas être négatifs, peu importe
le type de champ.

```json
{
  "fields": {
    "rating": { "type": "number", "label": "Your rating", "integer": true, "min": 1, "max": 5 },
    "budget": { "type": "number", "label": "Budget", "maxDecimalPlaces": 2 }
  }
}
```

## Champs de date

Les réponses `date` doivent être de vraies dates au format `YYYY-MM-DD`. `dateMin` et `dateMax` prennent le même format et incluent la
date indiquée.

| Règle | Code d’erreur |
| --- | --- |
| Pas une vraie date | `FIELD_DATE_INVALID` |
| Avant `dateMin` | `FIELD_DATE_TOO_EARLY` |
| Après `dateMax` | `FIELD_DATE_TOO_LATE` |

```json
{
  "fields": {
    "event_date": { "type": "date", "label": "Event date", "dateMin": "2027-01-01", "dateMax": "2027-12-31" }
  }
}
```

## Champs de choix

Utilisez `select` pour un seul choix. Énumérez les choix dans `allowedValues`.

```json
{
  "fields": {
    "department": { "type": "select", "label": "Department", "required": true, "allowedValues": ["Sales", "Support"] }
  }
}
```

### Plusieurs choix

Utilisez `checkbox` avec `allowedValues`. `minSelections` et `maxSelections` fixent l’intervalle, et échouent avec
`FIELD_SELECTION_TOO_FEW` et `FIELD_SELECTION_TOO_MANY`. `defaultValue` est une chaîne ou un tableau de chaînes de la liste.

```json
{
  "fields": {
    "interests": {
      "type": "checkbox",
      "label": "Interests",
      "allowedValues": ["Workshops", "Newsletter", "Events"],
      "minSelections": 1,
      "maxSelections": 2,
      "defaultValue": ["Newsletter"]
    }
  }
}
```

### Une seule case à cocher

Sans `allowedValues`, un `checkbox` est une seule case à cocher, et `boolean` fonctionne de la même façon. `defaultValue` vaut `true` ou
`false`. Ajoutez `accepted` quand la case doit être cochée, par exemple pour un consentement. L’échec donne `FIELD_ACCEPTED_REQUIRED`.

```json
{
  "fields": {
    "consent": { "type": "checkbox", "label": "I agree to the terms", "required": true, "accepted": true },
    "subscribe": { "type": "boolean", "label": "Send me the newsletter", "defaultValue": false }
  }
}
```

## Groupes

`type: "object"` regroupe des champs sous un même nom. Il accepte `label`, `description`, `required`, `messages` et `fields`. Les groupes
s’imbriquent sur au plus 8 niveaux. Les réponses arrivent sous forme d’objet, avec des chemins comme `contact.email`.

```json
{
  "fields": {
    "contact": {
      "type": "object",
      "label": "Contact",
      "fields": {
        "name": { "type": "string", "label": "Name", "required": true },
        "email": { "type": "email", "label": "Email", "required": true }
      }
    }
  }
}
```

## Listes

`type: "array"` répète un élément. `item` contient n’importe quel type de champ, y compris un groupe.

| Option | Valeur | Code d’erreur |
| --- | --- | --- |
| `minItems` | Nombre entier, 0 ou plus | `FIELD_ARRAY_TOO_SHORT` |
| `maxItems` | Nombre entier, 0 ou plus | `FIELD_ARRAY_TOO_LONG` |
| `uniqueItems` | `true` pour refuser les répétitions | `FIELD_ARRAY_DUPLICATES` |

```json
{
  "fields": {
    "guests": {
      "type": "array",
      "label": "Guests",
      "minItems": 1,
      "maxItems": 5,
      "item": {
        "type": "object",
        "fields": {
          "name": { "type": "string", "label": "Guest name", "required": true }
        }
      }
    }
  }
}
```

## Nettoyage des réponses

Les traitements nettoient une réponse avant sa validation. Le résultat est la valeur conservée et livrée. Ils fonctionnent sur `string`,
`textarea`, `email`, `url`, `phone`, `date` et `select`, et un champ peut en avoir jusqu’à 25 de chaque sorte.

Les `sanitizers` passent d’abord, puis `trim`, `collapseWhitespace` et `normalizeEmail`, puis les `transformers`, puis `caseTransform`.

| `type` de nettoyage | Ce qu’il fait |
| --- | --- |
| `trim` | Retire les espaces au début et à la fin |
| `collapseWhitespace` | Remplace toute suite d’espaces et de sauts de ligne par une seule espace |
| `normalizeNewlines` | Uniformise les sauts de ligne |
| `stripTags` | Retire tout ce qui est écrit comme une balise HTML |
| `removeUrls` | Retire les adresses Web qui commencent par `http://`, `https://` ou `www.` |
| `removeEmoji` | Retire les émojis |
| `digitsOnly` | Garde seulement les chiffres. Sur `string` et `phone` seulement |

| `type` de transformation | Exige aussi | Ce qu’il fait |
| --- | --- | --- |
| `case` | `mode` : `upper` ou `lower` | Change la casse de toute la réponse |
| `map` | `values` : jusqu’à 100 chaînes | Remplace une réponse entière par une autre. La réponse doit correspondre exactement |

```json
{
  "fields": {
    "province": {
      "type": "string",
      "label": "Province",
      "sanitizers": [{ "type": "trim" }, { "type": "collapseWhitespace" }],
      "transformers": [{ "type": "case", "mode": "upper" }, { "type": "map", "values": { "QC": "Québec" } }]
    }
  }
}
```

### Options raccourcies

| Option | Valeur | Fonctionne sur |
| --- | --- | --- |
| `trim` | `true` | Les champs de texte |
| `collapseWhitespace` | `true` | `string`, `textarea` |
| `normalizeEmail` | `true` | `email` |
| `caseTransform` | `upper` ou `lower` | `string`, `textarea`, `select` |

### Pipelines

`pipeline` exécute les nettoyages, les transformations et les validateurs dans une seule liste que vous ordonnez vous-même. Il ne se combine pas
avec `sanitizers`, `transformers`, `validators`, `trim`, `collapseWhitespace`, `normalizeEmail` ni `caseTransform`, échoue alors avec
`FORM_CONFIG_FIELD_OPTION_CONFLICT`, et exige au moins une étape. L’onglet **Traitement** de l’application affiche un pipeline sans permettre de
le modifier : préférez donc les deux listes ci-dessus.

```json
{
  "fields": {
    "code": {
      "type": "string",
      "label": "Code",
      "pipeline": [
        { "step": "sanitizer", "sanitizer": { "type": "trim" } },
        { "step": "validator", "validator": { "type": "startsWith", "value": "A", "code": "CODE_PREFIX", "message": "Start the code with A." } },
        { "step": "transformer", "transformer": { "type": "case", "mode": "lower" } }
      ]
    }
  }
}
```

## Validateurs

Ajoutez des vérifications à un champ de texte avec `validators`. Chacune exige un `type`, un `code` en majuscules comme `CUSTOM_POSTAL_CODE`, et
le `message` à afficher. Les réponses de validation renvoient votre `code` et votre `message`.

| `type` | Accepte aussi | Réussit quand la réponse est |
| --- | --- | --- |
| `email` | | Une adresse courriel |
| `url` | `allowedProtocols` | Une adresse Web |
| `domain` | | Un nom de domaine |
| `phoneE164` | `format` : `e164` | Un numéro comme `+15145550123` |
| `postalCode` | `country` : `CA` ou `US`, obligatoire | Un code postal ou ZIP |
| `regionCode` | `country` : `CA` ou `US`, obligatoire | Un code de province ou d’État |
| `countryCode` | `format` : `iso2` | Un code de pays à deux lettres |
| `uuid` | `version` : `any`, `v4` ou `v7` | Un UUID |
| `ip` | | Une adresse IPv4 ou IPv6 |
| `ipv4` | | Une adresse IPv4 |
| `ipv6` | | Une adresse IPv6 |
| `contains` | `value`, obligatoire | Contient le texte |
| `notContains` | `value`, obligatoire | Ne contient pas le texte |
| `startsWith` | `value`, obligatoire | Commence par le texte |
| `endsWith` | `value`, obligatoire | Se termine par le texte |
| `regex` | `pattern`, obligatoire, d’au plus 500 caractères. `flags` : `i`, `m`, `u` | Correspond à l’expression |

```json
{
  "fields": {
    "postal_code": {
      "type": "string",
      "label": "Postal code",
      "validators": [
        { "type": "postalCode", "country": "CA", "code": "CUSTOM_POSTAL_CODE", "message": "Enter a valid postal code." }
      ]
    },
    "sku": {
      "type": "string",
      "label": "Product code",
      "validators": [
        { "type": "regex", "pattern": "^[A-Z]{3}-[0-9]{4}$", "code": "CUSTOM_SKU", "message": "Use a code such as ABC-1234." }
      ]
    }
  }
}
```

## Conditions

Une condition est vraie ou fausse pour un ensemble de réponses. Elle sert aux règles de visibilité, aux règles de validation, aux étapes et aux
blocs de contenu.

| Opérateur | `value` | Vraie quand la réponse |
| --- | --- | --- |
| `isFilled` | Aucune | N’est pas vide |
| `isEmpty` | Aucune | Est vide |
| `equals` | N’importe laquelle | Est égale à la valeur |
| `notEquals` | N’importe laquelle | N’est pas égale à la valeur |
| `in` | Tableau | Est dans le tableau |
| `notIn` | Tableau | N’est pas dans le tableau |
| `greaterThan` | Nombre ou texte | Est plus grande |
| `greaterThanOrEqual` | Nombre ou texte | Est plus grande ou égale |
| `lessThan` | Nombre ou texte | Est plus petite |
| `lessThanOrEqual` | Nombre ou texte | Est plus petite ou égale |
| `matches` | Expression régulière | Correspond à l’expression |

Combinez les conditions avec `all`, `any` et `not`. Une condition s’imbrique sur au plus 5 niveaux, et un groupe contient au plus 25 conditions.
Un groupe vide échoue avec `FORM_CONFIG_CONDITION_REQUIRED`.

L’exemple suivant est complet. Il affiche `details` quand `type` vaut `Commercial` et que `size` dépasse 10, ou quand `type` n’est pas rempli.

```json
{
  "fields": {
    "type": { "type": "select", "label": "Type", "allowedValues": ["Personal", "Commercial"] },
    "size": { "type": "number", "label": "Team size" },
    "details": { "type": "textarea", "label": "Details" }
  },
  "visibilityRules": [
    {
      "id": "show-details",
      "path": "details",
      "default": "hidden",
      "state": "visible",
      "when": {
        "any": [
          { "all": [
            { "path": "type", "operator": "equals", "value": "Commercial" },
            { "path": "size", "operator": "greaterThan", "value": 10 }
          ] },
          { "not": { "path": "type", "operator": "isFilled" } }
        ]
      }
    }
  ]
}
```

### Conditions nommées

`conditions` conserve jusqu’à 100 conditions par nom, pour les réutiliser dans l’application. Chacune a un `id` et, au besoin, un `label` d’au plus
200 caractères, une `description` d’au plus 2 000 caractères et un `when`. Une règle garde sa propre copie de la logique : une condition nommée ne
change donc pas le comportement d’un formulaire.

```json
{
  "fields": {
    "type": { "type": "select", "label": "Type", "allowedValues": ["Personal", "Commercial"] }
  },
  "conditions": [
    {
      "id": "is-commercial",
      "label": "Commercial project",
      "when": { "path": "type", "operator": "equals", "value": "Commercial" }
    }
  ]
}
```

## Règles de visibilité

Une règle de visibilité affiche ou masque un champ ou un groupe. Quand `when` est vrai, le champ prend `state`. Sinon, il prend `default`, qui vaut
`visible` si vous l’omettez. Un formulaire peut avoir jusqu’à 100 règles, à raison d’une règle par champ.

Le serveur applique aussi ces règles. Les réponses à un champ masqué sont abandonnées, et ses vérifications ne s’exécutent pas.

```json
{
  "fields": {
    "meal": { "type": "select", "label": "Meal", "allowedValues": ["Standard", "Other"] },
    "meal_details": { "type": "string", "label": "Tell us more" }
  },
  "visibilityRules": [
    {
      "id": "show-meal-details",
      "path": "meal_details",
      "default": "hidden",
      "state": "visible",
      "when": { "path": "meal", "operator": "equals", "value": "Other" }
    }
  ]
}
```

## Règles de validation

Une règle de validation vérifie un champ seulement quand une condition est vraie. `then` contient jusqu’à 25 actions, chacune avec un `path` et un
`validator`. Un formulaire peut avoir jusqu’à 100 règles.

Le `validator` est n’importe quel type de la section [Validateurs](#validateurs), ou l’un de ceux-ci :

| `type` | Accepte aussi | Réussit quand |
| --- | --- | --- |
| `required` | | Le champ n’est pas vide |
| `allOf` | `validators` | Tous les validateurs de la liste réussissent |
| `anyOf` | `validators` | Au moins un validateur de la liste réussit |

Chacun exige un `code` et un `message`. Les listes `allOf` et `anyOf` contiennent jusqu’à 25 validateurs et s’imbriquent sur au plus 3 niveaux.

```json
{
  "fields": {
    "type": { "type": "select", "label": "Type", "allowedValues": ["Personal", "Commercial"] },
    "company": { "type": "string", "label": "Company" }
  },
  "validationRules": [
    {
      "id": "company-if-commercial",
      "when": { "path": "type", "operator": "equals", "value": "Commercial" },
      "then": [
        {
          "path": "company",
          "validator": { "type": "required", "code": "COMPANY_REQUIRED", "message": "Enter the company name." }
        }
      ]
    }
  ]
}
```

## Étapes

`steps` regroupe les champs en pages. À partir de deux, les pages hébergées et les intégrations par script en affichent une à la fois, avec Suivant,
Précédent et un compteur. Un formulaire peut avoir jusqu’à 50 étapes.

| Option | Valeur | Effet |
| --- | --- | --- |
| `id` | Chaîne unique | Nomme l’étape, y compris pour la validation d’étape |
| `fields` | De 1 à 100 chemins | Les champs de l’étape. Énumérez aussi les champs conditionnels |
| `label` | Chaîne | Le titre de l’étape |
| `summary` | Chaîne d’au plus 2 000 caractères | S’affiche sous le titre |
| `validateOn` | `stepSubmit`, `change` ou `blur` | `change` permet à Suivant de sauter la vérification sur le serveur. Les autres vérifient d’abord |
| `visibleWhen` | Condition | Masque l’étape et ignore ses champs, sauf si elle est vraie |

La soumission vérifie toujours tout le formulaire. Consultez [Sections et formulaires à étapes](/fr/guides/multi-step-forms/).

```json
{
  "fields": {
    "name": { "type": "string", "label": "Name", "required": true },
    "email": { "type": "email", "label": "Email", "required": true },
    "attending": { "type": "boolean", "label": "Will you attend?" },
    "meal": { "type": "select", "label": "Meal", "allowedValues": ["Standard", "Vegetarian"] }
  },
  "steps": [
    { "id": "about", "label": "About you", "summary": "How to reach you.", "fields": ["name", "email", "attending"], "validateOn": "stepSubmit" },
    {
      "id": "seat",
      "label": "Your seat",
      "fields": ["meal"],
      "visibleWhen": { "path": "attending", "operator": "equals", "value": true }
    }
  ]
}
```

## Blocs de contenu

Un bloc de contenu place du texte ou un séparateur parmi les champs. Il ne contient aucune réponse : il n’atteint donc jamais la validation, les
soumissions ni les courriels. Un formulaire peut en avoir jusqu’à 100.

| Option | Valeur | Effet |
| --- | --- | --- |
| `id` | Chaîne unique | Nomme le bloc |
| `type` | `text` ou `separator` | Un séparateur affiche une ligne, avec `title` comme libellé s’il est défini |
| `title` | Chaîne d’au plus 200 caractères | Un titre |
| `description`, `body` | Chaînes d’au plus 5 000 caractères | Des paragraphes, sur `text` |
| `before`, `after` | Chemin de champ | Son emplacement. Utilisez l’un des deux. Sans ni l’un ni l’autre, il termine le formulaire |
| `visibleWhen` | Condition | Affiche le bloc seulement quand elle est vraie |

```json
{
  "fields": {
    "name": { "type": "string", "label": "Name" },
    "seats": { "type": "number", "label": "Seats", "integer": true, "min": 1 }
  },
  "contentBlocks": [
    { "id": "intro", "type": "text", "title": "Seats are limited", "body": "Register early to save your spot.", "before": "name" },
    { "id": "divider", "type": "separator", "after": "name" },
    {
      "id": "group-note",
      "type": "text",
      "body": "We'll contact you about group rates.",
      "after": "seats",
      "visibleWhen": { "path": "seats", "operator": "greaterThan", "value": 5 }
    }
  ]
}
```

## Limites

`limits` accepte `duplicateWindowSeconds`, `rateLimits` et `quotas`. Toute autre clé est refusée : la requête échoue avec le statut HTTP 422 et `VALIDATION_FAILED`, avec un problème de code `FORM_CONFIG_UNKNOWN_KEY`. Consultez
[Limites et protection antipourriel](/fr/guides/spam-protection/) pour ce que fait chacune.

| Option | Valeur |
| --- | --- |
| `duplicateWindowSeconds` | Nombre entier. Toute valeur sous 60 agit comme 60 |
| `rateLimits[].id` | Chaîne unique |
| `rateLimits[].scope` | `ipHash` par visiteur, `form`, `workspace`, `origin`, `deviceToken` ou `idempotencyKey` |
| `rateLimits[].windowSeconds` | Nombre entier, de 1 à 604 800, soit 7 jours |
| `rateLimits[].max` | Nombre entier, de 1 à 100 000 |
| `rateLimits[].message` | Votre propre message |
| `quotas[].id` | Chaîne unique |
| `quotas[].scope` | `form` ou `workspace` |
| `quotas[].period` | `day`, `week`, `month` ou `lifetime` |
| `quotas[].maxAccepted` | Nombre entier, 1 ou plus |
| `quotas[].message` | Votre propre message |

```json
{
  "limits": {
    "duplicateWindowSeconds": 300,
    "rateLimits": [
      { "id": "per-visitor", "scope": "ipHash", "windowSeconds": 600, "max": 10 },
      { "id": "whole-form", "scope": "form", "windowSeconds": 3600, "max": 300 }
    ],
    "quotas": [
      { "id": "cap", "scope": "form", "period": "lifetime", "maxAccepted": 200, "message": "Registration is full." }
    ]
  }
}
```

Le code de réponse reste `RATE_LIMITED` ou `FORM_QUOTA_REACHED`, peu importe le contenu de `message`.

Avec le forfait gratuit, un formulaire accepte au plus 1 000 soumissions par heure, peu importe ses
`rateLimits`. Au-delà, il renvoie `RATE_LIMITED`.

Les dates d’ouverture et de fermeture ne font pas partie de `limits`. Réglez-les sous **Fenêtre de réponse**, dans l’onglet **Publication**.

## Vérifications antipourriel

| Option | Valeur | Effet |
| --- | --- | --- |
| `honeypotField` | Chemin de champ | Refuse une soumission dont ce champ est rempli. Les nouveaux formulaires utilisent `_formerie_hp`. Omettez-le pour désactiver la vérification |
| `minSubmitTimeMs` | Nombre entier, 0 ou plus | Refuse un formulaire envoyé plus vite que ce délai, quand le client envoie `metadata.startedAt`. `0` la désactive |
| `maxLinks` | Nombre entier, 0 ou plus | Refuse une soumission avec plus de liens que ce nombre, dans l’ensemble de ses réponses. `0` refuse tout lien. Omettez-le pour désactiver la vérification |

```json
{
  "spam": {
    "honeypotField": "_formerie_hp",
    "minSubmitTimeMs": 1000,
    "maxLinks": 3
  }
}
```

## Vérification humaine

| Option | Valeur | Effet |
| --- | --- | --- |
| `provider` | `turnstile`, `recaptcha-v2` ou `recaptcha-v3` | Le défi à afficher |
| `mode` | `managed` ou `custom` | `managed` utilise les clés de Formerie, sur `turnstile` seulement. Précisez-le toujours |
| `required` | `true` ou `false` | Si une soumission doit réussir le défi |
| `siteKey` | Clé de site publique | Nécessaire avec `custom` quand `required` vaut `true` |
| `minScore` | De 0 à 1 | Le score le plus bas accepté, sur `recaptcha-v3` seulement |
| `expectedAction` | Chaîne | L’action que le jeton doit porter, sur `recaptcha-v3` seulement |

```json
{
  "verification": {
    "provider": "turnstile",
    "mode": "managed",
    "required": true
  }
}
```

Avec vos propres clés, saisissez la clé secrète dans l’application, sous **Vérification humaine**, dans les **Paramètres** du formulaire. Elle est chiffrée,
et elle reste en place quand vous changez le reste de `verification` avec le même fournisseur. Tant qu’aucune clé n’est enregistrée, `required` doit rester à
`false`, comme dans cet exemple, sinon le changement échoue avec `FORM_CONFIG_VERIFICATION_SECRET_REQUIRED`.

```json
{
  "verification": {
    "provider": "recaptcha-v3",
    "mode": "custom",
    "required": false,
    "siteKey": "your-public-site-key",
    "minScore": 0.5,
    "expectedAction": "contact"
  }
}
```

## Origines autorisées

`allowedOrigins` énumère les sites dont le code de navigateur peut appeler directement le formulaire. Chaque entrée est une origine exacte, sans chemin ni
caractère générique. Consultez [Origines autorisées](/fr/guides/spam-protection/#origines-autorisées) pour savoir quand elles sont nécessaires.

```json
{
  "allowedOrigins": ["https://www.example.com", "https://example.com", "http://localhost:5173"]
}
```

## Adresse publique

`primaryPublicTarget` fixe l’adresse principale du formulaire.

| `kind` | Exige aussi | Adresse |
| --- | --- | --- |
| `workspace` | | Le chemin de l’espace de travail sur `forms.formerie.com` |
| `formerie` | | La même adresse Formerie |
| `runtimeHostname` | `hostname`, et `bindingKind` : `workspace` ou `form` | Votre propre nom d’hôte. Connectez-le d’abord sous **Domaines** |

```json
{
  "primaryPublicTarget": {
    "kind": "runtimeHostname",
    "hostname": "forms.example.com",
    "bindingKind": "workspace"
  }
}
```

## Comportement du formulaire

`settings.behavior` contient ce qui se passe autour des champs. `settings` se remplace au complet : envoyez donc `behavior`, `branding` et `layout` ensemble.

| Option | Valeur |
| --- | --- |
| `language` | Une balise de langue comme `en-CA` ou `fr-CA` |
| `submitButtonLabel` | Au plus 80 caractères |
| `successMessage` | Au plus 280 caractères |
| `successRedirectUrl` | Une adresse `http` ou `https`, d’au plus 500 caractères |

## Image de marque

`settings.branding` donne son style à la page hébergée et à l’intégration par script.

| Option | Valeur |
| --- | --- |
| `brandName` | Au plus 80 caractères |
| `logoUrl` | Une adresse `http` ou `https`, d’au plus 500 caractères |
| `density` | `compact`, `comfortable` ou `spacious` |
| `fontPreset` | `arial`, `georgia`, `helvetica`, `inter`, `mono`, `system`, `times` ou `verdana` |
| `previewTheme` | `light` ou `dark`. Les visiteurs obtiennent celui que vous choisissez |
| `radius` | `none`, `soft` ou `round` |
| `themes.light`, `themes.dark` | Des couleurs au format `#RRGGBB` |

Chaque thème accepte `accentColor`, `accentTextColor`, `backgroundColor`, `borderColor`, `errorColor`, `errorTextColor`, `fieldBackgroundColor`,
`mutedTextColor`, `surfaceColor` et `textColor`.

Le CSS personnalisé n’est pas appliqué aux pages hébergées.

## Mise en page

`settings.layout` fixe la largeur de la page hébergée.

| Option | Valeur |
| --- | --- |
| `containerWidthMode` | `contained`, `custom` ou `full` |
| `containerMinWidth`, `containerMaxWidth` | Une longueur comme `44rem`, `720px`, `90%` ou `72ch`, avec `custom` |

```json
{
  "settings": {
    "behavior": {
      "language": "fr-CA",
      "submitButtonLabel": "Envoyer",
      "successMessage": "Merci, nous vous répondrons sous peu."
    },
    "branding": {
      "brandName": "Example",
      "density": "comfortable",
      "fontPreset": "inter",
      "previewTheme": "light",
      "radius": "soft",
      "themes": { "light": { "accentColor": "#007c72" } }
    },
    "layout": { "containerWidthMode": "custom", "containerMaxWidth": "44rem" }
  }
}
```

## Limites de taille

| Élément | Limite |
| --- | --- |
| Champs d’un formulaire, y compris ceux des groupes et des listes | 250 |
| Imbrication des groupes et des listes | 8 niveaux |
| Étapes | 50 |
| Chemins dans une étape | 100 |
| Résumé d’étape | 2 000 caractères |
| Blocs de contenu | 100 |
| Titre d’un bloc de contenu | 200 caractères |
| Description ou corps d’un bloc de contenu | 5 000 caractères chacun |
| Conditions nommées | 100 |
| Libellé, description d’une condition nommée | 200, 2 000 caractères |
| Règles de visibilité | 100 |
| Règles de validation | 100 |
| Actions dans une règle de validation | 25 |
| Conditions dans une règle | 25 |
| Imbrication des conditions | 5 niveaux |
| Validateurs dans un `allOf` ou un `anyOf` | 25, sur au plus 3 niveaux |
| Nettoyages, transformations ou étapes de pipeline sur un champ | 25 de chaque sorte |
| Entrées d’une transformation `map` | 100 |
| Expression régulière | 500 caractères |
| Corps JSON d’une soumission | 100 000 octets |

## Lire les erreurs de définition

Une définition refusée renvoie `VALIDATION_FAILED` avec `issues`, et chaque problème a un `path`, un `code` stable et un `message`. Corrigez ces chemins
et envoyez de nouveau le correctif.

| Code | Ce qui ne va pas |
| --- | --- |
| `FORM_CONFIG_UNKNOWN_KEY` | Une clé n’est pas un réglage à cet endroit. `suggestion` nomme la bonne clé quand elle est évidente |
| `FORM_CONFIG_RATE_LIMIT_TOO_HIGH` | Une fenêtre ou un `max` de `rateLimits` dépasse sa borne supérieure |
| `FORM_CONFIG_FIELD_TYPE_INVALID` | `type` n’est pas l’un des douze types de champs |
| `FORM_CONFIG_FIELD_OPTION_INVALID` | Cette option ne fonctionne pas sur ce type de champ |
| `FORM_CONFIG_FIELD_OPTION_CONFLICT` | Deux options ne peuvent pas être utilisées ensemble |
| `FORM_CONFIG_FIELD_REF_UNKNOWN` | Un chemin désigne un champ qui n’existe pas |
| `FORM_CONFIG_FIELDS_REQUIRED` | Le formulaire n’a aucun champ |
| `FORM_CONFIG_NUMBER_TOO_SMALL` | Un nombre est sous son minimum |
| `FORM_CONFIG_RANGE_INVALID` | Un minimum dépasse son maximum |
| `FORM_CONFIG_DEFAULT_VALUE_INVALID` | `defaultValue` ne convient pas au champ |
| `FORM_CONFIG_DEFAULT_VALUE_NOT_ALLOWED` | `defaultValue` n’est pas dans `allowedValues` |
| `FORM_CONFIG_CODE_INVALID` | Un `code` n’est pas composé de majuscules, de chiffres et de `_` |
| `FORM_CONFIG_VALIDATOR_INVALID` | Le `type` de validateur n’existe pas |
| `FORM_CONFIG_CONDITION_OPERATOR_INVALID` | L’`operator` de la condition n’existe pas |
| `FORM_CONFIG_ID_DUPLICATE` | Deux entrées d’une liste ont le même `id` |
| `FORM_CONFIG_ORIGIN_WILDCARD` | Une origine autorisée utilise `*` |
| `FORM_CONFIG_ORIGIN_INVALID` | Une origine autorisée a un chemin, ou n’est pas en `http` ou `https` |

### Bloqué à la publication

Un formulaire qui ne peut pas fonctionner s’enregistre comme brouillon, mais ne peut pas être
publié. Ces codes s’affichent comme des blocages dans l’onglet **Publication** :

| Code | Ce qui ne va pas |
| --- | --- |
| `FORM_CONFIG_CHOICES_REQUIRED` | Une question n’a aucun choix, donc personne ne peut y répondre. Énumérez-les dans `allowedValues` |
| `FORM_CONFIG_CHOICE_BLANK` | Un des choix est vide |
| `FORM_CONFIG_CHOICE_DUPLICATE` | Un choix figure deux fois |
| `FORM_CONFIG_STEP_FIELD_DUPLICATE` | Un champ se trouve dans plus d’une étape |

### Avertissements

`FORM_DEFINITION_UNKNOWN_KEYS` est un avertissement sous **État de préparation de la livraison**, et n’arrête
rien. Le formulaire contient des réglages qui ne font rien, souvent d’avant que ces clés soient
refusées. Enregistrez de nouveau cette section sans elles.
