Legal pages
Seeded privacy policy, terms and cookie policy as editable Lexical documents, with tables, a live cookie table and effective-date versioning.
Most consent tools stop at the banner and leave you to paste a policy into a rich text field. Payload Consent seeds three real documents instead, as ordinary Payload documents with drafts, versions and localization, and keeps the cookie policy's table generated from the tracker list so it cannot go stale.
Turn the whole collection off with legalPages: false if you already have one; the banner then takes plain URLs instead of relationships.
The collection
legal-pages, with drafts enabled.
| Field | Notes |
|---|---|
| Title | Localized. |
| Slug | URL segment. The banner links to /legal/<slug>; render that route yourself. |
| Kind | privacy, terms, cookies, subprocessors, dpa, other. Only privacy and cookies feed documentsVersion. |
| Effective date | Publishing a privacy or cookie policy with a new effective date is what re-prompts visitors. |
| Show in footer | A convenience flag for your own footer query. |
| Content | Lexical, with tables and two custom blocks. |
Read is public; create, update and delete follow access.manage.
What gets seeded
With legalPages on and seed.company set, five documents are created into an empty collection and published — three, when the processor register is off:
| Document | Based on |
|---|---|
| Privacy Policy | GDPR Article 13/14 structure: what we collect and why with a legal-basis table, retention table, recipients, transfers, your rights, contact and DPO. |
| Cookie Policy | structure adapted from General Legal (CC0), with the live cookie table block in place of a hand-written list. |
| Terms of Service | the Common Paper Cloud Service Agreement model (CC BY 4.0): key terms, order form terms, the usual service, payment, liability and termination clauses. |
| Sub-processors | the public list your customers rely on under Art. 28(2), with the notice period, the objection route and a change log. |
| Data Processing Agreement | modelled on the Common Paper DPA (CC BY 4.0) and the EU SCC annex structure: cover page, nine clauses, Annex I, Annex II (TOMs) and Annex III generated from the register. |
seed.documents narrows which of the three you get. seed.locale seeds them into one locale.
These are a well-structured starting point written from the company details you passed, not legal advice. They name your company, your jurisdiction and your actual trackers, which puts them ahead of most templates — but a lawyer still has to read them against what your product really does.
Tables
Legal documents are made of tables — what we collect, how long we keep it, who receives it — but tables are not part of Payload's default Lexical feature set. The plugin enables EXPERIMENTAL_TableFeature on this collection's editor, which does two things: editors get the usual table controls in the admin, and the seeded markdown's GFM pipe tables are converted into real table nodes instead of landing as literal | — | — | text.
Rendering is handled by legalPageConverters (below). It replaces Lexical's built-in table converter with a semantic one: a <thead> and <tbody> split by header state, scope-annotated <th>, colSpan/rowSpan, and a horizontally scrollable wrapper so a five-column retention table cannot break a narrow layout. It sets no colours or padding of its own, so the table inherits your typography and works in dark mode. Style it through [data-lexical-table]; the wrapper carries [data-lexical-table-container].
The three blocks
Cookie table renders the current trackers, grouped by category or by vendor, with an optional duration column. This is the block that keeps the cookie policy honest: add a tracker in the admin and the policy gains a row, with no editing.
Policy version line renders Version <documentsVersion>, effective <date> — a small thing that makes it possible to answer "which version of your policy did I agree to?".
Processor table renders the register in one of five shapes — recipients, transfers, sub-processors, Annex III or a change log. See Processors & DPA.
All three are exported as CookieTableBlock, ProcessorTableBlock and PolicyVersionBlock if you want them in another collection's editor.
Rendering a legal page
import { getPayload } from 'payload'
import { headers } from 'next/headers'
import { notFound } from 'next/navigation'
import { RichText } from '@payloadcms/richtext-lexical/react'
import config from '@payload-config'
import { legalPageConverters } from '@payload-solutions/plugin-consent/rsc'
import { getConsentConfig, getProcessorTableData } from '@payload-solutions/plugin-consent/server'
export default async function LegalPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const payload = await getPayload({ config })
const result = await payload.find({ collection: 'legal-pages', where: { slug: { equals: slug } }, limit: 1 })
const page = result.docs[0]
if (!page) notFound()
const consent = await getConsentConfig(payload, { headers: await headers() })
const processors = await getProcessorTableData(payload)
const effective = new Date(page.effectiveDate).toLocaleDateString('en-GB', { dateStyle: 'long' })
return (
<article>
<h1>{page.title}</h1>
<RichText
converters={({ defaultConverters }) => ({
...defaultConverters,
...legalPageConverters({
categories: consent.categories,
trackers: consent.trackers,
documentsVersion: consent.versions.documentsVersion,
effectiveDate: effective,
...processors,
}),
})}
data={page.content}
/>
</article>
)
}legalPageConverters is the one to spread: it covers tables and all three blocks. If you already have a table converter you are happy with, spread consentBlockConverters instead and keep yours. getCookieTableData(payload) is a lighter alternative to getConsentConfig when all you need is the table's data.
Outside a rich text field, <CookieTable categories={…} trackers={…} /> renders the same markup anywhere, and <CookieTableFromPayload payload={payload} /> is an async Server Component that loads its own data.
Versioning and re-consent
documentsVersion is a hash of the effective dates of the published privacy and cookie policies. That has a deliberate consequence: fixing a typo changes nothing, and visitors are not re-prompted. Bumping the effective date is the explicit signal that the terms materially changed.
Whether that re-prompts anyone is up to Consent settings → Ask again when these change, which includes documents by default. When it does fire, the visitor sees the banner again with their previous choices pre-selected and repromptReason: 'documents' in the store, so your banner can say why it came back.
Drafts do not count. Only published documents feed the hash.
Localization
If your Payload config has localization, the title, slug-adjacent fields and content become localized automatically. The slug itself stays shared. Seed one locale with seed.locale, then translate in the admin — the version hash is locale-independent, so translating does not re-prompt anyone.
Bring your own documents
Two ways to opt out:
legalPages: false— no collection at all. Consent settings then offers Privacy URL and Cookie URL text fields for the banner links.seed.legalPages: false— the collection exists but nothing is seeded, so you start from an empty document.
You can also generate the markdown yourself:
import { buildLegalDocuments, markdownToLegalContent } from '@payload-solutions/plugin-consent/server'
const [privacy] = buildLegalDocuments(company, '2026-01-01', ['privacy'])
const content = await markdownToLegalContent(payload, privacy.markdown)markdownToLegalContent runs any markdown through the legal-pages editor config — tables included — and swaps the {{cookie-table}} and {{policy-version}} marker paragraphs for the corresponding blocks. It also handles {{processor-table:recipients}} and its four siblings. It is the supported way to import an existing policy from markdown.
Extra fields
consentPlugin({
legalPageFields: [{ name: 'reviewedBy', type: 'relationship', relationTo: 'users' }],
})Processors & DPA
The recipients you disclose under Art. 13(1)(e), the transfers under Art. 13(1)(f), and the sub-processor list and DPA your own customers need under Art. 28.
Jurisdictions & Consent Mode
The four consent models, how a visitor's model is resolved, Global Privacy Control, and Google Consent Mode v2.