Aller au contenu

Document Service de Strapi 5 : brouillons, locales et requêtes

Découvrez comment le Document Service de Strapi 5 remplace l'Entity Service, prend en charge documentId et gère brouillons, publication et contenus traduits.

Par l’équipe Revisionary · · 7 min de lecture

low angle photography of gray building at daytime
Photo de Anders Jildén sur Unsplash

L'API Document Service de Strapi 5 remplace l'ancien Entity Service pour offrir une approche unifiée et centrée sur les documents lors de l'interrogation et de la manipulation du contenu. Au lieu de gérer des lignes distinctes en base de données pour les versions brouillon, publiée et traduite d'un enregistrement, vous interagissez désormais avec un unique documentId qui encapsule tous les états et toutes les locales d'une entrée. Comprendre comment requêter, modifier, publier et filtrer via strapi.documents est indispensable pour développer des controllers personnalisés, des services et des lifecycles back-end dans Strapi 5.

Pourquoi Strapi 5 a remplacé l'Entity Service

Dans Strapi 4, les entrées de contenu reposaient strictement sur des identifiants numériques auto-incrémentés en base de données. Lorsque la fonctionnalité Draft & Publish était activée, Strapi stockait l'état brouillon et l'état publié sous forme de lignes distinctes au sein de la même table, en les reliant en coulisses. L'ajout du plugin d'internationalisation (i18n) complexifiait encore ce modèle : chaque locale générait une nouvelle ligne avec son propre ID, reliée par des tables de jointure relationnelles.

Cette architecture posait des problèmes récurrents aux développeurs :

  • Requêter un enregistrement par son ID pouvait renvoyer un brouillon ou une entrée publiée selon des flags internes.
  • La gestion des relations entre plusieurs locales entraînait souvent des liens rompus ou des mises à jour en cascade inattendues.
  • Les intégrations tierces et les applications front-end devaient suivre plusieurs ID pour ce que les éditeurs considéraient comme un seul et même contenu.

Strapi 5 résout ce problème grâce au Document Service. Un document représente un contenu unique à travers l'ensemble de ses variantes. Chaque document se voit attribuer un identifiant textuel persistant et unique appelé documentId. L'id numérique existe toujours au niveau de la ligne de base de données pour les requêtes SQL brutes, mais le code de votre application, vos controllers personnalisés et vos API de contenu manipulent désormais le documentId.

cable network
Photo de Taylor Vick sur Unsplash

Le concept fondamental : documentId vs id

Pour travailler efficacement avec Strapi 5, vous devez distinguer les lignes de la base de données des documents de contenu.

ConceptType d'identifiantPortéeUtilisation
DocumentdocumentId (string)Universel à travers les brouillons, versions publiées et localesLogique métier, requêtes Document Service, API REST/GraphQL
Ligne en baseid (integer)Spécifique à un enregistrement exact en base de données (un seul état dans une seule locale)Requêtes Knex de bas niveau, migrations de base de données héritées

Lorsque vous écrivez de la logique métier, utilisez systématiquement documentId. Si vous mettez à jour un document via son documentId, Strapi oriente automatiquement la modification vers le bon enregistrement (brouillon ou publié) en fonction des paramètres fournis.

Accéder au Document Service

Vous accédez au Document Service à l'aide du helper global strapi.documents('api::contentType.contentType'). Cette méthode accepte l'UID du Content Type et retourne un client d'API adapté à ce schéma.

typescript
// Exemple : Récupération du Document Service pour une collection Article
const articleService = strapi.documents('api::article.article');

Le Document Service prend en charge les opérations CRUD standard ainsi que des actions spécifiques aux documents :

  • findMany() : Rechercher plusieurs documents correspondant à des critères précis.
  • findOne() : Récupérer un document unique via son documentId.
  • findFirst() : Récupérer le premier document correspondant à un ensemble de filtres.
  • create() : Créer un nouveau document à l'état de brouillon ou directement publié.
  • update() : Modifier un document existant par son documentId.
  • delete() : Supprimer un document complet, une locale spécifique ou un état particulier.
  • publish() : Passer une version brouillon au statut publié.
  • unpublish() : Supprimer la version publiée tout en conservant le brouillon intact.
  • discardDraft() : Rétablir le brouillon avec le contenu de la version actuellement publiée.
  • count() : Compter les documents correspondant aux filtres.

Requêter du contenu : findMany et findOne

Par défaut, les requêtes exécutées avec le Document Service ciblent l'état brouillon (draft), sauf si vous demandez explicitement les entrées publiées. Ce comportement diffère de l'API de contenu publique (REST et GraphQL), qui renvoie par défaut les données publiées aux requêtes non authentifiées.

Requêtes de base et filtrage

Lors de la récupération de documents, vous pouvez filtrer les champs, trier les enregistrements, charger les relations (populate) et sélectionner des attributs spécifiques :

typescript
// controllers/article.ts
export default {
  async findFeatured(ctx) {
    const articles = await strapi.documents('api::article.article').findMany({
      fields: ['title', 'slug', 'publishedAt'],
      filters: {
        isFeatured: { $eq: true },
      },
      populate: {
        author: {
          fields: ['name', 'email'],
        },
        coverImage: true,
      },
      sort: { createdAt: 'desc' },
      limit: 10,
    });

    ctx.body = articles;
  },
};

Requêter un document par documentId

Pour récupérer une entrée spécifique, passez directement documentId à findOne() :

typescript
const article = await strapi.documents('api::article.article').findOne({
  documentId: 'a1b2c3d4e5f6g7h8i9j0',
  populate: ['category'],
});

Si vous omettez le paramètre status, Strapi renvoie la version draft du document si elle existe, ou la version published si aucun brouillon n'est présent.

Gérer les états Draft & Publish

Draft & Publish est une fonctionnalité native dans Strapi 5, gérée directement au moyen de paramètres de requête plutôt que par des endpoints distincts ou des flags complexes en base de données.

Définir le paramètre status

L'option status accepte deux valeurs :

  1. 'draft' : Cible les versions brouillons. Si une entrée comporte des modifications non publiées, elle retourne ces mises à jour en attente.
  2. 'published' : Cible exclusivement la version publiée et active. Si une entrée n'a jamais été publiée, une requête demandant son état publié retournera null.
typescript
// Récupère uniquement la version publiée pour une page publique
const publishedArticle = await strapi.documents('api::article.article').findOne({
  documentId: 'a1b2c3d4e5f6g7h8i9j0',
  status: 'published',
});

// Récupère la version brouillon pour un endpoint de prévisualisation
const draftArticle = await strapi.documents('api::article.article').findOne({
  documentId: 'a1b2c3d4e5f6g7h8i9j0',
  status: 'draft',
});

Publier, dépublier et abandonner les modifications

Strapi 5 propose des méthodes dédiées pour faire transiter les documents à travers les étapes du workflow, sans devoir mettre à jour manuellement des champs d'horodatage comme publishedAt.

typescript
const docService = strapi.documents('api::article.article');
const targetId = 'a1b2c3d4e5f6g7h8i9j0';

// 1. Publier un brouillon
const published = await docService.publish({
  documentId: targetId,
});

// 2. Dépublier (repasse le contenu en brouillon uniquement, retire l'état publié)
const unpublished = await docService.unpublish({
  documentId: targetId,
});

// 3. Abandonner le brouillon (annule les modifications non publiées et restaure l'état publié dans le brouillon)
const reverted = await docService.discardDraft({
  documentId: targetId,
});

Lorsque discardDraft() est appelée, toutes les modifications effectuées depuis la dernière publication sont effacées, réalignant ainsi le contenu du brouillon sur le document actuellement en ligne.

Travailler avec l'internationalisation (i18n)

Dans Strapi 5, les locales sont des sous-enregistrements d'un même document. Un unique documentId regroupe toutes les traductions d'une entrée. Il n'est donc plus nécessaire d'interroger des tables de jointure de traduction ni d'associer manuellement différentes entrées.

Requêter des locales spécifiques

Pour obtenir un document dans une locale précise, transmettez le paramètre locale :

typescript
// Récupère la traduction française d'un article
const frenchArticle = await strapi.documents('api::article.article').findOne({
  documentId: 'a1b2c3d4e5f6g7h8i9j0',
  locale: 'fr',
  status: 'published',
});

Si vous souhaitez récupérer toutes les locales disponibles pour un ensemble de documents, utilisez locale: '*' dans findMany() :

typescript
const allTranslations = await strapi.documents('api::article.article').findMany({
  locale: '*',
  filters: {
    category: { slug: 'technology' },
  },
});

Créer et mettre à jour du contenu localisé

Lors de la création d'une version traduite d'un document existant, vous ne faites pas appel à un service i18n séparé. Il vous suffit de fournir le documentId existant ainsi que le code de la nouvelle locale via update() ou create() :

typescript
// Ajout d'une version allemande à un document existant
const germanEntry = await strapi.documents('api::article.article').create({
  documentId: 'a1b2c3d4e5f6g7h8i9j0',
  locale: 'de',
  data: {
    title: 'Einführung in Strapi 5',
    content: 'Hier ist der Inhalt auf Deutsch...',
  },
});

Si vous appelez update() en spécifiant une locale, Strapi ne modifie que les données de cette langue, sans toucher aux autres :

typescript
await strapi.documents('api::article.article').update({
  documentId: 'a1b2c3d4e5f6g7h8i9j0',
  locale: 'es',
  data: {
    title: 'Título Actualizado',
  },
});

Créer et mettre à jour du contenu

Lorsque vous créez du nouveau contenu avec le Document Service, vous pouvez décider si le document doit débuter comme un brouillon ou être publié immédiatement.

Créer des brouillons vs documents publiés

Par défaut, un appel à create() sans paramètre de workflow génère un enregistrement en brouillon :

typescript
// Crée un document en brouillon
const newDraft = await strapi.documents('api::article.article').create({
  data: {
    title: 'Upcoming Product Launch',
    slug: 'upcoming-product-launch',
    body: 'Draft content undergoing review.',
  },
});

Pour créer un document et le publier directement en un seul appel, définissez status: 'published' dans les paramètres :

typescript
// Crée et publie immédiatement le document
const liveArticle = await strapi.documents('api::article.article').create({
  data: {
    title: 'Breaking News Announcement',
    slug: 'breaking-news',
    body: 'This content is live immediately.',
  },
  status: 'published',
});

Mettre à jour des champs spécifiques

Pour mettre à jour un document, vous ne transmettez que les champs que vous souhaitez modifier. Les champs relationnels acceptent des valeurs documentId ou des tableaux d'identifiants selon que la relation est simple ou polymorphique :

typescript
await strapi.documents('api::article.article').update({
  documentId: 'a1b2c3d4e5f6g7h8i9j0',
  data: {
    title: 'Revised Headline',
    category: 'cat_987654321',
  },
});

Middlewares du Document Service

Strapi 5 introduit les Document Service Middlewares. Ceux-ci interceptent les appels au Document Service, remplaçant avantageusement de nombreux cas d'usage où les développeurs recouraient auparavant à des lifecycle hooks fragiles (beforeCreate, afterUpdate).

Les middlewares enveloppent les opérations dans une chaîne prévisible, ce qui permet de valider les données, de modifier les paramètres avant qu'ils n'atteignent la base de données ou de déclencher des effets de bord une fois les mutations effectuées.

Déclarer un middleware de document

Vous enregistrez les middlewares du Document Service au sein des fonctions register ou bootstrap de votre application dans src/index.ts :

typescript
// src/index.ts
import type { Core } from '@strapi/strapi';

export default {
  register({ strapi }: { strapi: Core.Strapi }) {
    strapi.documents.use(async (context, next) => {
      // Vérifie la collection ciblée et l'action
      if (context.uid === 'api::article.article' && context.action === 'create') {
        // Inspecte ou modifie les paramètres entrants
        if (context.params.data && !context.params.data.slug) {
          context.params.data.slug = context.params.data.title
            .toLowerCase()
            .replace(/\s+/g, '-');
        }
      }

      // Exécute le middleware suivant ou l'opération en base de données
      const result = await next();

      // Logique post-opération
      if (context.uid === 'api::article.article' && context.action === 'publish') {
        strapi.log.info(`Document published: ${result.documentId}`);
      }

      return result;
    });
  },
};

À la différence des lifecycles classiques, les middlewares reçoivent l'intégralité du contexte de la requête du Document Service, y compris les options fields, populate, la locale et le status de publication.

Suppressions sécurisées : document vs locale

La suppression de contenu dans Strapi 5 offre un contrôle granulaire : vous pouvez choisir de purger une traduction précise ou d'éliminer l'entité entière.

Supprimer une locale spécifique

Si vous transmettez à la fois un documentId et une locale, seule cette traduction sera supprimée. Les autres locales associées au même document restent intactes :

typescript
// Supprime uniquement la traduction espagnole
await strapi.documents('api::article.article').delete({
  documentId: 'a1b2c3d4e5f6g7h8i9j0',
  locale: 'es',
});

Supprimer le document complet

Si vous omettez le paramètre locale ou si vous supprimez la locale principale, le document est effacé pour l'ensemble des locales et de ses états :

typescript
// Supprime l'ensemble des locales, brouillons et versions publiées de ce document
await strapi.documents('api::article.article').delete({
  documentId: 'a1b2c3d4e5f6g7h8i9j0',
});

Lors de la gestion de workflows éditoriaux complexes impliquant plusieurs contributeurs sur différentes locales, la traçabilité des suppressions et des révisions devient souvent indispensable. Si votre équipe a besoin de capacités complètes de restauration, des plugins comme Revisionary conservent chaque version de vos contenus Strapi. Ils permettent de visualiser les différences au niveau des champs, d'effectuer des restaurations ciblées et d'accéder à une corbeille pour récupérer les états de documents supprimés, locale par locale.

Migrer depuis l'Entity Service de Strapi 4

Lors de la mise à niveau de votre code back-end de Strapi 4 vers Strapi 5, inspectez votre base de code pour repérer les appels à strapi.entityService et les transformer en strapi.documents.

Principales différences de migration

  1. Appels de méthodes : Remplacez strapi.entityService.findMany(uid, params) par strapi.documents(uid).findMany(params).
  2. Références d'identifiants : Remplacez les recherches par ID numérique (findOne(uid, id)) par des document IDs (findOne({ documentId })).
  3. Logique de publication : Supprimez les mises à jour manuelles de type publishedAt: new Date(). Utilisez à la place strapi.documents(uid).publish({ documentId }).
  4. Syntaxe de populate : Les structures de populate dans Strapi 5 suivent une syntaxe objet similaire, mais les relations retournent dorénavant le documentId aux côtés de l'enregistrement.
  5. Valeur par défaut pour les brouillons : Dans Strapi 4, les requêtes de l'Entity Service contournaient Draft & Publish à moins d'avoir configuré des paramètres d'état de publication explicites. Dans Strapi 5, strapi.documents cible par défaut status: 'draft'. Si vous avez besoin des données en ligne, spécifiez expressément status: 'published'.

Voici une comparaison directe pour une requête courante :

typescript
// --- Strapi 4 (Entity Service) ---
const strapi4Result = await strapi.entityService.findOne('api::article.article', 42, {
  populate: { author: true },
  publicationState: 'live',
});

// --- Strapi 5 (Document Service) ---
const strapi5Result = await strapi.documents('api::article.article').findOne({
  documentId: 'c1d2e3f4g5h6i7j8k9l0',
  status: 'published',
  populate: { author: true },
});
a computer screen with a bunch of code on it
Photo de Chris Ried sur Unsplash

Conclusion

L'API Document Service de Strapi 5 unifie les brouillons, les publications et les traductions sous un unique documentId cohérent. En adaptant vos controllers personnalisés, vos tâches cron et vos services vers strapi.documents(), vous obtenez un comportement de requête prévisible, des structures de données plus saines et une prise en charge native des workflows de localisation complexes. Veillez à ce que vos requêtes définissent explicitement les paramètres status et locale souhaités afin de récupérer très exactement la version du contenu attendue par votre application.

Mots-clésStrapi 5Document ServiceModélisation de contenuBack-endTypeScript

L’équipe Revisionary

Les développeurs de Revisionary, un plugin d’historique des versions pour Strapi 5. Nous écrivons sur Strapi, le développement web, l’IA et la sécurité des applications.

Cet article a été rédigé avec l'aide d'une IA.

Tous les articles