How it works
How an icon name resolves through the active set, when cache entries refresh, and how to seed icons and sets declaratively.
An <Icon name> resolves through whichever icon set is currently active and published. This page traces that lookup, explains when a rendered icon refreshes after an edit, and shows how to seed icons and sets from source files.
Resolving an icon
<Icon name="arrow-right" /> looks the name up in the active, published iconSet. It finds the icon doc that row points at and inlines that doc's stored svgString as a real <svg>. The result recolors through currentColor and takes its size from your own classes.
The lookup reads through payload.find inside a 'use cache' entry that tags itself payload-icons. One query resolves the whole active set, and it's reused across requests until something busts the tag — so a page of fifty icons costs one read, and usually none.
The name it matches is the set entry's name, not the uploaded filename. That is why swapping the active set re-skins every icon at once.
A name the active set doesn't cover renders the fallback warning glyph instead of nothing. The miss is recorded for the Requested icons panel.
Revalidation
The active-set read is a 'use cache' entry that tags itself payload-icons. That's what lets a
page full of icons prerender: the SVGs come from a cache entry, not from a database read Next would
have to render around.
Install @pro-laico/payload-revalidate and the whole surface becomes self-healing, zero config. Two decoupled halves meet on that one shared tag:
- Write side — the
iconandiconSetcollections ship a data-onlycustom.revalidatemarker carryingextraTags: ['payload-icons']. revalidatePlugin's auto-attached hooks bust that tag on every published icon/set write and delete. The after-seed flush busts it too. - Read side —
getIconSvg(and therefore<Icon>) callscacheTaginside its own cached read. Re-upload an SVG, remap a name, or activate a different set, and every page that inlined an icon re-materializes on the next request.
Draft follows the same lane convention as the rest of the repo: a draft read claims both
payload-icons and payload-icons:draft, a published read claims only the plain tag. Editing a
draft never disturbs what visitors are served; publishing refreshes both.
iconRequest ships custom: { revalidate: false } — a diagnostic feed that busts nothing.
Without the revalidate plugin nothing busts the tag, and the entry lives out its cache lifetime — the same deal any other cached read gets.
The single coarse tag is deliberate. The active-set read isn't keyed by a doc id, icon writes are rare, and the whole set resolves in a single query. See it wired end-to-end in examples/icons-sandbox — which installs both plugins for exactly this reason — and in examples/revalidate-sandbox.
Without the revalidate plugin nothing busts payload-icons. The read still caches, so a page that
prerendered an icon keeps serving it until the entry expires — an SVG re-upload or an active-set
swap won't show. Install the pair, or bust the tag yourself.
Seeding
Because icon is a standard upload collection, it seeds natively — no script, and (unlike payload-mux) no custom.seedAsset marker. Seed it like any other collection with defineSeed. Each record carries its source SVG on the _file meta-key via the file() token.
Drop your source .svg files in the collection's folder under the seed assets dir. The folder is named after the slug, so assets/icon/star.svg resolves from file('star.svg'):
// src/seed/icons.ts — each icon doc carries its SVG on `_file`
import { defineSeed } from '@pro-laico/payload-seed'
export default defineSeed('icon', ({ file }) => [
{
_key: 'star',
_file: file('star.svg'), // assets/icon/star.svg
},
{
_key: 'check',
_file: file('check.svg'), // assets/icon/check.svg
},
])Each doc is optimized on upload via the same formatSVGHook. An uploaded icon doesn't render on its own, though. The frontend resolves <Icon name> through the active set, so seed an iconSet that maps lookup names to those icons with ordinary ref('icon', …) tokens:
// src/seed/iconSets.ts — an active, published set wiring names to the seeded icons
import { defineSeed } from '@pro-laico/payload-seed'
export default defineSeed('iconSet', ({ ref }) => [
{
_key: 'default',
title: 'Default',
active: true, // the live set
_status: 'published', // visible to the non-draft frontend
iconsArray: [
{ name: 'star', icon: ref('icon', 'star') },
{ name: 'check', icon: ref('icon', 'check') },
],
},
])Icons are equally referenceable from any other doc — e.g. a page's icon relationship:
// src/seed/pages.ts
import { defineSeed } from '@pro-laico/payload-seed'
export default defineSeed('pages', ({ ref }) => [
{
_key: 'home',
title: 'Home',
icon: ref('icon', 'star'),
},
])Wire the definitions into the seed plugin as usual — icons precede the set and page that reference them:
import { iconsPlugin } from '@pro-laico/payload-icons'
import { seedPlugin } from '@pro-laico/payload-seed'
import icons from './seed/icons'
import iconSets from './seed/iconSets'
import pages from './seed/pages'
plugins: [iconsPlugin(), seedPlugin({ definitions: [icons, iconSets, pages] })]Because icon is a real collection, its seed records are type-checked and other docs reference them with ref(). This package never imports the seed package, so the two stay decoupled. active: true makes the set live and _status: 'published' makes it visible to the published frontend.
See the examples/icons-sandbox app for a full working setup, or the seed plugin docs for the full reference.