Atomic Payload
Examples

fonts-only

Minimal example: @pro-laico/fonts in isolation, served with next/font/local.

A minimal Atomic Payload template that exercises @pro-laico/fonts on its own. Upload font files to the Font collection, choose the active four in the fontSet global, run the font download step, and the site serves them with next/font/local.

What it shows

The only @pro-laico dependencies are @pro-laico/fonts and @pro-laico/core, so you see the fonts plugin in isolation, set up exactly as the fonts plugin's Standalone guide describes:

  • fontsPlugin({ includeFontSet: true }) registers the Font upload collection (woff/woff2/ttf/otf) and the fontSet global, the active sans / serif / mono / display selection (the font analog of an active icon set).
  • POST /api/seed uploads four bundled open-source fonts (one per role) and points the fontSet global at them. It is auth-gated and idempotent, run from the admin dashboard, so you have fonts to pick without finding your own.
  • pnpm generate:fonts (wired as prebuild) reads the active selection from a running site, downloads the fonts to public/fonts, and writes src/app/definition.ts for next/font/local.
  • The layout applies those next/font variables to <html> and maps each role to --font-<role>. / renders a specimen per active role (pangram, size ramp, glyphs) via var(--font-<role>).

Scaffold it

npx @pro-laico/create-atomic-payload my-fonts --template fonts-only
pnpm dlx @pro-laico/create-atomic-payload my-fonts --template fonts-only
yarn dlx @pro-laico/create-atomic-payload my-fonts --template fonts-only

Then set a long PAYLOAD_SECRET in .env, generate types and the import map, and start dev:

pnpm generate:types        # generates src/payload-types.ts + augment
pnpm generate:importmap    # populates src/app/(payload)/admin/importMap.js
pnpm dev

Ships with SQLite (@payloadcms/db-sqlite) at ./font-only.db, with no DB server. Swap adapters by editing src/payload.config.ts.

The flow:

  1. Visit /, an explainer page. Logged out, it points you to the admin.
  2. Open /admin, create your first user.
  3. On the dashboard, click Seed sample fonts (or upload your own and pick them in the fontSet global).
  4. Run pnpm setup:fonts to download the active fonts for next/font/local. It boots a temporary server, runs the download against it (authenticating with your PAYLOAD_SECRET), and shuts it down.
  5. Run pnpm dev again and open / to see the four active fonts rendered as specimens.

How it works

upload Font (admin)  ──►  Payload stores the file (FONT_STATIC_DIR / ./media)

seed: POST /api/seed uploads public/seed-fonts/*.woff2
      then updateGlobal('fontSet', { sans, serif, mono, display })  ◄── active set

pnpm generate:fonts ── GET /api/fonts/export @ FONT_DOWNLOAD_URL ──► public/fonts/* + src/app/definition.ts

layout.tsx → applies the next/font variables from definition.ts to <html>,
             maps --font-<role>: var(--font-set<Role>)

page.tsx → getActiveFonts() [fontSet global] ──► a specimen per role via var(--font-<role>)
route is force-dynamic ──► seeding / swapping the active set shows up on the next render

The active selection lives in a global because it's the active-selection layer, mirroring icons-only's active iconSet. The Font collection can hold many uploads; the fontSet global picks the one sans / serif / mono / display the site uses, editable at /admin/globals/fontSet.

The download step

pnpm build runs prebuildgenerate:fonts, which calls the fonts plugin's export endpoint on a reachable Payload instance and writes the fonts to disk for next/font/local. It authenticates with PAYLOAD_SECRET and fetches from FONT_DOWNLOAD_URL, the running instance to pull the active fonts from (see .env.example):

FONT_DOWNLOAD_URL=http://localhost:3000   # the running instance to fetch from
PAYLOAD_SECRET=...                        # authenticates the request

The endpoint reads each font server-side, from disk for local storage, or by fetching the URL Payload reports for cloud adapters (Vercel Blob, S3, and the like), so there's no storage token or login to manage. When neither the URL nor the secret resolves, the download is skipped (the build still succeeds) and the specimens fall back to system fonts until you run it.

Local dev, one command. generate:fonts needs a running Payload to fetch from, which is awkward before anything's deployed. pnpm setup:fonts handles it: it boots a temporary next dev server, points FONT_DOWNLOAD_URL at it, runs the download, then shuts the server down. No credentials needed: it authenticates with your existing PAYLOAD_SECRET.

What to look at

FileWhat's there
src/payload.config.tsbuildConfig: fontsPlugin({ includeFontSet: true }) + staticDir
src/lib/fontDir.tsFONT_STATIC_DIR (shared with the config, no @payload-config import)
src/lib/fonts.tsgetActiveFonts(): reads the active selection from the fontSet global
src/seed/sampleFonts.tsmanifest mapping bundled files → title + role
src/components/admin/BeforeDashboard + SeedControls (seed/reset on the dashboard)
src/app/definition.tsgenerated by generate:fonts (gitignored): the next/font/local declarations
src/app/(frontend)/layout.tsxapplies the next/font variables + maps --font-<role>
src/app/(frontend)/page.tsxexplainer + a specimen per active role + "use them together"
src/app/(payload)/api/seed/route.tsPOST /api/seed → upload fonts + set the fontSet global
src/app/(payload)/api/reset/route.tsPOST /api/reset → clear the global + delete fonts
public/seed-fonts/*.woff2bundled OFL fonts (committed) + LICENSES.md

FONT_STATIC_DIR (./media, gitignored) is where Payload writes uploads, shared via src/lib/fontDir.ts so the config and the download step never drift. The bundled seed fonts (Inter, Lora, JetBrains Mono, Abril Fatface) are OFL: see public/seed-fonts/LICENSES.md, and replace them before shipping a real project.

On this page