Payload Plugins
Pluginspayload-icons

Collections

The three collections payload-icons registers — the SVG upload pipeline, the swappable icon sets, and the runtime miss tracker.

For AI / LLMs: View Markdown

iconsPlugin() registers three collections:

  • icon — the SVG upload collection, in an Assets admin group.
  • iconSet — the name → icon grouping collection, in a Sets admin group.
  • iconRequest — a hidden diagnostics collection, on by default (disable with collections: { iconRequest: false }).

Icons accept SVG only. Every upload is sanitized before storage, so a .svg is safe to inline straight onto a page.

icon

The upload collection you add icons to. Each file is optimized and sanitized on save by the formatSVGHook beforeChange hook and stored inline as svgString. Read is public (icons are frontend assets); writes are logged-in-admin only. The admin titles each doc by filename.

Fields

Prop

Type

Hooks

Prop

Type

On save, formatSVGHook runs svgo (loaded dynamically, so it never lands in a frontend or edge bundle) and:

  • Sanitizes untrusted SVGs: strips <script> elements, on* handlers, and javascript: URLs. The stored string is later inlined via dangerouslySetInnerHTML, so this runs even when geometry optimization is skipped.
  • Optimizes with svgo (preset-default + path/number cleanup, dimensions removed).
  • Themes by rewriting hard-coded fill/stroke to currentColor, so an icon takes its color from CSS.
  • Normalizes the viewBox: tightened to the real path bounds and squared around the glyph's center, so mismatched source artboards render consistently.

Payload blocks SVG uploads by default (they're on its restricted-file-types list). This collection is SVG-only and sanitizes every file before storage, so it opts in automatically via upload.allowRestrictedFileTypes, with nothing to configure.

SVGs using transform / clip-path skip the geometry rewrite (it can't be applied safely) but are still sanitized before storage.

iconSet

A named, ordered name → icon mapping into the shared icon pool, with a single-active toggle. Build a second set (a different arrow-right, a heavier weight, a seasonal pack) and flip it active to re-skin every icon at once, without touching the frontend. Reads and writes require a logged-in user. Drafts / versions are on by default; override with collections.iconSet.overrides.versions like any other Payload collection. Lives under a Sets admin group.

  • Settings tab: the active toggle and title (plus the usage panel unless collections.iconSet.options.usagePanel: false).
  • Icons tab: the iconsArray, one row per icon, each a name (auto kebab-cased, what the frontend looks up, independent of the uploaded filename) and an icon upload relationship into the icon collection — plus anything you add via collections.iconSet.options.iconRowFields.

Only one set is active at a time. Activating a set runs a beforeChange hook that deactivates the others within the same status lane, so staging a new active set as a draft (with live preview) doesn't disturb the live published set; the swap goes live only on publish. The hook runs in the same transaction and rolls back on failure, so you never end up with two active sets. It's self-contained: a plain checkbox, no external dependency.

iconRequest

On by default (disable with collections: { iconRequest: false }). A hidden diagnostics collection: every icon name that fails to resolve at runtime is recorded (throttled, fire-and-forget) with a hit count and first/lastRequestedAt timestamps. It's the runtime counterpart to the static scan, capturing dynamic names a static pass can't see. Surfaced in the IconSet usage panel, not browsed directly.

Icon use detection

Problem: code asks for a name the active set lacks → the fallback warning glyph, and it's easy to miss which icons a project actually needs.

Fix: the admin Requested icons panel (on by default) lists missing vs present names, from two sources:

  • Static scan: greps your source for literal <Icon name="…">, flagging missing names with file:line. In dev the panel scans live, so there's nothing to set up or run. For production (source isn't on disk at runtime) run payload-icons-scan in your build to write an icon-usage-manifest.json the panel reads there.

The live dev scan takes the defaults: roots ./src and ./app relative to the dev server's working directory, and a component literally named <Icon>. If your layout differs — icons under another root, an aliased tag like <AppIcon>, or a monorepo where the server doesn't start at the app root — it finds nothing. When that happens the panel falls back to the icon-usage-manifest.json on disk, so running payload-icons-scan with the right --component / roots fixes the panel in dev too. A scan that found nothing is reported as such, never as "all present" — an empty scan is not a pass.

  • Runtime: on by default too, <Icon> logs names that fail to resolve at runtime (incl. dynamic name={slug} the scan can't see) into the hidden iconRequest collection, with hit counts, last-seen, and a clear button.

Both are on with iconsPlugin(); pass collections: { iconSet: { options: { usagePanel: false } } } / collections: { iconRequest: false } to omit either.

payload-icons-scan            # production/CI: scans ./src and ./app; -o sets the manifest path

Scan sees literal names only: name="x", name={'x'}, name={`x`}; dynamic name={expr} is the iconRequest recorder's job. In prod the panel reads icon-usage-manifest.json from cwd (override with the ICON_USAGE_MANIFEST env var). Other component? --component Glyph. On standalone / Vercel builds, a JSON file that's never imported isn't traced into the function bundle — add it to outputFileTracingIncludes in next.config so the panel can read it there.

Two env vars tune usage detection at runtime, independent of the plugin options — ICON_USAGE_TRACKING and ICON_USAGE_MANIFEST. See Reference.

On this page