Fonts
Manage next/font local fonts (sans, serif, mono, display) from the CMS.
Upload and pick the active sans, serif, mono, and display fonts from the admin, then ship them to disk so Next.js renders them with next/font/local.
Overview
Fonts lets admins manage the site's typefaces from the CMS instead of hard-coding them in the codebase. Each typeface — a variable font or a set of weight files — is uploaded into a Font collection, and the active sans, serif, mono, and display roles are chosen in the admin, either from the active design set (when paired with Styles) or from a standalone fontSet global. A build-time download step then turns those CMS selections into real local fonts, so the rendered site gets every next/font/local performance benefit (subset preloading, layout-shift prevention).
How it works
next/font only loads fonts from the filesystem, so it can't fetch them from object storage at request time. This feature bridges that gap with three collections plus a build step:
- Three collections. The visible
Fontcollection is the typeface (e.g. "Inter") — it is not itself an upload collection. Editors drop files into its upload slots: either a variable font (one file covering a weight range, with separate upright and italic slots) or a weights array (one file per weight/style). Those raw files land in a hiddenfontOriginalcollection (the archive, kept private), and on save a hook subsets each one to a hiddenfontOptimizedWOFF2 — the file the site actually serves (charset-subsetted, and kept public). So the licensed originals stay private while only the optimized web fonts are exposed. - The active selection: which typeface fills each role (sans/serif/mono/display) is resolved in order: the active
designSet'sfontgroup first, then the standalonefontSetglobal. So a Styles project and a standalone fonts project both work the same way. - The export endpoint and download step: the plugin registers a
GET /api/fonts/exportendpoint (secured by yourPAYLOAD_SECRET) that resolves the active selection and reads each typeface's servedfontOptimizedbytes server-side, either straight from disk for local storage or by fetching theurlPayload reports for cloud adapters (Vercel Blob, S3, and so on). A build-time script (generate:fonts) calls that endpoint atFONT_DOWNLOAD_URLand writes the files to disk. Because the bytes are read inside Payload, there's no per-file storage token or login to manage.
The download delivers the fonts in two outputs: the font files land in public/fonts, and a generated definition module (./src/app/definition.ts by default, overridable via ATOMIC_FONTS_DEFINITION_FILE) declares each localFont(...) call (along with CSS custom properties per slot) that your app imports to wire the fonts into next/font/local. The download intentionally runs at build time (or during scaffolding), not at request time, because next/font's bundling expects file paths it can resolve at compile.
Using it
In the admin:
- Create a typeface in the Font collection: give it a name and family, then add its files — either a variable font (upload the upright file, and an italic file if you have one) or one file per row in the weights array (pick weight + style, upload the file). Each upload slot is create-only: every slot takes a fresh file, so a font file is never shared between typefaces. On save, each file is subsetted to a served WOFF2 automatically.
- Pick the active typeface for each role (sans/serif/mono/display): in the active design set's font group when using Styles, or in the
fontSetglobal when running standalone.
Running the download:
After selecting fonts, run the download step to pull the files to disk and regenerate the definition module. The fonts plugin's Setup wires this up as a generate:fonts script:
pnpm generate:fontsIt fetches the active fonts from a running, reachable Payload instance (calling the plugin's export endpoint at FONT_DOWNLOAD_URL and authenticating with PAYLOAD_SECRET), then writes the font files to public/fonts and the generated definition.ts that next/font/local consumes. It runs automatically on every build via prebuild, and you can re-run it any time the active selection changes.
If a download fails, it prints a short message by default and leaves your existing fonts in place. To see the full error, re-run it with --verbose (or set ATOMIC_FONTS_VERBOSE=true):
pnpm generate:fonts --verbosegenerate:fonts needs a Payload instance to fetch from. In CI or on a deploy, point FONT_DOWNLOAD_URL at your site. For local development, before anything's deployed, a setup:fonts wrapper boots a temporary dev server, runs the download against it, and shuts it down. See the fonts plugin Notes.
Configuration
The download calls the plugin's export endpoint using:
| Variable | Purpose |
|---|---|
FONT_DOWNLOAD_URL | The running Payload instance to fetch the fonts from (your dev server or deploy). |
PAYLOAD_SECRET | Authenticates the request to the export endpoint (sent as Authorization: Bearer). |
No storage token or script user is required: the endpoint reads each font's bytes server-side, from disk or the URL Payload reports.
Optional overrides let you change where outputs go, for example ATOMIC_FONTS_OUTPUT_DIR (defaults to ./public/fonts), ATOMIC_FONTS_DEFINITION_FILE (the generated localFont(...) module), and ATOMIC_FONTS_CSS_VAR_PREFIX (prefix for the emitted CSS custom properties).
For plugin options (enabled, fontOptions, includeFontSet, fontSetOptions), the exported configs, and the full environment-variable reference, see the fonts plugin.