Configuratiereferentie
Alles wat pullpress.config.yml begrijpt: het ene bestand dat van je repo een CMS maakt.
Het configuratiebestand staat in de hoofdmap van je repository en heeft versiebeheer zoals al de rest. Anders dan contentwijzigingen (die een wijzigingsverzoek openen om na te kijken), is het opslaan van de configuratie een actie van een beheerder die rechtstreeks naar je standaardbranch commit, zodat een nieuw contentmodel meteen werkt (en gecontroleerd wordt voor het geschreven wordt). Het heeft een handvol sleutels op het hoogste niveau, een lijst collecties en een kleine set veldtypes.
Bewerk je liever in de app? De ingebouwde configuratie-editor heeft een visuele modus waarvan de kiezer voor veldtypes elk type groepeert met een beschrijving van één regel, en de YAML-modus controleert voor het opslaan: een tikfout in de syntax wordt aangewezen op de precieze regel en kolom, en structurele problemen worden per veldpad opgesomd. De bestandsnaam is pullpress.config.yml (een extensie .yaml werkt ook).


Sleutels op het hoogste niveau
| Sleutel | Beschrijving |
|---|---|
version | Versie van het configuratieschema. Momenteel altijd 1. |
media.folder | Pad in de repository waar geüploade afbeeldingen gecommit worden, bijvoorbeeld public/uploads. |
media.public_path | URL-voorvoegsel waaronder de gepubliceerde site die map aanbiedt, bijvoorbeeld /uploads. Wordt gebruikt om correcte afbeeldingslinks in je Markdown te schrijven. |
collections | De lijst contentcollecties waarmee redacteuren kunnen werken. Elke collectie heeft een naam, een label, een type en velden. |
components | Optioneel. Een map van herbruikbare, benoemde veldgroepen, waarnaar velden met type: component verwijzen. Definieer een groep één keer en hergebruik ze in meerdere collecties. |
Collectietypes
Een collectie beschrijft één soort content. Haar type bepaalt hoe items overeenkomen met bestanden in de repository.
folder: één bestand per item
Elk item is een eigen Markdown-bestand in de ingestelde map. Redacteuren kunnen items maken, bewerken en verwijderen: ideaal voor blogberichten, nieuwsberichten of teamleden.
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 }Frontmatterformaten. Alle drie de formaten blijven behouden: YAML tussen regels ---, TOML tussen regels +++ (de standaard van Hugo) en een los JSON-object bovenaan het bestand. Bestaande items houden altijd hun formaat, de volgorde van de sleutels en de sleutels die PullPress niet beheert. YAML-frontmatter houdt bij het opslaan ook zijn # comments, anchors en blokstrings met |/>: één veld bewerken geeft een diff die alleen dat veld raakt. (TOML- en JSON-frontmatter houden waarden en volgorde van de sleutels, maar commentaarregels in TOML blijven niet behouden.) Zet format: toml (of json) op een collectie om nieuwe items in dat formaat te maken; de detectie vult dit voor je in als je bestaande content het gebruikt.
Sites met één map per item (Astro content collections, Hugo leaf bundles: content/my-post/index.md) zetten structure: bundle. Voeg media: colocated toe om de afbeeldingen van een item in diens eigen map te bewaren, met een relatieve verwijzing (./photo.jpg), zodat de beeldpijplijn van je generator ze oppikt. De detectie van de repository stelt dit allemaal voor je voor: ze herkent Astro content collections, Hugo leaf- en branch-bundles (plus contentDir en bestanden in data/), Jekyll-collecties uit _config.yml, contentmappen van Next.js en bundles genest per datum, en schrijft de gemaakte labels in de taal van je 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: vaste pagina’s
Een vaste set bestanden die je uitdrukkelijk oplijst. Redacteuren kunnen ze bewerken, maar er geen toevoegen of weghalen: geschikt voor losse pagina’s zoals Over ons of Contact. Elk bestand heeft zijn eigen fields (genest onder dat bestand, niet gedeeld op het niveau van de collectie), zodat elke pagina een andere vorm kan hebben.
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 }De detectie gebruikt dit ook voor sectiepagina’s: de _index.md van een Hugo branch bundle (de landingspagina van een sectie) is geen item van de folder-collectie, dus de detectie verzamelt ze allemaal in een gemaakte files-collectie “Section pages”, te bewerken zoals elke andere pagina, zonder handwerk.
data: één gestructureerd databestand
Eén YAML-, JSON- of TOML-bestand dat volledig via een formulier bewerkt wordt, zonder Markdown-body. Gebruik het voor gestructureerde data die je sjablonen rechtstreeks lezen: openingsuren, navigatiemenu’s, site-instellingen. Sleutels in het bestand die niet in de veldconfiguratie staan, overleven elke keer opslaan, in alle drie de formaten. YAML-bestanden houden ook hun commentaar; TOML- en JSON-bestanden houden hun sleutels en volgorde, maar worden opnieuw opgemaakt.
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 }Sommige databestanden zijn geen map maar één grote lijst: een array bovenaan het bestand, zoals de loader file() van Astro die leest (src/data/authors.json met [ …, … ]), of lijstbestanden van Hugo/Jekyll/Eleventy. Zet root: list en declareer precies één veld van het type list: de items ervan zijn de array op het hoogste niveau van het bestand, bewerkt als herhaalbaar formulier. Items die je niet aanraakt, worden letterlijk teruggeschreven (de volgorde van hun sleutels en eventuele extra sleutels blijven); TOML kan geen array op het hoogste niveau voorstellen, dus dit werkt voor bestanden .json, .yaml en .yml. De detectie stelt deze vorm automatisch voor.
Bestanden in CSV en TSV (Jekyll _data/*.csv, exports uit een spreadsheet) werken net zo: ze zijn altijd tabellen, dus laat een data-collectie met root: list naar het bestand wijzen en som de kolommen op als string-velden van het ene veld list. De kopregel en de volgorde van de kolommen blijven bij het opslaan behouden; elke waarde gaat heen en terug als tekst.
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 met data-items: geen Markdown-body
Soms is elk item pure gestructureerde data zonder artikeltekst: auteurs, teamleden, producten, lijsten met links. Geef een folder-collectie een data-extension (json, yaml, yml of toml) en elk item wordt één databestand dat redacteuren als formulier invullen: hetzelfde maken, bewerken, verwijderen, nakijken en dezelfde geschiedenis als bij Markdown, alleen zonder body. Zo werken collecties met type: 'data' in Astro precies, en de detectie stelt ze voor je voor.
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 }Het hele bestand is de data (het formaat volgt de extensie), en net als bij een data-collectie blijven de volgorde van de sleutels en sleutels die niet in je lijst velden staan, bij elke keer opslaan behouden. Er is geen body-veld, dus voeg er geen toe; relaties kunnen ook naar deze collecties wijzen.
Collectie-opties
Naast name, label, folder en fields neemt een folder-collectie deze optionele sleutels. De detectie vult de meeste voor je in.
| Sleutel | Beschrijving |
|---|---|
structure | flat (standaard) houdt één bestand per item; bundle gebruikt één map per item (folder/slug/index.md). |
media | site (standaard) stuurt uploads naar de mediamap; colocated bewaart ze in de eigen bundlemap van het item, met een verwijzing als ./photo.jpg. colocated vraagt structure: bundle. |
extension | Bestandstype van items: md (standaard), markdown of mdx voor Markdown-pagina’s; html, adoc of org voor content die geen Markdown is (de frontmatter blijft behouden; declareer de body als type: code zodat die als platte tekst bewerkt wordt); of json, yaml, yml of toml om van elk item een databestand zonder body te maken (een map met data-items, zoals Astro data collections). |
format | Frontmatterformaat voor nieuwe items: yaml (standaard), toml of json. Bestaande items houden het formaat dat ze al gebruiken. |
nested | true verzamelt ook items uit submappen (tot vijf niveaus diep), gegroepeerd per map. Standaard uit. |
locales | Extra taalcodes (bijvoorbeeld [fr, en]) die elk item per taal bewerkbaar maken, ingedeeld volgens de sleutel i18n. |
i18n | Waar vertalingen staan: suffix (standaard) gebruikt bestanden naast elkaar (post.fr.md); subfolder gebruikt één map per taal (Hugo content/<lang>/, Astro/Starlight docs/<lang>/). Met subfolder laat je folder wijzen naar de map van de standaardtaal en noem je dat segment in defaultLocale; bijvoorbeeld folder: content/en, defaultLocale: en, locales: [fr, de] zet Franse items in content/fr. |
slug | Sjabloon voor de bestandsnaam van nieuwe items. Standaard {{slug:title}}. Tokens: {{slug:field}} (veld als slug), {{field}}, {{date}}, {{year}}, {{month}}, {{day}}. |
sort | Hoe de lijst items gesorteerd is: { field, order } met order asc of desc. Standaard op het datumveld, nieuwste eerst. |
Een files-collectie somt in plaats daarvan elk bestand op met zijn eigen fields (en een optioneel format per bestand); een data-collectie wijst naar één file en deelt één set fields.
Veldtypes
Elk veld in een collectie wordt een formulierelement in de editor. De gewone opties label, required en default werken op elk type. Twee andere helpen je redacteuren: een tekst help toont een hint onder het veld, en een placeholder toont voorbeeldtekst in een leeg invoerveld. Verplichte velden krijgen een rood sterretje en bij het indienen een vriendelijk overzicht van wat er ontbreekt.
Alleen-lezen velden. Voeg readOnly: true toe om een veld met zijn bewaarde waarde te tonen zonder dat redacteuren het kunnen wijzigen: de naam van een instellingendocument, een vergrendelde slug, een externe id. Het is vergrendeld in zowel de webeditor als de iOS-app, en de server zet bij elke keer opslaan de bewaarde waarde terug, zodat een alleen-lezen veld nooit kan verschuiven, hoe een wijziging ook binnenkomt.
- name: title
type: string
label: Name
readOnly: true # shown, but editors can't change itTekst per taal. De interface past zich aan de gekozen taal van elke redacteur aan, en jij kunt hetzelfde doen voor je eigen teksten in label, help en placeholder. De kale sleutel ziet iedereen; voeg een achtervoegsel _<language> toe (een van en, nl, fr, de, es) om die te vervangen voor redacteuren die die taal gebruiken. Er wordt niets automatisch vertaald: je schrijft elke variant zelf.
- 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| Type | Beschrijving | Voorbeeld |
|---|---|---|
string | Eén regel tekst. | { name: title, type: string, required: true } |
text | Platte tekst over meerdere regels, als tekstvak. | { name: summary, type: text } |
markdown | Opgemaakte tekst in de rustige editor, bewaard als Markdown. | { name: body, type: markdown } |
number | Een geheel of decimaal getal. | { name: price, type: number } |
boolean | Een schakelaar ja/nee. | { name: featured, type: boolean, default: false } |
date | Een kalenderdatum. default: now vult vandaag in. | { name: date, type: date, default: now } |
datetime | Een datum met een tijdstip. | { name: published_at, type: datetime } |
select | Eén keuze uit een vaste lijst, via options. | { name: category, type: select, options: [news, recipes] } |
multiselect | Meerdere keuzes uit een vaste lijst, als vinkjes. | { name: categories, type: multiselect, options: [news, events] } |
url | Een webadres, gecontroleerd als https://… | { name: website, type: url } |
email | Een gecontroleerd e-mailadres. | { name: contact, type: email } |
color | Een kleurkiezer, bewaard als #rrggbb. | { name: accent, type: color } |
image | Een geüploade afbeelding, gecommit naar de mediamap. | { name: cover, type: image } |
list | Een herhaalbare lijst items; of is een velddefinitie voor het type item. | { name: tags, type: list, of: { type: string } } |
object | Een groep geneste velden, gedefinieerd met fields. | { name: cta, type: object, fields: [...] } |
relation | Een verwijzing naar een item in een andere folder-collectie (bewaart de slug). | { name: author, type: relation, collection: team } |
blocks | Stapelbare secties met verschillende vormen (elk item draagt een sleutel _type; overschrijf de sleutel met typeKey). | { name: sections, type: blocks, blocks: [...] } |
hidden | Niet zichtbaar voor redacteuren; wordt meegeschreven met een vaste waarde (bijvoorbeeld een layoutsleutel). | { name: layout, type: hidden, default: post } |
code | Een codeblok in monospace, met een optionele taalhint. | { name: snippet, type: code, language: js } |
map | Een locatie, bewerkt als breedte- en lengtegraad (bewaard als 'lat,lng'). | { name: location, type: map } |
compute | Een alleen-lezen waarde afgeleid van een sjabloon; elk {{token}} is de naam van een veld op hetzelfde niveau. | { name: ref, type: compute, template: '{{brand}}-{{sku}}' } |
keyvalue | Willekeurige paren sleutel/waarde, bijvoorbeeld extra metadata. | { name: meta, type: keyvalue } |
seo | SEO-paneel: titel + beschrijving (met lengtehints), canonieke URL, OG-afbeelding en noindex, met een voorbeeld van het zoekresultaat en van de JSON-LD. | { name: seo, type: seo } |
component | Klapt een herbruikbare veldgroep uit de map components op het hoogste niveau uit. | { name: cta, type: component, component: callToAction } |
list en object zijn te combineren: een list van object geeft je herhaalbare groepen velden, zoals de openingsuren per week in het voorbeeld hierboven.
Validatieregels
Naast required kan een veld precies de beperkingen overnemen die het schema van je generator oplegt, zodat fouten in de editor met een vriendelijke melding opgevangen worden, en niet door een mislukte build. Getalvelden nemen min, max en integer: true; tekstvelden (string, text, markdown, code) nemen minLength, maxLength en een reguliere expressie in pattern; list en multiselect nemen minItems en maxItems. Overtredingen verschijnen tijdens het schrijven bij het veld (een optioneel veld mag leeg blijven; de regels gelden zodra er iets in staat), en dezelfde regels draaien bij elke keer opslaan op de server.
- { 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 }blocks definiëren
Met een veld blocks stapelen redacteuren secties met verschillende vormen (een hero, een citaat, een galerij…). Som de sectietypes op onder blocks; elk is een benoemde groep fields (met een optioneel label), net als een 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 }Elk item dat een redacteur toevoegt, legt onder een sleutel _type vast welk blok het is (hero, quote, …), zodat je generator weet hoe hij elke sectie moet weergeven. Verwacht je schema een andere sleutel (de z.discriminatedUnion('type', …) van Astro, of content die uit Decap CMS komt, dat ze als type bewaart), zet dan typeKey op het veld (typeKey: type) en PullPress leest, schrijft en controleert die sleutel. De import uit Decap zet dit automatisch, zodat geïmporteerde items ongewijzigd blijven werken.
Voorwaardelijke velden (showIf)
Elk veld kan showIf declareren om alleen te verschijnen als een veld op hetzelfde niveau overeenkomt. Verborgen velden worden leeggemaakt (zodat ze nooit in de frontmatter belanden), en de regel required van een voorwaardelijk veld geldt alleen zolang het zichtbaar is.
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 workGeneste collecties
Zet nested: true op een folder-collectie om ook items uit submappen te verzamelen (tot vijf niveaus diep) en ze gegroepeerd per map te tonen: handig voor documentatiebomen. Zoeken en de kiezers voor relaties pikken de geneste items automatisch op.
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 }Herbruikbare veldcomponenten
Definieer één keer een benoemde veldgroep onder de map components op het hoogste niveau en zet ze in elke collectie met een veld component. Bij het laden klapt ze uit tot een gewone objectgroep, zodat een wijziging aan de groep elke collectie bijwerkt die ze gebruikt.
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 }Waar media staan (en waarom niet in S3)
Uploads worden in je repository gecommit, naast de content die ernaar verwijst. Dat is een bewuste keuze, geen ontbrekende functie: de repo blijft de enige bron van waarheid, afbeeldingen reizen met hun berichten mee door branches, nakijken en geschiedenis, en wie de repo kloont, heeft de volledige site, zonder tweede systeem om te back-uppen of voor te betalen.
Een CDN of verkleinen van afbeeldingen nodig? Laat je generator of host dat bij de build doen (de beeldpijplijnen van Astro en Next.js, de image CDN van Netlify, fetch-URL’s van Cloudinary). PullPress bewaart het origineel in Git; je build maakt het snel. Uploads worden voor het versturen in de browser verkleind tot webformaat, zelfs een telefoonfoto van 20–50 MB, wat repo’s in de praktijk ruim klein houdt. Externe objectopslag (S3 en co.) zou de garantie “je repo is alles” breken, dus die plannen we niet.
AI-vindbaarheid (llms.txt)
PullPress kan een llms.txt in de hoofdmap van je repository maken: een index in platte tekst van je site (de naam, de URL en per folder-collectie een lijst titels) die AI-antwoordmachines lezen om je site correct samen te vatten en te citeren. Ze wordt deterministisch opgebouwd uit je contentmodel (geen AI-aanroep, er verlaat niets je repo).
Beheerders vinden dit in het paneel Instellingen → AI-vindbaarheid (GEO) van een site, dat ook een score voor gereedheid toont: of je site-URL ingesteld is, of llms.txt er staat, of je gepubliceerde content en velden voor beschrijving/SEO hebt, en (als je site-URL bereikbaar is) of de homepage een meta description, Open Graph-tags en JSON-LD toont. Klik op llms.txt bijwerken om het bestand (opnieuw) te maken; het wordt gecommit zoals elke andere wijziging: langs het nakijken, of op sites die direct publiceren rechtstreeks naar je standaardbranch.
brandvoice.md hieronder aan: de merkstem bepaalt hoe AI voor je schrijft, terwijl llms.txt AI helpt te vinden en citeren wat je al publiceerde. Het beheert geen robots.txt of sitemap; laat die aan je generator over.Merkstem voor AI-agents
Zet een optionele brandvoice.md in de hoofdmap van je repository om de toon van de site, gewenste en verboden woorden en het schrijfniveau te beschrijven. AI-agents die via MCP gekoppeld zijn, kunnen het lezen (via de tool get_style_guide) en volgen bij het opstellen van content. Omdat het een gewoon bestand in je repo is, heeft het versiebeheer en wordt het nagekeken zoals elke andere content, en per klant uniek.