ITSkillsCenter
Blog

Strapi content types et API : guide pratique avancé

12 دقائق للقراءة

Lecture : 13 minutes · Niveau : intermédiaire · Mise à jour : avril 2026

Le cœur de Strapi, ce sont les content types et l’API qu’ils exposent. Les bases s’apprennent en quelques heures, mais bien modéliser des structures complexes et exploiter l’API au mieux demande de la pratique. Ce guide couvre les patterns vraiment utiles : composants, dynamic zones, populate, filtres avancés, lifecycle hooks pour automatiser des comportements.

Voir aussi → Strapi headless CMS pour PME : guide complet.


Sommaire

  1. Modélisation : philosophie et conventions
  2. Champs simples et leurs subtilités
  3. Relations : one-to-many, many-to-many, polymorphic
  4. Components réutilisables
  5. Dynamic Zones pour pages modulaires
  6. API REST : populate, filtres, sort, pagination
  7. API GraphQL avancée
  8. Lifecycle hooks et logique métier
  9. Custom controllers et services
  10. Validation et sanitization
  11. FAQ

1. Modélisation : philosophie et conventions

Bien modéliser ses content types évite des refactorings douloureux plus tard.

Principes

  • Penser API consumer-first : comment le frontend va-t-il consommer ce contenu ?
  • Pas de duplication : si un champ est partagé entre plusieurs types, en faire un component ou une relation
  • Unité de la donnée : un content type = un concept métier
  • Évolutivité : prévoir l’extension future plutôt que tout enfermer dès le début

Conventions de nommage

  • Singular API ID : nom du content type au singulier (article, pas articles)
  • Plural API ID : Strapi gère automatiquement (articles)
  • Champs en snake_case ou camelCase : choisir une convention et s’y tenir
  • Components dans des catégories : grouper logiquement (shared.seo, blocks.hero, forms.contact)

Single Type vs Collection Type

  • Collection Type : entité multiple (Article, Produit, Client, Page)
  • Single Type : entité unique (Configuration globale, Page d’accueil, Footer)

Single Types parfaits pour des données globales. Évite la confusion d’avoir une seule entrée dans une collection.


2. Champs simples et leurs subtilités

Text vs Rich Text

  • Text (short) : titre, slug, label
  • Text (long) : excerpt, description courte
  • Rich Text : contenu d’article. Format Markdown ou Blocks (éditeur structuré v5)

Le format Blocks (Strapi v5) est plus puissant : structure JSON éditée via UI dédiée, plus précis qu’un Markdown libre, mais plus complexe à rendre côté frontend.

Number

  • Integer, BigInteger : entiers
  • Decimal : monétaire (préférer à Float pour la précision)
  • Float : flottants

Pour des prix : Decimal avec précision contrôlée.

Date

  • Date : seulement la date (anniversaire, deadline)
  • DateTime : avec heure (publication, événement précis)
  • Time : heure seule

Toujours utiliser DateTime pour des timestamps réels.

Boolean

Utile pour des flags simples (publié/brouillon, actif/inactif). Pour des états multiples : utiliser un champ Enumeration plutôt qu’un boolean qui devient ambigu.

Enumeration

Liste fermée de valeurs. Très utile pour des status, types, catégories fixes. Plus contraint que Text avec validation, plus simple qu’une relation.

JSON

Pour des données structurées libres (paramètres, métadonnées variables). À utiliser avec parcimonie : moins requêtable qu’un schéma typé.

Media

  • Single media : une seule image/fichier
  • Multiple media : galerie d’images, fichiers attachés

Configurer les types de fichiers acceptés par champ (images uniquement, ou tous fichiers).

UID

Identifiant unique généré (souvent depuis le titre). Pour les slugs URL. Auto-update activable.


3. Relations : one-to-many, many-to-many, polymorphic

One-to-One

Article a un Auteur principal :

Article ←→ Author (one-to-one)

Rarement utilisé en pratique : préférer one-to-many quand possible.

One-to-Many

Catégorie contient plusieurs Articles :

Category 1 → N Articles
Article N → 1 Category

Configuration : sur Article, ajouter un champ Relation « Many-to-One » vers Category.

Many-to-Many

Article a plusieurs Tags, Tag est sur plusieurs Articles :

Article M → N Tags

Strapi gère automatiquement la table de jointure.

Polymorphic

Une Image peut être attachée à plusieurs types (Article, Page, Produit). Strapi gère via Media field. Pour des relations polymorphiques custom (commentaires sur articles ou pages, par exemple) : workaround via JSON ou tables séparées.

Performance des relations

Strapi N+1 par défaut sur les relations. Toujours utiliser populate (REST) ou sélection explicite (GraphQL) pour charger en une seule requête. Voir section API.


4. Components réutilisables

Les components sont des groupes de champs réutilisables.

Cas d’usage typiques

  • SEO : meta_title, meta_description, og_image, canonical_url. À mettre sur tous les content types ayant une URL publique.
  • Address : street, city, postal_code, country
  • ContactInfo : email, phone, fax
  • OpeningHours : repeatable component (un par jour de la semaine)
  • CallToAction : title, link, button_text, button_style

Création

Content-Type Builder → Components → Create new component. Choisir une catégorie (group logique) et le nom.

Utilisation

Sur un content type, ajouter un champ « Component » et sélectionner le component créé. Option « Repeatable » pour permettre plusieurs instances (ex: liste d’horaires d’ouverture).

Avantages

  • Cohérence : même structure partout où le component est utilisé
  • Maintenabilité : modifier le component met à jour partout
  • Réutilisation : une seule définition pour de nombreux types

Limites

  • Pas de relations vers d’autres content types depuis un component (limitation à connaître)
  • Pas indépendamment requêtable : les components sont toujours imbriqués dans un parent

5. Dynamic Zones pour pages modulaires

Les Dynamic Zones permettent à un éditeur de construire une page modulaire en empilant des sections de type variable.

Cas d’usage

Une page marketing flexible où l’éditeur peut combiner :
– Hero
– Texte avec image
– Galerie
– Témoignages
– Call-to-action
– FAQ

Configuration

Sur un content type Page : ajouter un champ « Dynamic Zone ». Sélectionner les components autorisés. L’éditeur peut alors empiler ces sections dans n’importe quel ordre.

Rendu côté frontend

// Next.js exemple
function PageRenderer({ blocks }) {
  return blocks.map((block, i) => {
    switch (block.__component) {
      case 'blocks.hero':
        return <Hero key={i} {...block} />;
      case 'blocks.gallery':
        return <Gallery key={i} {...block} />;
      case 'blocks.cta':
        return <CTA key={i} {...block} />;
      default:
        return null;
    }
  });
}

Le frontend distingue les types via le champ __component et rend le composant React approprié.

Avantages

  • Flexibilité énorme pour les éditeurs
  • Pas besoin de coder une nouvelle page pour chaque variation
  • Réutilisation des sections sur plusieurs pages

Coûts

  • Nécessite que le frontend implémente chaque type de bloc
  • Plus complexe à débugger que des pages statiques
  • Demande discipline dans la modélisation pour éviter le chaos

Pour des sites marketing dynamiques : excellent investissement.


6. API REST : populate, filtres, sort, pagination

Populate

Sans populate, les relations ne sont pas chargées. Toujours préciser explicitement.

GET /api/articles?populate=*                          # tout (déconseillé en prod)
GET /api/articles?populate=author,categories          # ciblé
GET /api/articles?populate[author][populate]=avatar   # imbriqué
GET /api/articles?populate[blocks][on][blocks.hero][populate]=image  # dynamic zone

populate=* charge tout en cascade : à éviter en production (performance, taille payload). Toujours préciser ce dont le frontend a vraiment besoin.

Filtres

?filters[publishedAt][$notNull]=true
?filters[author][slug][$eq]=jean-doe
?filters[categories][name][$contains]=tech
?filters[$or][0][title][$contains]=guide&filters[$or][1][title][$contains]=tutoriel

Opérateurs : $eq, $ne, $lt, $gt, $lte, $gte, $contains, $in, $notIn, $null, $notNull, $between, $startsWith, $endsWith.

Sort

?sort=publishedAt:desc
?sort[0]=featured:desc&sort[1]=publishedAt:desc   # multi-critères

Pagination

?pagination[page]=1&pagination[pageSize]=20
?pagination[start]=0&pagination[limit]=20
?pagination[withCount]=true

Sélection de champs

?fields=title,slug,excerpt

Pour ne charger que les champs nécessaires. Optimise le payload.

Locale

?locale=fr
?locale=all

Pour le multi-langue.


7. API GraphQL avancée

Avec le plugin GraphQL, plus flexible que REST.

query GetArticles($category: String!) {
  articles(
    filters: { categories: { slug: { eq: $category } } }
    sort: "publishedAt:desc"
    pagination: { page: 1, pageSize: 20 }
  ) {
    data {
      id
      attributes {
        title
        slug
        excerpt
        publishedAt
        coverImage {
          data {
            attributes {
              url
              alternativeText
              formats
            }
          }
        }
        author {
          data {
            attributes { name avatar { data { attributes { url } } } }
          }
        }
        categories {
          data { attributes { name slug } }
        }
      }
    }
    meta { pagination { page pageCount total } }
  }
}

Mutations

mutation CreateArticle($data: ArticleInput!) {
  createArticle(data: $data) {
    data { id attributes { title slug } }
  }
}

Permissions et authentification s’appliquent comme sur REST.

Avantages GraphQL

  • Une seule requête pour un graphe complet (vs plusieurs REST)
  • Schema introspectable (auto-complétion dans IDE)
  • Type safety si client typé
  • Pas de over-fetching ni under-fetching

Inconvénients

  • Plus complexe à mettre en cache (le caching CDN classique fonctionne moins bien)
  • Courbe d’apprentissage
  • Outillage côté frontend (Apollo, urql) à intégrer

Pour des apps avec relations complexes : GraphQL gagne. Pour des cas simples : REST suffit.


8. Lifecycle hooks et logique métier

Strapi expose des hooks au cycle de vie des entités.

Hooks disponibles

  • beforeCreate, afterCreate
  • beforeUpdate, afterUpdate
  • beforeDelete, afterDelete
  • beforeFindOne, afterFindOne
  • beforeFindMany, afterFindMany
  • beforeCount, afterCount

Implémentation

src/api/article/content-types/article/lifecycles.js :

module.exports = {
  async beforeCreate(event) {
    const { data } = event.params;
    if (!data.slug && data.title) {
      data.slug = slugify(data.title);
    }
  },

  async afterCreate(event) {
    const { result } = event;
    await strapi.entityService.create('api::audit-log.audit-log', {
      data: { action: 'article_created', article: result.id, user: ctx.state.user.id },
    });
  },

  async beforeUpdate(event) {
    // ex: incrémenter une version, mettre à jour un champ updated_by
  },
};

Cas d’usage typiques

  • Slugification automatique
  • Audit log
  • Notifications (email, webhook) après publication
  • Calculs dérivés (ex: word_count, reading_time)
  • Sanitization de champs
  • Validation métier au-delà des validations natives

Attention performance

Les hooks s’exécutent à chaque opération. Lourd ou bloquant = ralentissement API. Pour des actions coûteuses : déléguer à un job asynchrone (Bull, BullMQ, ou autre queue).


9. Custom controllers et services

Pour aller au-delà des routes par défaut.

Controller custom

src/api/article/controllers/article.js :

const { createCoreController } = require('@strapi/strapi').factories;

module.exports = createCoreController('api::article.article', ({ strapi }) => ({
  async findFeatured(ctx) {
    const articles = await strapi.entityService.findMany('api::article.article', {
      filters: { featured: true, publishedAt: { $notNull: true } },
      sort: 'publishedAt:desc',
      limit: 5,
      populate: ['author', 'coverImage'],
    });
    return { data: articles };
  },
}));

Route custom

src/api/article/routes/custom.js :

module.exports = {
  routes: [
    {
      method: 'GET',
      path: '/articles/featured',
      handler: 'article.findFeatured',
      config: { policies: [], auth: false },
    },
  ],
};

Ainsi GET /api/articles/featured retourne uniquement les articles vedettes, optimisé.

Services

Les services Strapi factorisent la logique business réutilisable entre controllers, hooks, plugins. Pattern standard MVC adapté à Strapi.

module.exports = createCoreService('api::article.article', ({ strapi }) => ({
  async getRelatedArticles(article) {
    // logique de recommandation
  },
}));

Appel depuis controller : strapi.service('api::article.article').getRelatedArticles(...).


10. Validation et sanitization

Validations natives

Sur chaque champ, configurable dans Content-Type Builder :

  • required
  • unique
  • min/max length pour text
  • min/max value pour number
  • regex pattern
  • email format
  • default value

Validations custom

Via lifecycle hooks beforeCreate / beforeUpdate :

async beforeCreate(event) {
  const { data } = event.params;
  if (data.startDate && data.endDate && data.startDate > data.endDate) {
    throw new ValidationError('startDate must be before endDate');
  }
}

Sanitization

Strapi sanitize automatiquement les inputs (XSS, injection). Pour des cas spécifiques (whitelist de tags HTML autorisés dans rich text), configurer le plugin upload ou le rich text editor.

Permissions par champ

Dans Settings → Roles, on peut désactiver des champs spécifiques pour certains rôles. Un éditeur peut modifier le titre mais pas le statut publication, par exemple.


11. FAQ

populate=* en prod ou pas ?

Non. Charge toutes les relations en cascade, payload énorme, performance dégradée. Toujours préciser explicitement les champs nécessaires.

Components ou content types séparés ?

Components quand le contenu est intégré au parent (SEO, address). Content type séparé quand on veut le requêter, le réutiliser via relations, ou avoir une URL publique dédiée.

Dynamic Zones partout ?

Non, ça devient vite chaotique. Réserver aux pages vraiment modulaires (landing, marketing). Pour des structures stables (article de blog), un schéma fixe est plus clair.

Lifecycle hooks ralentissent l’API ?

Oui si lourds. Garder les hooks rapides (calculs simples, validations). Pour actions lourdes (envoi email, appel API tiers) : queue asynchrone.

Migration de schema sans perdre les données ?

Strapi génère des migrations automatiques pour la plupart des changements. Renommage de champ, changement de type, suppression : à tester sur staging avant production.

REST vs GraphQL : impact sur le cache ?

REST : facile à cacher au niveau CDN/HTTP par URL. GraphQL : tout sur même endpoint POST, cache HTTP standard ne marche pas. Solutions : Apollo cache côté client, ou GraphQL caching server-side.

Comment versionner les content types ?

Changements de schéma génèrent des migrations dans database/migrations. Versionner ces migrations dans Git, jamais les modifier après application.


Articles liés (cluster Strapi)


Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité