---
title: "Schéma des collections · Documentation Leiho"
description: "Le format de leiho.schema.json."
url: https://leiho.dev/fr/docs/collections-schema
lang: fr
updated: 2026-11-04
---
Référence

# Schéma des collections

Le format de leiho.schema.json.

Leiho Collections permet à un site de définir son modèle de contenu dans Leiho même, sans ACF : « des collections à la Strapi / Webflow dans WordPress ». Un site n’a besoin que de WordPress et de Leiho, rien d’autre. ACF continue de fonctionner via son adaptateur ; les deux peuvent cohabiter.

La phase 1 couvre le moteur de stockage, le format du schéma, les migrations, les révisions, l’API REST d’administration et l’adaptateur qui branche Collections sur l’éditeur visuel et l’API de contenu. La phase 2 est l’interface de construction du schéma, la phase 3 l’import ACF → Collections (§9) ; toutes deux s’appuient sur l’API décrite ici.

## 1. Concepts

|  |  |
| --- | --- |
| **Collection** | Un type de contenu avec de nombreuses entrées (Services, Articles, Membres de l’équipe). |
| **Page unique** | Un type de contenu avec exactement une entrée (Accueil, Contact, Réglages du site). Leiho crée l’entrée ; elle ne peut être ni dupliquée ni supprimée. |
| **Entrée** | Une ligne de `wp_posts` (type de publication = l’id de la collection) plus une ligne dans la table des champs de la collection. |
| **Champ** | Une valeur typée d’une entrée, identifiée par une `key` stable et exposée sous son `name`. |

## Stockage hybride

Chaque entrée est une vraie publication WordPress : utilisateurs et rôles, brouillons, planification, corbeille, révisions, médiathèque, extensions SEO et multilingues continuent de fonctionner. Titre, slug, statut, dates, auteur, `post_content` (quand `editor` est pris en charge), extrait et image mise en avant restent dans `wp_posts` / `wp_postmeta` comme d’habitude.

Les valeurs des champs ne vont **pas** dans les postmeta. Chaque collection possède sa propre table, `{$wpdb->prefix}leiho_<collection id>`, avec une ligne par entrée indexée par `post_id`. Les champs scalaires ont une colonne typée et indexée ; les valeurs imbriquées sont stockées sous forme de texte JSON. Les requêtes restent ainsi peu coûteuses (une lecture par clé primaire par entrée au lieu de dizaines de lignes de méta), les valeurs peuvent être triées et filtrées en SQL, et `wp_postmeta` reste léger.

## 2. Format du schéma

Le schéma est stocké dans l’option `leiho_collections` et peut être exporté vers / importé depuis un fichier, `leiho.schema.json`, destiné à être versionné dans le dépôt du front (le schéma comme du code).

```json
{
  "leiho": "collections",
  "version": 1,
  "collections": [
    {
      "id": "home",
      "kind": "singleton",
      "label": "Home",
      "path": "/",
      "fields": [
        { "key": "hero_title", "name": "hero_title", "label": "Hero title", "type": "text", "required": true, "max_length": 80 },
        { "key": "sections", "name": "sections", "label": "Sections", "type": "blocks", "blocks": [
          { "key": "hero", "name": "hero", "label": "Hero", "fields": [
            { "key": "image", "name": "image", "label": "Image", "type": "image" }
          ] },
          { "key": "cards", "name": "cards", "label": "Cards", "max": 3, "fields": [
            { "key": "items", "name": "items", "label": "Cards", "type": "list", "max": 6, "fields": [
              { "key": "title", "name": "title", "label": "Title", "type": "text" },
              { "key": "service", "name": "service", "label": "Service", "type": "reference", "to": ["service"] }
            ] }
          ] }
        ] }
      ]
    },
    {
      "id": "service",
      "label": "Services",
      "singular_label": "Service",
      "path": "/services/{slug}",
      "supports": ["thumbnail", "excerpt"],
      "fields": [
        { "key": "price", "name": "price", "label": "Price", "type": "number", "min": 0 },
        { "key": "featured", "name": "featured", "label": "Featured", "type": "boolean" },
        { "key": "tone", "name": "tone", "label": "Tone", "type": "select", "choices": [
          { "value": "light", "label": "Light" }, { "value": "dark", "label": "Dark" }
        ] },
        { "key": "related", "name": "related", "label": "Related", "type": "reference", "to": ["service"], "multiple": true, "max": 3 }
      ]
    }
  ]
}
```

## Collection

| Propriété |  |  |
| --- | --- | --- |
| `id` | obligatoire | Nom du type de publication et suffixe de la table. `^[a-z][a-z0-9_]{0,19}$`, pas un type de publication réservé de WordPress (`post`, `page`, `attachment`…), ni `export`/`import`/`activity` (la table propre à Leiho). Ne change jamais. |
| `kind` | `collection` (par défaut) \| `singleton` |  |
| `label`, `singular_label` |  | Libellés d’administration. `singular_label` vaut `label` par défaut. |
| `path` |  | Modèle d’URL côté front (`/services/{slug}`, `{id}` fonctionne aussi). Utilisé sauf si les réglages de Leiho en définissent un. |
| `supports` |  | Fonctionnalités de publication supplémentaires : `editor`, `excerpt`, `thumbnail`, `page-attributes`. `title` et `revisions` sont toujours actifs. |
| `icon` |  | Un nom `dashicons-*` pour le menu d’administration. |
| `external` | bool | Le type de publication est enregistré hors de Leiho (thème, extension, ACF) : Leiho ne l’enregistre pas, ne le refuse pas parce qu’il existe déjà, et ne l’ajoute pas aux types servis par l’API de contenu (ce sont les réglages de Leiho qui décident, comme avant). Leiho ne gère que ses champs. Jamais une page unique. Défini par l’import ACF. |
| `source` | `""` \| `acf` | `acf` : importé depuis ACF. Les noms des champs et des blocs gardent alors leur nom d’API ACF (`^[A-Za-z_][A-Za-z0-9_-]{0,63}$`, camelCase et tirets compris) pour que le front reçoive le même JSON ; les clés restent `^[a-z][a-z0-9_]{0,47}$`. |
| `fields` |  | Champs, 200 au maximum au premier niveau. |
| `seo` | objet | Facultatif. `type` (schema.org), `title`, `description`, `image`, `faq` (noms de champs racine) et `llms`. Voir [SEO et recherche IA](https://leiho.dev/fr/docs/seo-and-ai-search). |

## Champ

| Propriété |  |  |
| --- | --- | --- |
| `key` | id stable | `^[a-z][a-z0-9_]{0,47}$`, unique parmi les champs frères. Ne la changez jamais : elle nomme la colonne et les clés JSON. Vaut `name` si elle est omise (à la création uniquement). |
| `name` | nom d’API | `^[a-z][a-z0-9_]{0,63}$` (snake_case ; voir `source` pour les collections importées), unique parmi les champs frères. Renommer = changer `name` en gardant `key`. |
| `label` |  | Vaut `name` par défaut. |
| `type` |  | Voir ci-dessous. |
| `required` | bool | Un champ obligatoire ne peut pas être vidé (vérifié à chaque écriture, quel que soit le client). |
| `max_length` | int | Champs de type texte. Appliqué à l’écriture. |
| `help` | string | Instructions affichées aux rédacteurs. |

| `type` | Options | Valeur servie par l’API de contenu | Colonne |
| --- | --- | --- | --- |
| `text` | `multiline`, `max_length` | `string` | `VARCHAR(191)` indexée quand `0 < max_length <= 191`, sinon `TEXT` |
| `richtext` |  | `string` HTML rendu | `LONGTEXT` |
| `code` | `max_length` | `string`, exactement tel que saisi (balises comprises) : affichez-le comme du texte, jamais comme du HTML | `TEXT` |
| `number` | `integer`, `min`, `max`, `step` | `number \\| null` (un nombre entier est servi sous la forme `5`, jamais `5.0`) | `BIGINT` si `integer`, sinon `DECIMAL(20,6)` ; indexée |
| `boolean` |  | `boolean` | `TINYINT(1)` indexée |
| `date` |  | `"YYYY-MM-DD"` | `DATE` indexée |
| `datetime` |  | `"YYYY-MM-DD HH:MM:SS"` (heure du site) | `DATETIME` indexée |
| `select` | `choices` (`[{value,label}]`), `multiple` | `string` / `string[]` | `VARCHAR(191)` indexée / JSON |
| `slug` | `max_length` | `string` (assaini comme un slug de publication) | `VARCHAR(191)` indexée |
| `email` |  | `string` | `VARCHAR(191)` indexée |
| `url` |  | `string` | `TEXT` |
| `color` |  | `string` (`#rgb[a]`, `#rrggbb[aa]`, `rgb[a]()`) | `VARCHAR(32)` |
| `image`, `file` |  | `LeihoMedia \\| null` | `BIGINT UNSIGNED` indexée |
| `gallery` | `max` | `LeihoMedia[]` | JSON |
| `link` |  | `{ url, title, target } \\| null` | JSON |
| `reference` | `to` (ids de collections ou types de publication), `multiple`, `max` | `LeihoRef \\| null` / `LeihoRef[]` | `BIGINT UNSIGNED` indexée / JSON |
| `list` | `fields`, `min`, `max` | `Array<{…}>` | JSON |
| `blocks` | `blocks` (`[{key,name,label,fields,min,max}]`), `min`, `max` | `Array<{ _layout: name, … }>` | JSON |
| `group` | `fields` | `{…}` | JSON |

Les colonnes JSON sont des `LONGTEXT` contenant du texte JSON : aucune fonction SQL JSON n’est utilisée, si bien que MySQL 5.7, MariaDB et le drop-in SQLite se comportent de la même façon. Dans le JSON, les objets sont indexés par la **clé** du champ (renommer un champ imbriqué ne coûte rien) et les lignes de blocs portent `"_block": "<block key>"`. Les valeurs vides sont stockées en `NULL`.

L’imbrication est limitée à 8 niveaux ; les colonnes `VARCHAR` de premier niveau à 60 par collection (taille de ligne InnoDB).

## 3. Tables

```sql
CREATE TABLE wp_leiho_service (
  post_id BIGINT(20) UNSIGNED NOT NULL,
  f_price DECIMAL(20,6) NULL DEFAULT NULL,
  f_featured TINYINT(1) NULL DEFAULT NULL,
  f_tone VARCHAR(191) NULL DEFAULT NULL,
  f_related LONGTEXT NULL,
  PRIMARY KEY (post_id),
  KEY k_f_price (f_price), KEY k_f_featured (f_featured), KEY k_f_tone (f_tone)
) {charset_collate};
```

Les colonnes s’appellent `f_<key>` (le préfixe évite les mots réservés). Les identifiants ne sont construits qu’à partir d’ids et de clés validés, et revérifiés avec `^[A-Za-z0-9_]{1,64}$` avant tout DDL ; les valeurs passent toujours par `$wpdb->prepare()` / `$wpdb->insert()` / `$wpdb->update()`.

Ce que la base de données contient réellement est consigné dans l’option `leiho_collections_state` : pour chaque collection, sa table, et pour chaque clé de champ la colonne, son type SQL et la définition de champ pour laquelle elle a été créée, plus la liste des colonnes archivées. Les migrations comparent le schéma demandé à cet état, jamais au schéma précédent.

## 4. Migrations

Enregistrer un schéma (REST, import ou PHP) le valide, calcule un plan, l’applique, puis stocke le schéma. `dry_run` renvoie le plan sans rien toucher.

| Changement | Opération |
| --- | --- |
| Nouvelle collection | `CREATE TABLE IF NOT EXISTS` ; une page unique reçoit son entrée. |
| Nouveau champ | `ADD COLUMN` (+ index). S’il existe une colonne archivée avec la même clé et le même type SQL, elle est restaurée à la place : supprimer un champ par erreur puis le rajouter ne fait rien perdre. |
| Champ renommé (même `key`, nouveau `name`) | Rien en base de données. |
| Options modifiées avec le même type de colonne (libellé, choix, `max`…) | Rien en base de données. |
| Type modifié, ou changement de type de colonne (`max_length` 100 → 500, référence simple → multiple…) | Nouvelle colonne `f_<key>__<n>`, valeurs converties ligne par ligne en PHP, ancienne colonne **archivée** (conservée, signalée). Les valeurs non convertibles sont comptées dans le rapport et restent dans la colonne archivée. |
| Champ supprimé | Colonne **archivée**, jamais supprimée. |
| Collection supprimée | Désenregistrée ; table, entrées et état conservés. La rajouter restaure tout. |
| Changement imbriqué (dans une liste / des blocs / un groupe) | Rien en base de données : le JSON est indexé par clé. Un champ imbriqué supprimé disparaît du JSON stocké au prochain enregistrement de ce conteneur (les révisions gardent les valeurs précédentes). |

Conversions : tout scalaire vers du texte ; texte numérique vers nombre ; texte vers booléen (`"", "0", "false", "no"` → false) ; texte interprétable vers date/datetime ; une valeur simple vers une liste (`id` → `[id]`) et le premier élément d’une liste vers une valeur simple ; tout le reste n’est pas converti.

Les colonnes archivées sont listées dans `GET /leiho/v1/collections/{id}` et ne sont supprimées que par un `POST /leiho/v1/collections/{id}/purge` explicite avec `confirm` égal à l’id de la collection.

Le plan est idempotent : l’état est mis à jour après chaque opération réussie, si bien qu’une migration interrompue par une erreur de base de données reprend en enregistrant à nouveau. Le SQL utilise `CREATE TABLE`, `ALTER TABLE … ADD COLUMN`, `CREATE INDEX`, `DROP INDEX` + `ALTER TABLE … DROP COLUMN` (purge uniquement) : tous pris en charge par MySQL 5.7+, MariaDB 10.3+ et le drop-in SQLite. Pas de `ALTER … MODIFY` / `RENAME COLUMN`.

## 5. Révisions

Les valeurs des entrées suivent les révisions WordPress :

- Quand WordPress stocke une révision d’une entrée (`_wp_put_post_revision`), un instantané de la ligne de l’entrée est enregistré dans la méta `_leiho_fields` de la révision : `{ "v": 1, "fields": { "<key>": { "type": "…", "value": … } } }` (indexé par clé de champ, si bien qu’un instantané survit aux renommages et aux changements de type).
- `wp_save_post_revision_post_has_changed` compare aussi la ligne au dernier instantané, si bien qu’un changement portant uniquement sur les champs crée une révision. Enregistrer des champs via Leiho demande une révision à WordPress après l’écriture.
- `wp_restore_post_revision` réécrit l’instantané (en convertissant les valeurs dont le type de champ a changé depuis, en ignorant les champs qui ne sont plus dans le schéma), puis stocke une nouvelle révision de l’état restauré.
- Les entrées supprimées définitivement perdent leur ligne ; les entrées mises à la corbeille la gardent.

## 6. Surface d’API

## PHP

```php
Leiho_Collections::schema();                         // normalized schema
Leiho_Collections::get( 'service' );                 // one collection or null
Leiho_Collections::save_schema( $schema, $dry_run ); // validate + migrate + store → report | WP_Error
Leiho_Collections::put_collection( $collection, $dry_run );
Leiho_Collections::delete_collection( 'service' );
Leiho_Collections::export();                         // array, the leiho.schema.json content
Leiho_Collections::import( $json_or_array, $dry_run );
Leiho_Collections::entry( 'home' );                  // singleton post id
Leiho_Collections::save_entry( $post_id, array( 'price' => 120, 'sections.0.image' => 12 ) ); // { saved, errors }
```

Un rapport de migration : `{ dry_run, operations: [{ op, collection, key?, column?, … }], converted: { "<collection>.<key>": { converted, failed } }, warnings: [] }`.

## REST (`leiho/v1`, `manage_options`, filtre `leiho_collections_capability`)

| Route |  |
| --- | --- |
| `GET /collections` | `{ version, collections, state }` |
| `POST /collections` | Crée une collection (corps : la collection). `?dry_run=1`. |
| `GET /collections/{id}` | Collection + table + colonnes archivées. |
| `PUT /collections/{id}` | Remplace la définition d’une collection, migre. `?dry_run=1`. |
| `DELETE /collections/{id}` | Retire du schéma ; données conservées. |
| `POST /collections/{id}/purge` | Supprime les colonnes archivées (`confirm` = id). |
| `GET /collections/export` | `leiho.schema.json` (pièce jointe). |
| `POST /collections/import` | Remplace tout le schéma par le contenu d’un fichier. `?dry_run=1`. |

| `GET /collections/import-acf` | Types de contenu ayant des champs ACF et leur état d’import ; `elsewhere` : groupes de champs rattachés à aucun type de contenu. §9. |
| `GET /collections/import-acf/preview?post_type=` | Aperçu d’un import. |
| `POST /collections/import-acf/run` | `{ post_type }` : lance l’import, puis copie un lot par appel. |
| `GET /collections/import-acf/status?post_type=` | Enregistrement de l’import. |
| `POST /collections/import-acf/rollback` | `{ post_type, confirm }` (confirm = type de publication) : retour à ACF. |

`DELETE /collections/{id}` refuse une collection importée (`409`) : revenez plutôt à ACF.

Les schémas invalides renvoient `400` avec `data.errors` : une liste de `{ path, message }` (`collections.1.fields.3.key` ; relatif à la collection, `fields.3.key`, sur les routes portant sur une seule collection).

## Contenu et éditeur

Les routes existantes incluent désormais Collections : `/leiho/v1/export`, `/content`, `/types` servent tous les types de publication des collections (ajoutés à `leiho_content_post_types` via les réglages), et `/leiho/v1/schema/{id}` + `/leiho/v1/save/{id}` modifient les entrées avec les mêmes chemins pointés qu’ACF (`sections.2.items.1.title`).

## 7. Adaptateur

`Leiho_Native_Adapter` (id `collections`) implémente `Leiho_Adapter_Interface` et `Leiho_Content_Adapter_Interface`. Il traduit chaque champ Collections en tableau de champ au format ACF, que le code existant comprend déjà, et réutilise le moteur de champs commun (`Leiho_Field_Adapter`, extrait de l’adaptateur ACF) pour décrire, lire, modifier par chemins pointés, assainir et mettre en forme les valeurs publiques. L’éditeur visuel, le journal des modifications, l’API de contenu et `Leiho_Types` fonctionnent donc sans changement.

| Collections | Type du moteur de champs |
| --- | --- |
| `text` / `text` + `multiline` | `text` / `textarea` |
| `richtext` | `wysiwyg` |
| `boolean` | `true_false` |
| `date` / `datetime` | `date_picker` / `date_time_picker` (stockés en `Y-m-d` / `Y-m-d H:i:s`) |
| `color` | `color_picker` |
| `slug` | `text` (assaini avec `sanitize_title`) |
| `reference` / + `multiple` | `post_object` / `relationship` (types de publication limités à `to`) |
| `list` / `blocks` / `group` | `repeater` / `flexible_content` / `group` |
| autres | même nom |

L’assainissement reprend celui de l’adaptateur ACF (obligatoire, `max_length`, les pièces jointes et les références doivent exister et être lisibles par l’utilisateur courant) et ajoute : les références doivent appartenir aux types de publication de `to`, les nombres doivent respecter `integer`/`min`/`max` et la plage de la colonne, les sélections multiples n’acceptent que les choix déclarés.

## 8. Limites et prochaines phases

- L’écran d’édition classique de wp-admin n’affiche que les champs de la publication ; les champs de collection se modifient dans l’éditeur Leiho (ou sa vue formulaire, pour les collections sans `path`) ou via les API.
- Phase 2 (terminée) : le constructeur de schéma dans Leiho → Collections, une interface au-dessus des routes REST ci-dessus. Les nouveaux champs reçoivent `key = name` (rendu unique parmi les champs frères) lors de leur première application ; le constructeur ne change jamais la clé d’un champ enregistré.
- Phase 3 (terminée) : import ACF → Collections, §9.
- Cache par requête uniquement ; l’export lit une ligne par entrée (lectures par clé primaire).
- Les valeurs `datetime` sont stockées telles que saisies (heure du site), comme dans ACF.
- Désinstaller Leiho supprime les tables et options des collections (définissez `LEIHO_KEEP_DATA` pour les conserver) ; les entrées restent dans `wp_posts`.

## 9. Import ACF

`Leiho_ACF_Import` déplace les champs ACF d’un type de contenu vers une collection Leiho. Rien n’est supprimé d’ACF (postmeta, groupes de champs, types de publication ACF) : l’import est réversible.

## Aperçu

Les groupes de champs rattachés au type de publication sont les groupes actifs dont les règles d’emplacement le désignent (`Leiho_ACF_Adapter::targets()`), dans l’ordre d’ACF. Un groupe dont les règles ajoutent d’autres conditions (modèle de page, taxonomie…) est importé lui aussi, et signalé : ses champs deviennent disponibles sur chaque entrée. Chaque champ ACF devient :

| ACF | Leiho | Remarques |
| --- | --- | --- |
| `text`, `textarea` | `text` (`multiline` pour une zone de texte) | `maxlength` → `max_length` |
| `wysiwyg` | `richtext` | Rendu comme dans ACF (`acf_the_content`), même une fois ACF désactivé |
| `number`, `range` | `number` | `min`, `max`, `step` |
| `email`, `url` | identique |  |
| `oembed` | `url` | L’URL, telle qu’ACF la servait |
| `image`, `file`, `gallery` | identique | Restrictions de médias (taille, dimensions, types) abandonnées, signalées |
| `select` / `checkbox` / `radio`, `button_group` | `select` / `select` + `multiple` / `select` | Choix conservés dans l’ordre ; les valeurs hors de ces choix sont refusées (signalées) |
| `true_false` | `boolean` |  |
| `link` | `link` |  |
| `post_object` / `relationship` | `reference` (+ `multiple`) | `to` = les types de publication d’ACF, ou tous les types modifiables quand ACF n’en définit aucun ; filtres de taxonomie et bidirectionnalité abandonnés |
| `date_picker` / `date_time_picker` | `date` / `datetime` | `Ymd` → `Y-m-d` |
| `time_picker` | `text` | `HH:MM:SS` |
| `color_picker` | `color` |  |
| `repeater` / `group` / `flexible_content` | `list` / `group` / `blocks` | Récursivement ; les layouts deviennent des types de blocs du même nom |
| `clone` | ses champs / `group` | Un clone transparent est déjà remplacé par les champs clonés dans les définitions d’ACF ; un clone affiché comme un groupe devient un groupe (même JSON). Un clone sans rien à cloner est abandonné |
| `tab`, `message`, `accordion` | — | Aucune valeur : ignorés |
| `page_link`, `taxonomy`, `user`, `password`, `google_map`, autres | — | Non repris : leurs valeurs disparaissent de l’API de contenu (elles restent dans ACF) |

Les clés sont dérivées des noms (`heroTitle` → `herotitle`, uniques parmi les champs frères) ; les noms sont conservés (`source: "acf"`). La logique conditionnelle et les valeurs par défaut sont abandonnées et signalées. La collection générée est `external: true` et validée comme n’importe quel schéma avant toute écriture ; un type de publication qui ne peut pas servir d’id de collection (`post`, `page`, tirets, plus de 20 caractères) n’est pas importable, avec la raison : les champs Leiho appartiennent au type de publication propre à une collection, il n’existe pas de « champs Leiho sur les pages ».

## Exécution

`start()` crée la collection (`put_collection()`) et l’enregistrement d’import, dans l’option `leiho_acf_import` (type de publication → `{ status, origin, args, total, done, cursor, imported, failed, different, failures, diffs, … }`). Chaque `step()` copie le lot d’entrées suivant (tous statuts sauf `auto-draft` et `inherit`, par id croissant à partir de `cursor`) : il réinitialise la ligne de l’entrée, lit chaque valeur ACF brute (`get_field( key, id, false )`, valeurs par défaut d’ACF comprises), la convertit au format d’entrée de l’éditeur (lignes indexées par nom, `_layout` pour les blocs, dates `Ymd`, médias et publications disparus retirés) et l’écrit avec `Leiho_Collections::save_entry()`, c’est-à-dire avec la validation de l’éditeur, sans révision par entrée. Un champ refusé est consigné (`post_id`, `field`, `code`, `message`) et le reste de l’entrée et de l’import se poursuit. Ensuite, la valeur servie par Leiho (`Leiho_Native_Adapter::get_content()`) est comparée, champ par champ, à celle qu’ACF servait avant la copie (JSON canonique : `5` et `5.0` sont égaux) ; toute différence est consignée. Les 200 premiers échecs et différences sont conservés ; les compteurs sont exacts.

Tant que l’enregistrement est `running`, ACF sert toujours le type (l’adaptateur natif ne le sert qu’à l’import lui-même). Une fois le dernier lot terminé, l’enregistrement passe à `done` et le type est servi par Leiho : `Leiho_ACF_Adapter` l’ignore (`retired()`), et les règles d’emplacement d’ACF ne le ciblent plus (`acf/location/rule_match`), si bien que l’écran d’édition classique n’affiche plus les groupes ACF. Si la collection disparaît ensuite du schéma (un fichier de schéma importé sans elle), ACF sert de nouveau le type.

## Enregistrement du type de publication

`origin` vaut `acf` (déclaré dans l’écran Types de publication d’ACF), `external` (thème, extension, mu-plugin) ou `builtin`. Pour `acf`, les arguments avec lesquels ACF enregistre le type (`ACF_Post_Type::get_post_type_args()`) sont stockés dans l’enregistrement, et `Leiho_ACF_Import::register_post_types()` (`init`, priorité 6, après ACF) enregistre le type avec eux quand rien d’autre ne l’a fait, c’est-à-dire une fois ACF désactivé. Les taxonomies déclarées dans ACF ne sont pas reprises. Pour `external`, le thème ou l’extension continue de l’enregistrer.

## Retour arrière

`rollback()` (ACF doit être actif) retire la collection du schéma, supprime sa table et son état (`Leiho_Collections_Store::drop()`) et efface l’enregistrement : ACF sert de nouveau les valeurs d’origine. Les valeurs modifiées dans Leiho depuis l’import ne sont pas réécrites dans ACF ; elles sont perdues avec la table.

## Compatibilité du front

Pour les champs importés, `/export`, `/content` et `/types` renvoient le même JSON qu’avant : mêmes noms et même ordre, objets média, références (cibles non publiées omises), lignes de répéteur, blocs avec `_layout` en premier, texte riche rendu, valeurs de select / checkbox sous forme de chaînes, dates `Y-m-d`, nombres (entiers en tant qu’entiers), booléens, `null` / `""` / `[]` pour les valeurs vides. Différences connues, toutes signalées par l’aperçu ou l’import : les champs non repris disparaissent ; les valeurs que la validation de l’éditeur refuse (choix inconnu, texte au-delà de `max_length`, sous-champ obligatoire manquant…) restent vides ; les valeurs qu’elle réécrit (balises ou retours à la ligne dans un texte sur une ligne, HTML dangereux dans du texte riche) sont listées comme différences ; dans `/types`, les champs des groupes ayant des règles d’emplacement supplémentaires ne sont plus optionnels ; les nombres de plus de 6 décimales sont arrondis par `DECIMAL(20,6)`. L’éditeur affiche un seul groupe (la collection) au lieu d’un par groupe ACF ; les chemins pointés (`sections.2.cartes.1.titre`) ne changent pas.

Mis à jour le 4 novembre 2026[Un point pas clair sur cette page ? Dites-le-nous →](https://github.com/pixelersagency/leiho/issues/new?title=Docs%3A%20Sch%C3%A9ma%20des%20collections)
