#Objets
Voici tout ce qu’un thème peut lire. La liste est complète : ce qui n’y figure pas n’existe pas, et rendra une chaîne vide sans prévenir. Chaque champ est donné avec son type, un usage tiré d’un thème livré, et la valeur que le serveur rend réellement.
La source qui fait foi est src/lib/liquid/contexte.ts, complétée par le code
qui la remplit — src/lib/liquid/rendu-site.ts et le rendu public du site. Les
valeurs ci-dessous en sortent, pas d’une intention.
#La table des matières du contexte
| Objet | Forme | Disponible |
|---|---|---|
site | objet | partout |
page | objet | partout |
settings | objet libre | partout |
menu | tableau | partout |
articles | tableau | partout |
annee | entier | partout |
theme_base | chaîne | partout |
section | objet | dans une section seulement |
content_for_layout | chaîne de HTML | dans layout/theme.liquid seulement |
Un snippet appelé par render ne voit rien de tout cela. Le tag isole
complètement la portée : seuls les paramètres passés explicitement entrent. Voir
les tags — c’est la surprise la plus fréquente, et de loin.
#site
L’entreprise à qui appartient le site. Deux champs, et le second n’est pas ce qu’on croit.
site.nomchaîneLe nom du site tel qu’il est saisi dans le CMS. C’est ce qu’on met dans la balise
title, dans le logo textuel de l’en-tête et dans la mention de copyright.
site.adressechaîneLe préfixe des liens internes, pas une adresse absolue. En production il est
vide : le site est servi sous son propre nom de domaine, et un lien interne
n’a besoin d’aucun préfixe. Il ne vaut /s/mon-site que sur l’hôte de
l’administration, c’est-à-dire en développement, où les sous-domaines ne
résolvent pas.
C’est pour cela que les thèmes livrés écrivent {{ site.adresse }}/ et non
{{ site.adresse }} : sans la barre oblique finale, le lien vers l’accueil
serait une chaîne vide en production.
<a class="marque" href="{{ site.adresse }}/">{{ site.nom }}</a>N’écris jamais <a href="{{ site.adresse }}"> seul, ni
{{ site.adresse }}/contact en croyant fabriquer une URL absolue. Le premier
produit href="" en production — le navigateur recharge la page courante ; le
second produit /contact, ce qui se trouve être juste, mais par accident. Pour
un lien interne, écris simplement /contact.
#page
La page en cours de rendu.
page.titrechaîneLe titre saisi dans le CMS. Il sert dans la balise title de la coquille, et les
thèmes livrés le reprennent aussi comme champ caché provenance du formulaire de
contact, pour savoir depuis quelle page la demande a été envoyée.
page.adressechaîne/ pour l’accueil, /le-slug pour les autres. Sans préfixe : contrairement à
site.adresse et aux entrées de menu, ce champ n’est pas préfixé sur l’hôte
d’administration. Il sert à savoir où l’on est, pas à fabriquer un lien.
page.accueilbooléenVrai sur la page d’accueil. Utile pour une coquille qui met un h1 sur l’accueil
et un h2 ailleurs, ou pour n’afficher une barre d’annonce que sur la première
page.
page.demandechaîne"merci", "erreur", ou vide. C’est l’état du dernier envoi de formulaire, lu
depuis ?demande= dans l’adresse.
Ce champ mérite qu’on s’y arrête, parce qu’il porte à lui seul tout le retour
utilisateur d’un formulaire de contact. Le formulaire poste vers /api/contacts,
la route enregistre la demande puis redirige vers la page avec
?demande=merci (ou ?demande=erreur), et la page se rend à nouveau. La section
Contact peut donc remercier sans une ligne de JavaScript — ce qui tombe bien,
puisque assets/ n’accepte pas de .js.
{%- if page.demande == 'merci' -%}
<p class="message message-ok" role="status">
Merci, votre message est bien arrivé. Nous vous répondons vite.
</p>
{%- elsif page.demande == 'erreur' -%}
<p class="message message-ko" role="alert">
Le message n'a pas pu être envoyé. Vérifiez votre e-mail ou votre
téléphone, puis réessayez.
</p>
{%- endif -%}
<form class="formulaire" method="post" action="/api/contacts">
…
<input type="hidden" name="provenance" value="{{ page.titre }}">
</form>role="status" et role="alert" ne sont pas décoratifs : ils font annoncer le
message par un lecteur d’écran au moment où il apparaît. Un message de
confirmation qu’on ne peut pas entendre n’est pas un message de confirmation.
#settings
Les réglages globaux du thème — couleurs, largeur, typographie. Ils sont lus
dans config/settings_data.json, sous la clé current, et l’accès se fait par
identifiant : {{ settings.accent }} pour le réglage dont l’id est accent.
{
"current": {
"encre": "#15171c",
"papier": "#ffffff",
"accent": "#4364df",
"largeur": 1160,
"boutons": "pilule",
"sections": {
"entete": { "settings": { "cta_label": "Nous contacter" } }
}
}
}La clé sections est retirée de settings avant le rendu : elle ne contient
pas des réglages globaux mais ceux des sections posées par la coquille.
{{ settings.sections }} rend donc du vide, et c’est voulu.
Les valeurs par défaut déclarées dans config/settings_schema.json ne sont pas
fusionnées dans settings. Un réglage global absent de settings_data.json
rend du vide, même s’il a un "default" dans le schéma — contrairement aux
réglages de section, où la fusion a bien lieu.
Concrètement : ajouter un réglage global à un thème demande de l’ajouter aux
deux fichiers. Sinon --accent: ; part dans la feuille de style, la
déclaration est invalide, et toute la page perd sa couleur d’accent sans qu’une
seule erreur soit levée. Voir les réglages globaux.
#section
L’objet de la section en cours. Il n’existe que pendant le rendu d’un fichier
de sections/ — ni dans la coquille, ni dans un snippet appelé par render.
section.idchaîneL’identifiant de l’instance. Pour une section posée par un template, c’est la
clé qu’elle porte dans templates/*.json ; pour une section posée par la
coquille avec section, c’est son nom de fichier. Deux instances de la même
section dans une page ont le même type et des id différents — c’est ce qui
permet de fabriquer une ancre unique.
section.typechaîneLe nom du fichier, sans son extension : sections/prestations.liquid donne
prestations.
section.settingsobjetLes réglages de cette instance, accessibles par leur id. Ils sont fusionnés
avec les valeurs "default" du {% schema %} : ce qui n’a pas été renseigné
prend le défaut. Sans cette fusion, ajouter un réglage à un thème déjà installé
afficherait du vide partout, et l’auteur croirait son défaut ignoré.
section.blockstableauLes blocs répétables de la section, dans l’ordre défini par block_order. Vide
si la section n’en déclare pas. Chaque bloc porte id, type et settings.
<ol class="etapes-liste">
{%- for bloc in section.blocks -%}
<li>
<span class="etape-rang">{{ forloop.index }}</span>
<div>
<h3>{{ bloc.settings.titre }}</h3>
{%- if bloc.settings.texte != blank -%}<p>{{ bloc.settings.texte }}</p>{%- endif -%}
</div>
</li>
{%- endfor -%}
</ol>forloop.index vient de Liquid, pas de Webcosa : dans une boucle for, l’objet
forloop donne index (à partir de 1), index0, first, last, length et
rindex. Numéroter les étapes en CSS aurait été possible ; le faire ici permet
au numéro d’être stylé comme un élément à part entière.
Quand une section porte plusieurs types de blocs, on teste bloc.type :
{%- for bloc in section.blocks -%}
{%- if bloc.type == 'image' -%}
<img src="{{ bloc.settings.fichier }}" alt="{{ bloc.settings.legende | escape }}">
{%- else -%}
<p>{{ bloc.settings.texte }}</p>
{%- endif -%}
{%- endfor -%}Le détail de la déclaration est sur la page des blocs.
#menu
Le menu principal du site, tel qu’il est composé dans le CMS. C’est un tableau d’objets à deux champs, et rien d’autre : ni sous-menus, ni état actif, ni identifiant.
menu[].labelchaîneLe texte du lien.
menu[].adressechaîneL’adresse. Les liens internes sont déjà préfixés — contrairement à
page.adresse — donc on les écrit tels quels. Les liens externes sont laissés
intacts.
<nav class="menu" aria-label="Menu principal">
{%- for lien in menu -%}
<a href="{{ lien.adresse }}">{{ lien.label }}</a>
{%- endfor -%}
</nav>Pour marquer l’entrée courante, compare avec page.adresse — mais souviens-toi
que menu[].adresse est préfixé et page.adresse non. En production les deux
préfixes sont vides et la comparaison directe fonctionne ; sur l’hôte
d’administration, elle échoue silencieusement. Un thème qui en dépend paraîtra
cassé en développement seulement.
#articles
Les derniers articles publiés du site, tous blogs confondus, du plus récent au plus ancien. Le tableau est plafonné à 12 entrées, quelle que soit la valeur demandée par la section.
articles[].titrechaîneLe titre de l’article.
articles[].extraitchaîneLe chapeau saisi dans le CMS. Vide s’il n’y en a pas — d’où le test
!= blank avant de rendre le paragraphe.
articles[].adressechaîneL’adresse de l’article, de la forme /blog/<blog>/<slug>, déjà préfixée.
articles[].imagechaîneL’adresse de l’image de couverture, vide s’il n’y en a pas. C’est une URL
complète servie par la plateforme, pas un fichier d’assets/ : ne lui applique
pas asset_url.
articles[].datechaîneLa date de publication, destinée au filtre date_fr.
Ce champ est actuellement toujours vide. Le rendu public le remplit avec une
chaîne vide au lieu de la date de publication lue en base. La conséquence est
discrète : {{ article.date | date_fr }} rend du vide, donc la ligne de date
d’une carte d’article est simplement absente. Le champ existe, son contrat tient,
sa valeur ne suit pas encore. Écris ton thème comme s’il était rempli — le jour
où il le sera, ton thème l’affichera sans modification.
{%- if articles.size > 0 -%}
<div class="grille grille-3">
{%- for article in articles limit: section.settings.nombre -%}
<article class="carte">
{%- if article.image != blank -%}
<a href="{{ article.adresse }}"><img src="{{ article.image }}" alt="" loading="lazy"></a>
{%- endif -%}
<h3><a href="{{ article.adresse }}">{{ article.titre }}</a></h3>
{%- if article.extrait != blank -%}<p>{{ article.extrait }}</p>{%- endif -%}
<span class="date">{{ article.date | date_fr }}</span>
</article>
{%- endfor -%}
</div>
{%- endif -%}Trois habitudes à reprendre de cet extrait. Le if articles.size > 0 évite un
titre de section suivi du vide sur un site sans blog. Le limit: de la boucle
for prend sa valeur d’un réglage range, ce qui rend le nombre de cartes
réglable sans toucher au code. Et loading="lazy" sur les images n’est pas un
détail : une page de douze photos qui les charge toutes d’un coup perd deux
secondes sur la note de vitesse.
#annee
L’année en cours, en entier. Elle est calculée au moment du rendu, pas à la compilation du thème.
<span>© {{ annee }} {{ site.nom }}</span>Pourquoi l’exposer alors qu’un thème pourrait s’en passer ? Parce que sans elle, chaque thème écrirait l’année en dur dans son pied de page, et tous les sites afficheraient 2025 en février 2026. Un pied de page périmé est le genre de détail qui fait douter un visiteur de la fraîcheur de tout le reste.
Le calcul a lieu à chaque rendu de page, jamais à la génération d’un cache. Une page qui serait mise en cache pour la nuit du 31 décembre garderait sinon l’année de sa génération. Le rendu public étant dynamique, la valeur est toujours juste.
#theme_base
La base des adresses des fichiers d’assets/. Sa valeur est
/theme-assets/<id-du-thème> — un identifiant de thème, pas son nom.
Ne l’utilise pas directement. Un thème peut techniquement écrire
{{ theme_base }}/theme.css et court-circuiter asset_url, mais ce n’est pas
une promesse qu’on tient : le jour où la route des assets changera, seul
asset_url suivra. Ce champ est documenté parce qu’il est visible, pas parce
qu’il est fait pour être écrit.
Écris {{ 'theme.css' | asset_url }}. Voir les filtres.
#content_for_layout
Le HTML des sections de la page, déjà rendu. Il n’existe que dans
layout/theme.liquid, et c’est le point d’insertion du contenu : la coquille
décide où il tombe et pose autour ce qu’elle veut.
{% section 'entete' %}
<main id="contenu">{{ content_for_layout }}</main>
{% section 'pied' %}C’est du HTML déjà assemblé : ne lui applique ni escape, ni lignes, ni aucun
filtre de texte — tu afficherais le balisage au visiteur. La coquille est
détaillée sur sa propre page.
#Ce qui n’est pas là
Ni product, ni cart, ni collection, ni customer, ni checkout. Ce n’est
pas un oubli et ce n’est pas provisoire : Webcosa fait des sites vitrines, et
exposer des objets systématiquement vides ferait écrire des thèmes contre une API
qui ne rend jamais rien. La page des absents explique le
raisonnement en détail et dit quoi faire à la place.

