Aller au contenu

Référence des types de champs

Les champs se déclarent avec l’objet fields de @menestrel/fields, dans les collections et singletons de cms.config.ts. Cette page détaille chaque type : ses options, ce que l’éditeur voit dans l’admin, et la valeur que ton code Astro reçoit. La vue d’ensemble du schéma (locales, sync, diff) est dans Modéliser le contenu.

import { collection, defineConfig, fields, singleton } from '@menestrel/fields';
Type Rôle Options propres
text Texte court d’une ligne default, min, max, pattern, placeholder
textarea Texte long sans mise en forme default, min, max, rows
richtext Texte mis en forme features
number Nombre entier ou décimal default, min, max, integer, step, unit
boolean Interrupteur oui/non default, trueLabel, falseLabel
select Choix dans une liste fermée options, multiple, default
date Date seule default, min, max
datetime Date et heure, en UTC default, min, max
image Image de la médiathèque aucune
file Fichier téléchargeable accept
relation Lien vers des entrées d’une collection to, many
group Sous-champs regroupés fields
repeater Liste ordonnée de blocs fields, min, max, itemLabel
slug Adresse de la page from
seo Groupe SEO préfabriqué aucune

Tous les types acceptent ces options, sauf exceptions listées juste après.

Option Type Défaut Description
label string | { fr, en } clé capitalisée Libellé dans l’admin. C’est la langue de l’interface (toujours fr et en), indépendante des locales de contenu. Une chaîne simple est dupliquée dans les deux langues.
help string | { fr, en } aucun Texte d’aide affiché sous le champ.
required boolean false S’évalue par locale : publier une langue exige les champs requis dans cette langue. Un brouillon peut rester incomplet.
localized boolean false Une valeur par langue de contenu. Non traduit, le champ garde une seule valeur pour toutes les langues.
default typé par champ null Appliqué à la création d’une entrée dans l’admin, jamais injecté en lecture : une valeur absente reste absente.

Exceptions voulues :

  • boolean refuse required : un interrupteur a toujours un état. Son default vaut false, pas null.
  • group et repeater ne portent ni required, ni localized, ni default : leurs sous-champs déclarent leurs propres règles.
  • slug est toujours requis et sa localisation est héritée du champ source.
  • richtext et image n’ont pas de default : un document TipTap ou une image par défaut n’aurait pas de sens éditorial.
  • seo n’accepte que label et help : ses sous-champs sont figés.

Texte court d’une ligne : titre, nom, référence. 320 caractères maximum par défaut.

Option Type Description
default string Valeur à la création.
min number Longueur minimale.
max number Longueur maximale (défaut : 320).
pattern string Source d’une expression régulière, gardée en chaîne pour que la définition reste du JSON.
placeholder string | { fr, en } Texte indicatif dans le champ vide.
titre: fields.text({ label: 'Titre', localized: true, required: true, max: 120 }),
reference: fields.text({
label: 'Référence',
pattern: '^[A-Z]{2}-\\d{4}$',
placeholder: 'ST-0042',
}),

Dans l’admin : un champ de saisie d’une ligne, avec le placeholder éventuel.

Côté Astro : une string.

Texte long sans mise en forme : résumé, extrait, description. Pour du gras, des titres ou des liens, prends richtext.

Option Type Description
default string Valeur à la création.
min number Longueur minimale.
max number Longueur maximale (défaut : 20 000).
rows number Hauteur de la zone de saisie en lignes (défaut : 5).
resume: fields.textarea({
label: { fr: 'Résumé', en: 'Summary' },
help: { fr: 'Affiché sur les cartes de la liste.', en: 'Shown on the list cards.' },
localized: true,
max: 300,
rows: 3,
}),

Dans l’admin : une zone de texte multiligne, sans barre d’outils.

Côté Astro : une string.

Texte mis en forme, sur un périmètre fermé de huit fonctionnalités. Tout ce qui sort de la liste blanche (nœud, marque, attribut) est rejeté à la validation, côté client comme côté serveur : pas de HTML arbitraire en base.

Option Type Description
features RichtextFeature[] Sous-ensemble des huit fonctionnalités. Défaut : toutes.

Les huit valeurs possibles de features :

Feature Ce qu’elle active
heading Titres H2 et H3 uniquement. Le H1 appartient au layout, pas au contenu.
bold Gras.
italic Italique.
bullet_list Liste à puces.
ordered_list Liste numérotée.
link Liens. Schémas acceptés : http(s), mailto:, tel:, chemins internes (/...) et ancres (#...).
image Images de la médiathèque, avec alt spécifique à l’emplacement.
video Vidéos YouTube ou Vimeo, référencées par identifiant.
contenu: fields.richtext({ label: 'Contenu', localized: true }),
citation: fields.richtext({
label: 'Citation',
features: ['bold', 'italic', 'link'],
}),

Dans l’admin : un éditeur TipTap dont la barre d’outils ne montre que les fonctionnalités déclarées.

Côté Astro : un document structuré à rendre avec <RichText />. Un nœud hors liste blanche est ignoré avec un avertissement de build, jamais rendu tel quel.

Nombre entier ou décimal : prix, surface, durée.

Option Type Description
default number Valeur à la création.
min number Borne basse.
max number Borne haute.
integer boolean true : n’accepte que des entiers (défaut : false).
step number Pas des flèches de saisie, affichage seulement (défaut : 1 si integer, sinon 0.01).
unit string Unité affichée à côté du champ ('€', 'm²', 'h'). Jamais stockée.
prix: fields.number({
label: { fr: 'Prix à partir de', en: 'Starting price' },
unit: '',
min: 0,
}),
duree_jours: fields.number({ label: 'Durée', unit: 'jours', integer: true, min: 1 }),

Dans l’admin : une saisie numérique avec l’unité affichée à côté. La virgule française est acceptée puis normalisée en point. Un champ vidé reste absent, jamais transformé en zéro implicite.

Côté Astro : un number.

Interrupteur oui/non : mise en avant, disponibilité. required est interdit sur ce type, et le compilateur le refuse : un interrupteur a toujours un état.

Option Type Description
default boolean État à la création (défaut : false).
trueLabel string | { fr, en } Libellé de l’état activé.
falseLabel string | { fr, en } Libellé de l’état désactivé.
en_avant: fields.boolean({
label: { fr: 'Mettre en avant', en: 'Feature' },
trueLabel: { fr: 'Sur la page d’accueil', en: 'On the home page' },
falseLabel: { fr: 'Liste uniquement', en: 'List only' },
}),

Dans l’admin : un interrupteur, avec les libellés d’état s’ils sont déclarés.

Côté Astro : un boolean.

Choix dans une liste fermée, simple ou multiple. La validation n’accepte que les valeurs déclarées : pas de valeur orpheline en base.

Option Type Description
options Array<string | { value, label? }> Obligatoire, non vide. Une chaîne nue est une valeur qui sert aussi de libellé. Valeurs de 1 à 64 caractères, uniques.
multiple boolean true : plusieurs choix, stockés en tableau sans doublon (défaut : false).
default string | string[] Valeur(s) à la création.
statut: fields.select({
label: 'Statut',
options: [
{ value: 'neuf', label: { fr: 'Neuf', en: 'New' } },
{ value: 'occasion', label: { fr: 'Occasion', en: 'Used' } },
],
default: 'neuf',
}),
poses: fields.select({
label: 'Types de pose',
options: ['murale', 'plafond', 'encastree'],
multiple: true,
}),

Dans l’admin : une liste déroulante, ou une sélection multiple si multiple est vrai.

Côté Astro : une string, ou un string[] si multiple.

Date seule, sans heure : date de publication, d’événement. Stockée au format YYYY-MM-DD. La validation vérifie que la date existe vraiment dans le calendrier : un 30 février est rejeté.

Option Type Description
default string Au format YYYY-MM-DD.
min string Date plancher, même format.
max string Date plafond, même format.
date_evenement: fields.date({ label: 'Date', required: true, min: '2020-01-01' }),

Dans l’admin : un sélecteur de date.

Côté Astro : une string au format YYYY-MM-DD.

Date et heure. Stockée en UTC, au format YYYY-MM-DDTHH:mm:ssZ : le Z final est obligatoire, pas d’heure locale ambiguë en base.

Option Type Description
default string Au format YYYY-MM-DDTHH:mm:ssZ.
min string Instant plancher, même format.
max string Instant plafond, même format.
publie_le: fields.datetime({ label: { fr: 'Publié le', en: 'Published at' } }),

Dans l’admin : une saisie date et heure dans le fuseau du navigateur, avec le fuseau affiché sous le champ. La conversion vers l’UTC est faite à l’enregistrement.

Côté Astro : une string ISO en UTC, prête pour new Date(...).

Image choisie dans la médiathèque du projet. Aucune option propre, pas de default. Le texte alternatif principal vit sur l’asset, dans la médiathèque ; chaque emplacement peut le surcharger.

visuel: fields.image({ label: 'Visuel', required: true }),

Dans l’admin : un bouton qui ouvre la médiathèque, un aperçu de l’image choisie et un champ d’alt spécifique à cet emplacement.

Côté Astro : un objet AssetRef résolu :

{
src: string;
mime: string;
bytes: number;
width: number | null;
height: number | null;
alt: string; // alt de l'asset, ou la surcharge de l'emplacement
focal_x: number | null; // point focal, utilisé en object-position
focal_y: number | null;
variants?: Array<{ width: number; format: 'avif' | 'webp' | 'jpg'; url: string }>;
}

Les variantes sont générées aux largeurs 400, 800, 1200, 1600 et 2048 px. Le composant <CmsImage /> en tire un <picture> responsive, point focal compris.

Fichier téléchargeable : PDF, archive, catalogue.

Option Type Description
accept string[] Types MIME autorisés. Le contrôle réel a lieu à l’upload.
catalogue: fields.file({ label: 'Catalogue PDF', accept: ['application/pdf'] }),

Dans l’admin : un bouton de choix dans la médiathèque, restreint aux types déclarés.

Côté Astro : le même objet AssetRef que pour image, sans variants ni point focal utile : src, mime, bytes suffisent pour un lien de téléchargement.

Lien vers une ou plusieurs entrées d’une autre collection : services mis en avant, articles liés, auteur d’un billet.

Option Type Description
to string Obligatoire. Clé d’une collection du même config. Jamais un singleton : le compilateur le refuse.
many boolean true : liste ordonnée d’entrées, sans doublon (défaut : false, une seule entrée).
services_en_avant: fields.relation({
label: { fr: 'Services mis en avant', en: 'Featured services' },
to: 'services',
many: true,
}),

Dans l’admin : une recherche parmi les entrées de la collection cible, avec le statut de chaque entrée (publiée ou brouillon). En multiple, la liste choisie se réordonne.

Côté Astro : un id d’entrée (string), ou un tableau d’ids si many. Le loader les réécrit en ids de store de la même locale : getEntry('services', id) fonctionne sans helper. Une cible dépubliée est omise avec un avertissement (relation simple) ou filtrée de la liste (relation multiple). Détail dans la référence du loader.

Sous-champs regroupés visuellement : une adresse, un bloc horaires. Un groupe est structurel : ni required, ni localized, ni default. Ses sous-champs portent leurs propres règles, y compris leur traduction.

Option Type Description
fields Record<string, Field> Obligatoire. Les sous-champs, dans l’ordre du formulaire.
adresse: fields.group({
label: 'Adresse',
fields: {
rue: fields.text({ label: 'Rue', required: true }),
ville: fields.text({ label: 'Ville', required: true }),
code_postal: fields.text({ label: 'Code postal', pattern: '^\\d{5}$' }),
},
}),

Dans l’admin : un bloc repliable qui contient les sous-champs.

Côté Astro : un objet imbriqué : entry.data.adresse.ville.

Liste ordonnée de blocs de même forme : témoignages, étapes, questions fréquentes.

Option Type Description
fields Record<string, Field> Obligatoire. La forme de chaque élément.
min number Nombre minimal d’éléments.
max number Nombre maximal d’éléments.
itemLabel string Clé d’un sous-champ affichée comme titre de l’élément replié.
temoignages: fields.repeater({
label: { fr: 'Témoignages', en: 'Testimonials' },
itemLabel: 'auteur',
max: 6,
fields: {
auteur: fields.text({ label: 'Auteur', required: true, max: 80 }),
citation: fields.textarea({ label: 'Citation', localized: true, required: true, rows: 3 }),
},
}),

Chaque élément reçoit un identifiant interne stable (_id) : réordonner ou traduire ne mélange jamais les blocs entre eux.

Dans l’admin : des blocs repliables à ajouter, dupliquer, supprimer (avec annulation) et réordonner, par boutons ou au clavier. Le bouton d’ajout se désactive à max.

Côté Astro : un tableau d’objets, chacun avec son _id et ses sous-valeurs.

L’adresse de la page. Obligatoire dans chaque collection (exactement un, à la racine), interdit dans un singleton : un contenu unique a déjà son adresse. Sa clé est imposée : slug.

Option Type Description
from string Obligatoire. Clé d’un champ text à la racine de la même collection.
label string | { fr, en } Défaut : « Adresse de la page ».
help string | { fr, en } Texte d’aide.

Le raccourci slugFrom sur la collection évite la déclaration explicite :

services: collection({
slugFrom: 'titre', // équivaut à slug: fields.slug({ from: 'titre' })
fields: {
titre: fields.text({ label: 'Titre', localized: true, required: true }),
},
}),

Un slug est toujours requis. Sa localisation suit le champ source : un titre traduit donne un slug par langue. La valeur accepte minuscules, chiffres et tirets (stores-bannes), 160 caractères maximum.

Dans l’admin : le slug suit le champ source à mesure de la frappe, tant qu’il n’est pas figé. Une édition manuelle le fige ; un cadenas permet de le refaire suivre.

Côté Astro : une string (le slug nu). Il compose aussi l’id d’entrée du loader : fr/stores-bannes.

Groupe préfabriqué pour le référencement, à poser tel quel : ses quatre sous-champs sont figés par la version du package. À la racine uniquement, une occurrence par collection ou singleton.

Option Type Description
label string | { fr, en } Défaut : « Référencement » / « SEO ».
help string | { fr, en } Texte d’aide.
seo: fields.seo(),

Les sous-champs, dans l’ordre du formulaire :

Sous-champ Type Règles
title text Traduit, 70 caractères max.
description textarea Traduit, 160 caractères max.
image image Image de partage (Open Graph).
noindex boolean Masquer la page des moteurs de recherche. Défaut : faux.

Dans l’admin : un bloc repliable avec un aperçu façon résultat de recherche et des compteurs 70/160 qui passent au rouge en cas de dépassement.

Côté Astro : un objet à passer à <CmsSeo />, qui écrit <title>, la meta description, le canonical et les balises Open Graph.

Rappel des règles structurelles, vérifiées à la compilation avant tout appel réseau :

  • Clés de champs en minuscules : ^[a-z][a-z0-9_]{0,63}$. id est réservée ; slug est réservée au type slug.
  • slug et seo vivent à la racine, jamais dans un group ou un repeater.
  • Trois niveaux maximum de conteneurs imbriqués.
  • 200 champs maximum par collection (sous-champs compris), 100 collections et singletons par projet.

Le pourquoi de ces règles et le fonctionnement du sync sont dans Modéliser le contenu.