Aller au contenu

Référence de configuration

Tout ce que comprend pullpress.config.yml : le seul fichier qui transforme votre dépôt en CMS.

Le fichier de configuration se trouve à la racine de votre dépôt et est versionné comme tout le reste. Contrairement aux modifications de contenu (qui ouvrent une demande de modification à relire), enregistrer la configuration est une action d’administrateur qui commite directement sur votre branche par défaut : un nouveau modèle de contenu prend effet aussitôt (et il est validé avant d’être écrit). Il comporte quelques clés de premier niveau, une liste de collections et un petit ensemble de types de champs.

Vous préférez modifier dans l’application ? L’éditeur de configuration intégré propose un mode visuel dont le sélecteur de types de champs regroupe chaque type avec une description d’une ligne, et son mode YAML valide avant l’enregistrement : une faute de syntaxe est signalée à sa ligne et sa colonne exactes, et les problèmes de structure sont listés par chemin de champ. Le nom du fichier est pullpress.config.yml (l’extension .yaml fonctionne aussi).

L’éditeur de configuration de l’application en mode visuel : onglets Visuel et YAML, les collections Blog posts, Pages et Opening hours dans une barre latérale, et le nom, le libellé, le dossier, le modèle de nom de fichier et la liste des champs de la collection choisie sous forme de formulaire
L’éditeur de configuration : le même fichier, modifiable visuellement ou en YAML validé.

Clés de premier niveau

CléDescription
versionVersion du schéma de configuration. Actuellement toujours 1.
media.folderChemin du dépôt où les images envoyées sont commitées, par exemple public/uploads.
media.public_pathPréfixe d’URL sous lequel le site publié sert ce dossier, par exemple /uploads. Sert à écrire des liens d’images corrects dans votre Markdown.
collectionsLa liste des collections de contenu avec lesquelles les rédacteurs travaillent. Chaque collection a un nom, un libellé, un type et des champs.
componentsFacultatif. Une table de groupes de champs nommés et réutilisables, référencés par les champs de type: component. Définissez un groupe une fois, réutilisez-le dans plusieurs collections.

Types de collections

Une collection décrit un type de contenu. Son type décide de la correspondance entre les entrées et les fichiers du dépôt.

folder : un fichier par entrée

Chaque entrée est son propre fichier Markdown dans le dossier configuré. Les rédacteurs peuvent créer, modifier et supprimer des entrées : idéal pour les articles de blog, les actualités ou les membres de l’équipe.

collections:
  - name: blog
    label: Blog posts
    type: folder
    folder: content/blog
    fields:
      - { name: title, label: Title, type: string, required: true }
      - { name: date,  label: Date,  type: date, default: now }
      - { name: body,  label: Body,  type: markdown }

Formats de frontmatter. Les trois formats sont conservés : YAML entre des lignes ---, TOML entre des lignes +++ (le format par défaut de Hugo) et un objet JSON nu en haut du fichier. Les entrées existantes gardent toujours leur format, l’ordre de leurs clés et les clés que PullPress ne gère pas. Le frontmatter YAML garde aussi ses # comments, ses ancres et ses chaînes en bloc |/> à l’enregistrement : modifier un champ produit un diff qui ne touche que ce champ. (Les frontmatters TOML et JSON gardent les valeurs et l’ordre des clés, mais les lignes de commentaire TOML ne sont pas conservées.) Mettez format: toml (ou json) sur une collection pour créer les nouvelles entrées dans ce format ; la détection le renseigne pour vous quand votre contenu existant l’utilise.

Les sites qui utilisent un dossier par entrée (content collections d’Astro, leaf bundles de Hugo : content/my-post/index.md) mettent structure: bundle. Ajoutez media: colocated pour ranger les images d’une entrée dans son propre dossier, référencées de façon relative (./photo.jpg), pour que la chaîne d’images de votre générateur les prenne en charge. La détection du dépôt vous propose tout cela : elle reconnaît les content collections d’Astro, les leaf et branch bundles de Hugo (plus contentDir et les fichiers de data/), les collections Jekyll de _config.yml, les dossiers de contenu Next.js et les bundles imbriqués par date, et écrit les libellés générés dans la langue de votre interface :

collections:
  - name: projects
    label: Projects
    type: folder
    folder: redesign/content
    structure: bundle      # one folder per entry: content/my-post/index.md
    media: colocated       # images live inside the entry folder (./photo.jpg)
    slug: "{{date}}-{{slug:title}}"
    fields:
      - { name: title, label: Title, type: string, required: true }
      - { name: date,  label: Date,  type: date, default: now }
      - { name: body,  label: Body,  type: markdown }

files : des pages fixes

Un ensemble fixe de fichiers que vous listez explicitement. Les rédacteurs peuvent les modifier mais pas en ajouter ni en retirer : parfait pour des pages uniques comme À propos ou Contact. Chaque fichier a ses propres fields (imbriqués sous ce fichier, pas partagés au niveau de la collection), donc chaque page peut avoir une forme différente.

collections:
  - name: pages
    label: Pages
    type: files
    files:
      - name: about
        label: About page
        file: content/about.md
        fields:
          - { name: title, label: Title, type: string, required: true }
          - { name: body,  label: Body,  type: markdown }
      - name: contact
        label: Contact page
        file: content/contact.md
        fields:
          - { name: heading, label: Heading, type: string, required: true }
          - { name: body,    label: Body,    type: markdown }

La détection l’utilise aussi pour les pages de section : le _index.md d’un branch bundle de Hugo (la page d’accueil d’une section) n’est pas une entrée de la collection folder, donc la détection les rassemble tous dans une collection files générée « Section pages », modifiable comme n’importe quelle page, sans câblage manuel.

data : un seul fichier de données structurées

Un fichier YAML, JSON ou TOML modifié entièrement via un formulaire, sans corps Markdown. Utilisez-le pour les données structurées que vos templates lisent directement : horaires d’ouverture, menus de navigation, réglages du site. Les clés du fichier absentes de la configuration des champs survivent à chaque enregistrement, dans les trois formats. Les fichiers YAML gardent aussi leurs commentaires ; les fichiers TOML et JSON gardent leurs clés et leur ordre mais sont reformatés.

collections:
  - name: opening_hours
    label: Opening hours
    type: data
    file: data/opening-hours.yml
    fields:
      - name: hours
        label: Weekly hours
        type: list
        of:
          type: object
          fields:
            - { name: day,    label: Day,    type: select, options: [mon, tue, wed, thu, fri, sat, sun] }
            - { name: open,   label: Opens,  type: string }
            - { name: close,  label: Closes, type: string }
            - { name: closed, label: Closed all day, type: boolean, default: false }

Certains fichiers de données ne sont pas une table mais une grande liste : un tableau en haut du fichier, comme celui que lit le loader file() d’Astro (src/data/authors.json contenant [ …, … ]), ou les fichiers de données en liste de Hugo/Jekyll/Eleventy. Mettez root: list et déclarez exactement un champ de type list : ses éléments sont le tableau de premier niveau du fichier, modifié comme un formulaire répétable. Les éléments que vous ne touchez pas sont réécrits tels quels (l’ordre de leurs clés et leurs clés supplémentaires sont conservés) ; TOML ne peut pas représenter un tableau de premier niveau, donc cela fonctionne pour les fichiers .json, .yaml et .yml. La détection propose automatiquement cette forme.

Les fichiers CSV et TSV (Jekyll _data/*.csv, exports de tableur) fonctionnent de la même façon : ils sont toujours tabulaires, donc faites pointer une collection data en root: list vers le fichier et listez les colonnes comme champs string de l’unique champ list. La ligne d’en-tête et l’ordre des colonnes sont conservés ; chaque valeur fait l’aller-retour en texte.

collections:
  - name: authors
    label: Authors
    type: data
    file: src/data/authors.json   # the file's root is [ … ], not { … }
    root: list
    fields:                       # exactly one field, of type list
      - name: items
        label: Authors
        type: list
        of:
          type: object
          fields:
            - { name: name,   label: Name,   type: string, required: true }
            - { name: avatar, label: Avatar, type: image }

folder d’entrées de données : sans corps Markdown

Parfois chaque entrée n’est que des données structurées, sans corps d’article : auteurs, membres de l’équipe, produits, listes de liens. Donnez à une collection folder une extension de données (json, yaml, yml ou toml) et chaque entrée devient un seul fichier de données que les rédacteurs remplissent comme un formulaire : la même création, modification, suppression, relecture et le même historique que pour le Markdown, simplement sans corps. C’est exactement ainsi que fonctionnent les collections Astro type: 'data', et la détection vous les propose.

collections:
  - name: authors
    label: Authors
    type: folder
    folder: src/content/authors
    extension: json          # each entry is a .json data file (no body)
    slug: "{{slug:name}}"
    fields:
      - { name: name,   label: Name,   type: string, required: true }
      - { name: bio,    label: Bio,    type: text }
      - { name: avatar, label: Avatar, type: image }

Le fichier entier constitue les données (son format suit l’extension), et, comme pour une collection data, l’ordre des clés et les clés absentes de votre liste de champs sont conservés à chaque enregistrement. Il n’y a pas de champ de corps, n’en ajoutez donc pas ; les relations peuvent aussi pointer vers ces collections.

Options des collections

En plus de name, label, folder et fields, une collection folder accepte ces clés facultatives. La détection en renseigne la plupart pour vous.

CléDescription
structureflat (par défaut) garde un fichier par entrée ; bundle utilise un dossier par entrée (folder/slug/index.md).
mediasite (par défaut) envoie les fichiers dans le dossier des médias ; colocated les range dans le dossier bundle de l’entrée, référencés comme ./photo.jpg. colocated exige structure: bundle.
extensionType de fichier des entrées : md (par défaut), markdown ou mdx pour des pages Markdown ; html, adoc ou org pour du contenu non Markdown (le frontmatter est toujours conservé ; déclarez le corps en type: code pour qu’il soit modifié en texte brut) ; ou json, yaml, yml ou toml pour faire de chaque entrée un fichier de données sans corps (un dossier d’entrées de données, comme les data collections d’Astro).
formatFormat du frontmatter des nouvelles entrées : yaml (par défaut), toml ou json. Les entrées existantes gardent leur format.
nestedtrue rassemble aussi les entrées des sous-dossiers (jusqu’à cinq niveaux), groupées par dossier. Désactivé par défaut.
localesCodes de langue supplémentaires (par exemple [fr, en]) qui rendent chaque entrée modifiable par langue, organisées selon la clé i18n.
i18nOù se trouvent les traductions : suffix (par défaut) utilise des fichiers voisins (post.fr.md) ; subfolder utilise un dossier par langue (Hugo content/<lang>/, Astro/Starlight docs/<lang>/). Avec subfolder, faites pointer folder vers le dossier de la langue par défaut et nommez ce segment dans defaultLocale ; par exemple folder: content/en, defaultLocale: en, locales: [fr, de] place les entrées françaises dans content/fr.
slugModèle de nom de fichier pour les nouvelles entrées. Par défaut {{slug:title}}. Jetons : {{slug:field}} (champ converti en slug), {{field}}, {{date}}, {{year}}, {{month}}, {{day}}.
sortOrdre de la liste des entrées : { field, order } avec order asc ou desc. Par défaut le champ de date, le plus récent en premier.

Une collection files liste plutôt chaque fichier avec ses propres fields (et un format facultatif par fichier) ; une collection data pointe vers un seul file et partage un seul ensemble de fields.

Types de champs

Chaque champ d’une collection devient un contrôle de formulaire dans l’éditeur. Les options communes label, required et default fonctionnent sur tous les types. Deux autres aident vos rédacteurs : un texte help affiche une indication sous le champ, et un placeholder affiche un texte d’exemple dans un champ vide. Les champs obligatoires reçoivent un astérisque rouge et, à la soumission, un récapitulatif aimable de ce qui manque.

Champs en lecture seule. Ajoutez readOnly: true pour afficher un champ avec sa valeur enregistrée sans que les rédacteurs puissent la changer : le nom d’un document de réglages, un slug verrouillé, un identifiant externe. Il est verrouillé dans l’éditeur web comme dans l’application iOS, et le serveur restaure la valeur enregistrée à chaque enregistrement : un champ en lecture seule ne peut jamais dériver, quelle que soit la façon dont arrive une modification.

- name: title
  type: string
  label: Name
  readOnly: true            # shown, but editors can't change it

Textes par langue. L’interface s’adapte à la langue choisie par chaque rédacteur, et vous pouvez faire de même pour vos propres textes label, help et placeholder. La clé simple est affichée à tous ; ajoutez un suffixe _<language> (parmi en, nl, fr, de, es) pour la remplacer pour les rédacteurs qui utilisent cette langue. Rien n’est traduit automatiquement : vous écrivez chaque variante.

- name: title
  type: string
  label: Titel              # shown to everyone
  help: De titel van je pagina
  help_en: Your page title  # only for editors using English
TypeDescriptionExemple
stringUne seule ligne de texte.{ name: title, type: string, required: true }
textTexte brut sur plusieurs lignes, affiché dans une zone de texte.{ name: summary, type: text }
markdownTexte mis en forme dans l’éditeur épuré, stocké en Markdown.{ name: body, type: markdown }
numberUn nombre entier ou décimal.{ name: price, type: number }
booleanUn interrupteur oui/non.{ name: featured, type: boolean, default: false }
dateUne date du calendrier. default: now remplit la date du jour.{ name: date, type: date, default: now }
datetimeUne date avec une heure.{ name: published_at, type: datetime }
selectUn choix dans une liste fixe, via options.{ name: category, type: select, options: [news, recipes] }
multiselectPlusieurs choix dans une liste fixe, sous forme de cases à cocher.{ name: categories, type: multiselect, options: [news, events] }
urlUne adresse web, validée comme https://…{ name: website, type: url }
emailUne adresse e-mail validée.{ name: contact, type: email }
colorUn sélecteur de couleur, stocké en #rrggbb.{ name: accent, type: color }
imageUne image envoyée, commitée dans le dossier des médias.{ name: cover, type: image }
listUne liste répétable d’éléments ; of est la définition de champ du type d’élément.{ name: tags, type: list, of: { type: string } }
objectUn groupe de champs imbriqués, définis avec fields.{ name: cta, type: object, fields: [...] }
relationUne référence à une entrée d’une autre collection folder (stocke son slug).{ name: author, type: relation, collection: team }
blocksDes sections empilables de formes différentes (chaque élément porte une clé _type ; changez la clé avec typeKey).{ name: sections, type: blocks, blocks: [...] }
hiddenNon affiché aux rédacteurs ; écrit avec une valeur fixe (par exemple une clé de mise en page).{ name: layout, type: hidden, default: post }
codeUn bloc de code en police à chasse fixe, avec une indication de langage facultative.{ name: snippet, type: code, language: js }
mapUn lieu, modifié en latitude/longitude (stocké en 'lat,lng').{ name: location, type: map }
computeUne valeur en lecture seule dérivée d’un modèle ; chaque {{token}} est le nom d’un champ voisin.{ name: ref, type: compute, template: '{{brand}}-{{sku}}' }
keyvalueDes paires clé/valeur libres, par exemple des métadonnées supplémentaires.{ name: meta, type: keyvalue }
seoPanneau SEO : titre + description (avec indications de longueur), URL canonique, image OG et noindex, avec un aperçu de l’extrait de recherche et du JSON-LD.{ name: seo, type: seo }
componentDéploie un groupe de champs réutilisable de la table components de premier niveau.{ name: cta, type: component, component: callToAction }

list et object se combinent : une list d’object donne des groupes de champs répétables, comme les horaires hebdomadaires de l’exemple ci-dessus.

Règles de validation

En plus de required, un champ peut reprendre exactement les contraintes qu’impose le schéma de votre générateur : les erreurs sont ainsi repérées dans l’éditeur avec un message aimable, et non par un build qui échoue. Les champs numériques acceptent min, max et integer: true ; les champs texte (string, text, markdown, code) acceptent minLength, maxLength et une expression régulière pattern ; list et multiselect acceptent minItems et maxItems. Les erreurs s’affichent à côté du champ pendant la saisie (un champ facultatif peut rester vide ; ses règles s’appliquent dès qu’on y tape quelque chose), et les mêmes règles tournent côté serveur à chaque enregistrement.

- { name: title,  type: string, required: true, maxLength: 70 }
- { name: rating, type: number, min: 1, max: 5, integer: true }
- { name: sku,    type: string, pattern: "^[A-Z]{2}-\\d{4}$" }
- { name: tags,   type: list, of: { type: string }, minItems: 1, maxItems: 8 }

Définir des blocks

Un champ blocks permet aux rédacteurs d’empiler des sections de formes différentes (un hero, une citation, une galerie…). Listez les types de sections sous blocks ; chacun est un groupe nommé de fields (avec un label facultatif), comme un object :

- name: sections
  label: Page sections
  type: blocks
  blocks:
    - name: hero
      label: Hero
      fields:
        - { name: heading, type: string, required: true }
        - { name: image,   type: image }
    - name: quote
      label: Pull quote
      fields:
        - { name: text,   type: text, required: true }
        - { name: source, type: string }

Chaque élément ajouté par un rédacteur enregistre de quel bloc il s’agit sous une clé _type (hero, quote, …), pour que votre générateur sache comment afficher chaque section. Si votre schéma attend une autre clé (le z.discriminatedUnion('type', …) d’Astro, ou du contenu importé de Decap CMS, qui la stocke en type), mettez typeKey sur le champ (typeKey: type) et PullPress lit, écrit et valide cette clé à la place. L’import Decap le règle automatiquement, donc les entrées importées fonctionnent sans changement.

Champs conditionnels (showIf)

Tout champ peut déclarer showIf pour n’apparaître que lorsqu’un champ voisin correspond. Les champs masqués sont vidés (ils n’atterrissent donc jamais dans le frontmatter), et la règle required d’un champ conditionnel ne s’applique que tant qu’il est visible.

fields:
  - { name: hasCta, label: Add a call to action?, type: boolean }
  - name: ctaUrl
    label: Button URL
    type: url
    required: true
    showIf: { field: hasCta, truthy: true }   # equals / oneOf also work

Collections imbriquées

Mettez nested: true sur une collection folder pour rassembler aussi les entrées des sous-dossiers (jusqu’à cinq niveaux) et les afficher groupées par dossier : pratique pour des arborescences de documentation. La recherche et les sélecteurs de relations prennent en compte les entrées imbriquées automatiquement.

collections:
  - name: docs
    label: Documentation
    type: folder
    folder: content/docs
    nested: true            # gather entries from subfolders too
    fields:
      - { name: title, type: string, required: true }
      - { name: body,  type: markdown }

Composants de champs réutilisables

Définissez une fois un groupe de champs nommé sous la table components de premier niveau, puis insérez-le dans n’importe quelle collection avec un champ component. Au chargement, il se déploie en un groupe d’objet ordinaire, donc une modification du groupe met à jour chaque collection qui l’utilise.

components:
  callToAction:
    - { name: label, type: string }
    - { name: url,   type: url }
collections:
  - name: pages
    type: folder
    folder: content/pages
    fields:
      - { name: title, type: string, required: true }
      - { name: cta,   type: component, component: callToAction }

Où vivent les médias (et pourquoi pas S3)

Les fichiers envoyés sont commités dans votre dépôt, à côté du contenu qui les référence. C’est un choix délibéré, pas une fonction manquante : le dépôt reste la seule source de vérité, les images suivent leurs articles à travers les branches, la relecture et l’historique, et cloner le dépôt donne le site complet, sans second système à sauvegarder ou à payer.

Besoin d’un CDN ou de redimensionnement d’images ? Laissez votre générateur ou votre hébergeur s’en charger au build (chaînes d’images d’Astro et de Next.js, CDN d’images de Netlify, URL fetch de Cloudinary). PullPress garde l’original dans Git ; votre build le rend rapide. Les fichiers sont redimensionnés au format web dans le navigateur avant l’envoi, même une photo de téléphone de 20 à 50 Mo, ce qui garde en pratique les dépôts très légers. Un stockage d’objets externe (S3 et consorts) casserait la garantie « votre dépôt est tout », donc nous ne le prévoyons pas.

Découvrabilité IA (llms.txt)

PullPress peut générer un llms.txt à la racine de votre dépôt : un index en texte brut de votre site (son nom, son URL et, pour chaque collection folder, une liste de titres d’entrées) que les moteurs de réponse IA lisent pour résumer et citer correctement votre site. Il est construit de façon déterministe à partir de votre modèle de contenu (aucun appel IA, rien ne quitte votre dépôt).

Les administrateurs le trouvent dans le panneau Paramètres → Découvrabilité IA (GEO) d’un site, qui affiche aussi un score de préparation : si l’URL de votre site est renseignée, si llms.txt est présent, si vous avez du contenu publié et des champs de description/SEO, et, si l’URL de votre site est joignable, si la page d’accueil expose une meta description, des balises Open Graph et du JSON-LD. Cliquez sur Mettre à jour llms.txt pour (re)générer le fichier ; il est commité comme toute autre modification : via la relecture, ou directement sur votre branche par défaut sur les sites en publication directe.

Cela complète brandvoice.md ci-dessous : le ton de marque guide la façon dont l’IA écrit pour vous, tandis que llms.txt aide l’IA à trouver et citer ce que vous avez déjà publié. Il ne gère ni robots.txt ni votre sitemap : laissez-les à votre générateur.

Ton de marque pour les agents IA

Déposez un brandvoice.md facultatif à la racine de votre dépôt pour décrire le ton du site, les mots préférés et proscrits, et le niveau de langue. Les agents IA connectés via MCP peuvent le lire (avec l’outil get_style_guide) et le suivre en rédigeant. Comme c’est un fichier ordinaire de votre dépôt, il est versionné et relu comme tout autre contenu, et propre à chaque client.