Reference
Plugin options, environment variables, the download CLI, the export endpoint, and every export for payload-fonts.
Plugin options
fontsPlugin(options?) is the single entry point; one call registers the three collections, the
fontSet global, and the export endpoint. It's zero-config. The collections and global are covered on
the Collections page.
Everything the plugin registers is one key under collections (Payload collections) or globals
(fontSet is a Payload global, so it lives there, not under collections) — mirroring Payload's own
config shape. The plugin's own knobs — charset, families — live in a separate options bucket,
kept apart from the collections and globals it registers.
Each collections.<name> / globals.fontSet entry is the same uniform shape: slug renames it (see
Renaming below), overrides is a plain Partial<CollectionConfig> (or
Partial<GlobalConfig>) merged onto the plugin's config, and options would carry that entry's own
plugin knobs — the fonts collections and global have none. globals.fontSet also takes false to
skip it.
Pass options to customize. Reference is the interactive view; TypeScript is the same shape in code, every option at its default.
enabledbooleandefault trueWhen false, the plugin is a no-op and registers nothing.
collections{ font?, fontOriginal?, fontOptimized? }
The collections the plugin registers, one key each. All three are always registered — there is no false, and none takes plugin-specific options.
fontCollectionOption
The visible font typeface collection. No plugin options.
slugstringdefault 'font'Rename it; the plugin follows it (see Renaming).
overridesPartial<CollectionConfig>Payload config merged onto the plugin's by the shared merge rules, e.g. tighter access. Rename with the sibling slug key, not here.
fontOriginalCollectionOption
The hidden fontOriginal upload collection. No plugin options.
slugstringdefault 'fontOriginal'Rename it; the plugin follows it.
overridesPartial<CollectionConfig>Payload config merged onto the plugin's, e.g. an upload.staticDir or a client-uploads (direct-to-Blob) adapter. Rename with the sibling slug key, not here.
fontOptimizedCollectionOption
The hidden fontOptimized upload collection (the served WOFF2s). No plugin options.
slugstringdefault 'fontOptimized'Rename it; the plugin follows it.
overridesPartial<CollectionConfig>Payload config merged onto the plugin's, e.g. an upload.staticDir. Rename with the sibling slug key, not here.
globals{ fontSet? }
The globals the plugin registers.
fontSetfalse | GlobalOption
The fontSet global: the active per-family selection the export endpoint and your frontend read. Registered by default; false skips it entirely — set that only if you drive the selection some other way (without it the export endpoint has nothing to resolve). No plugin options.
slugstringdefault 'fontSet'Rename the global; the export endpoint follows it.
overridesPartial<GlobalConfig>Payload GlobalConfig merged onto the plugin's by the shared merge rules. Rename with the sibling slug key, not here.
optionsFontsOptions
The plugin's own knobs.
charset'latin' | 'latin-ext' | stringdefault 'latin'Characters the subsetter keeps in the served WOFF2 files: a preset (latin is ASCII + Latin-1 + common punctuation, latin-ext widens it), or an explicit string of characters to retain.
familiesFontFamilyConfig[]default sans / serif / mono / display
Font slots. The COMPLETE list: it replaces the four defaults wholesale (spread DEFAULT_FONT_FAMILIES to keep them). Each entry:
keystringrequiredFamily id. Becomes a family option, a fontSet slot, and (capitalised) font<Key> / --font-set<Key>.
labelstringdefault capitalised keyAdmin label for the family option and fontSet slot.
fallbackstringdefault generic sans stackCSS fallback appended after the served family in the family variable.
accessFontsAccessOptions
Per-endpoint gates for the plugin's HTTP endpoints — one EndpointAccess ((req) => boolean | Promise<boolean>) per endpoint. See Gating endpoints.
exportEndpointAccessdefault PAYLOAD_SECRET bearer-token checkGates GET /fonts/export. A machine endpoint with no user session — defaults to the PAYLOAD_SECRET bearer-token check the download CLI sends. Override only if you terminate auth upstream.
import { fontsPlugin } from '@pro-laico/payload-fonts'
// Every option at its default. This is the zero-config behaviour, written out.
fontsPlugin({
enabled: true,
// collections: { // optional, no defaults; each is { slug?, overrides? }
// font: { overrides: { /* … */ } }, // the visible font typeface collection
// fontOriginal: { overrides: { /* … */ } }, // the hidden originals upload collection
// fontOptimized: { overrides: { /* … */ } },// the hidden served-WOFF2 upload collection
// }, // overrides is any Partial<CollectionConfig>; `slug` renames it
// globals: {
// fontSet: {}, // {} registers the global as-is; { slug?, overrides? } tweaks it, false skips it
// },
options: {
charset: 'latin',
// families: [{ key: 'sans' }, { key: 'serif' }, { key: 'mono' }, { key: 'display' }],
// ^ the default, written out. Pass your own COMPLETE list to replace it
// (spread DEFAULT_FONT_FAMILIES to keep these and add more).
// access: {
// export: () => true, // default: PAYLOAD_SECRET bearer-token check — override only if terminated upstream
// },
},
})Overrides merge non-destructively, by the same rules in every plugin here:
| Key | Merge |
|---|---|
slug | Renames the collection or global. Every internal reference follows it. |
fields | Appended after the plugin's. A duplicate name is a boot error naming the field. |
hooks | Merged per phase — yours run after the plugin's. |
access / admin / upload / custom | Shallow-merged. |
defaultPopulate / forceSelect | Merged as selects. |
| everything else | Replaced. |
So collections: { fontOriginal: { overrides: { upload: { staticDir } } } } keeps the built-in
font-mime whitelist, and adding a field via collections: { font: { overrides: { fields } } } keeps
title / family / variable / weights.
Renaming
slug is an ordinary override. Rename anything the plugin registers and every internal reference is
rebuilt around the new name — the fontOptimized → font relationship, the fontSet slots, the
upload relationships, the cleanup and optimize hooks' queries, and the slugs the export endpoint and
payload fonts:download read:
fontsPlugin({
collections: { font: { slug: 'typefaces' } },
globals: { fontSet: { slug: 'typography' } },
})
// → editors get a `typefaces` collection and a `typography` global; nothing else to update.Renaming is a schema change, not a label change: on an existing database it's a migration, so do it before you have font data (or migrate the tables/collections yourself).
Environment variables
Both commands write files, so both read the output vars. Only the transport differs: payload fonts:download gets its database connection from your Payload config, so the FONT_DOWNLOAD_URL /
endpoint pair is dead weight there.
| Env var | Default | Applies to | What |
|---|---|---|---|
FONT_DOWNLOAD_URL | — | HTTP CLI | Required by payload-fonts-download. URL of the running Payload instance to fetch from. payload fonts:download doesn't use it. |
PAYLOAD_SECRET | — | both | Required. The HTTP CLI sends it as the bearer token the export endpoint checks; payload fonts:download just needs it because it boots your Payload config. It's already in your project either way. |
PAYLOAD_FONTS_ENDPOINT | /api/fonts/export | HTTP CLI | Export endpoint path, resolved against the site URL. |
PAYLOAD_FONTS_OUTPUT_DIR | ./public/fonts | both | Where the downloaded WOFF2 files are written. |
PAYLOAD_FONTS_DEFINITION_FILE | ./src/app/definition.ts | both | The generated next/font/local module. |
PAYLOAD_FONTS_SRC_PREFIX | ../../public/fonts | both | src path in the generated localFont() calls, relative to the definition file. |
PAYLOAD_FONTS_CSS_VAR_PREFIX | --font-set | both | Prefix for the emitted CSS family variables; must match <PreviewFonts cssVarPrefix>. |
PAYLOAD_FONTS_ENV_FILE | ./.env.local, ./.env | both | Dotenv file(s) loaded before anything else is read. Unset, both are loaded; set, only the named file. |
PAYLOAD_FONTS_VERBOSE | false | both | Print the full error on failure (also the --verbose / -v flag). |
CLI commands
| Command | What |
|---|---|
payload fonts:download | The recommended build step — put it on both predev and prebuild. The baked path is the same in dev and production, so keeping definition.ts current on every boot is exactly what you want. Resolves the fontSet selection through the Local API, so no running site, no FONT_DOWNLOAD_URL, no HTTP, and writes public/fonts/*.woff2 + src/app/definition.ts. See Serving. |
payload-fonts-download | The same job over HTTP, for a build that can't reach the database (a remote build box). Fetches GET /api/fonts/export from a running site with FONT_DOWNLOAD_URL + PAYLOAD_SECRET, and empties the definition on any failure — including when that site is unreachable, so the build can ship with no fonts. Prefer payload fonts:download where you can. |
Endpoints
GET /fonts/export is gated by options.access.export, which defaults to the PAYLOAD_SECRET
bearer-token check the download CLI sends — a machine endpoint with no user session. Override it only
if you terminate auth upstream. See Gating endpoints.
| Route | Auth | What |
|---|---|---|
GET /api/fonts/export | Bearer PAYLOAD_SECRET (default; options.access.export replaces it) | Returns each active family's fontOptimized bytes; the source the download CLI fetches. Response shape is ExportFontsResponse. See Serving. |
Exports
| Export | From | What |
|---|---|---|
fontsPlugin | @pro-laico/payload-fonts | The plugin factory (also the default export). |
FontsPluginOptions | @pro-laico/payload-fonts | The fontsPlugin(options?) argument type: { enabled?, collections?, globals?, options? }. |
FontsOptions | @pro-laico/payload-fonts | The options bucket's shape: { charset?, families?, access? }. |
Charset | @pro-laico/payload-fonts | The options.charset type: 'latin' | 'latin-ext' or an explicit string of characters to keep. |
readFontsMarker | @pro-laico/payload-fonts | The typed view of config.custom.payloadFonts: { options, fontSlug, fontOriginalSlug, fontOptimizedSlug, fontSetSlug, familyKeys, exportPath }. The supported way to discover the slugs the plugin registered — they follow collections.<name>.slug / globals.fontSet.slug, and fontSetSlug is null when the global is off. Returns undefined when the plugin isn't registered. |
PayloadFontsMarker | @pro-laico/payload-fonts | The marker's type. |
FontFamilyConfig | @pro-laico/payload-fonts | The { key, label?, fallback? } shape of a families entry. |
FontsAccessOptions | @pro-laico/payload-fonts | The options.access gate-map shape: { export? }. |
EndpointAccess | @pro-laico/payload-fonts | The endpoint-gate function type (req) => boolean | Promise<boolean>; see Gating endpoints. |
DEFAULT_FONT_FAMILIES | @pro-laico/payload-fonts | The built-in sans/serif/mono/display families (spread to extend them). |
extractFonts | @pro-laico/payload-fonts | Collect the generated next/font classes for the root <html> className. |
getActiveFontFaces | @pro-laico/payload-fonts | Resolve the active fontSet selection to its served files; the read behind <PreviewFonts>, for custom UIs. |
buildFontFaceCss | @pro-laico/payload-fonts | Turn the resolved faces from getActiveFontFaces into the @font-face + --font-set* CSS <PreviewFonts> inlines; for custom serving. |
ActiveTypeface / ActiveFace | @pro-laico/payload-fonts | The types getActiveFontFaces returns and buildFontFaceCss accepts. |
ExportFontsResponse / ExportFamilyDiagnostics | @pro-laico/payload-fonts | The shape of GET /api/fonts/export (and its per-family diagnostics), for custom consumers. |
PreviewFonts | @pro-laico/payload-fonts/PreviewFonts | The live preview escape hatch — inlines @font-face from the live DB selection, in dev or prod. |
payload-fonts-download | bin | The CLI the production build runs to write fonts to disk. |
Collections & globals
fontsPlugin registers the font / fontOriginal / fontOptimized collections and the fontSet
global. Their fields are documented on Collections.
How it works
How the subset pipeline, one-file italics, the baked next/font path, and the PreviewFonts escape hatch fit together — so you can reason about what happens on save, on build, and on refresh.
Troubleshooting
Fix the common payload-fonts failures fast — blank admin fields, fonts that never shrink, changes that don't show, and the generated files.