payload-icons
A fully custom icon set in Payload CMS that's as easy to use as Lucide, and anyone in the admin can add to it.
Upload an .svg in the admin, and the plugin optimizes, sanitizes, and themes it on save. A single
<Icon name="…" /> server component then drops it into any page as a real, inline <svg> that
recolors from CSS.
No sprite sheet, no wrapper file, no untrusted markup reaching the browser. Building your own icon set becomes as easy as using an off-the-shelf one like Lucide, and designers or clients can extend it from the admin without touching code.
pnpm add @pro-laico/payload-iconsMental model. Icons are a shared pool of SVGs. An icon set maps a lookup name
(arrow-right) to an icon in that pool. One set is active, and <Icon name> resolves against
it, so the set entry's name is the lookup key, not the uploaded filename. Swap which set is
active and every icon re-skins at once. Uploading an icon isn't enough on its own; it renders once
an active, published set points a name at it.
What's included
- Add icons in the admin: upload an
.svgand the plugin optimizes, sanitizes, and themes it on save — designers and clients extend the set without touching code. - One drop-in component:
<Icon name="arrow-right" />inlines a real, recolorable<svg>— as easy as Lucide, but it's your set. - Swappable icon sets: activating a different set re-skins every icon at once — a seasonal pack, a heavier redraw, an A/B test.
- Never ship a missing icon: the Requested icons panel scans your code and tracks runtime misses, surfacing exactly which icons you still need.
- Declarative seeding: icons seed natively through
@pro-laico/payload-seed— no helper, no provider.
Icon set Default, rendered in the /dev/icons route
Icon set Alternate, rendered in the /dev/icons route
Requirements
- Source solid-fill SVGs. The optimizer themes filled glyphs by rewriting their colors to
currentColor. Stroke-drawn sets like Lucide render as solid blobs — see Troubleshooting. - Next.js 15+ with
cacheComponents: trueto render icons. The collection, the admin, and the SVG pipeline run in any Payload app. Rendering is Next-bound: the active-set read is a'use cache'entry (which is what keeps a page of icons prerenderable) and runtime miss-tracking usesafter()fromnext/server. Thenextpeer is optional for exactly that reason — managing icons needs none of it. - Render in a server component.
<Icon>is an async server component that queries Payload; it can't render inside a'use client'component. - Revalidation is opt-in. The icon read caches and tags itself, but on its own the plugin busts nothing — the entry simply lives out its cache lifetime. Install the companion
@pro-laico/payload-revalidateand any icon or set write invalidates that tag, making the whole surface self-healing — see How it works.
Quickstart
Add the plugin
import { buildConfig } from 'payload'
import { iconsPlugin } from '@pro-laico/payload-icons'
export default buildConfig({
plugins: [iconsPlugin()],
})That registers the icon and iconSet collections plus a hidden iconRequest diagnostics
collection. Zero-config.
Required: regenerate the admin import map — the icon-preview components are registered by string path:
pnpm payload generate:importmapUpload an SVG
Add .svg files to the icon collection in the admin. Each is optimized and sanitized on save and
stored as svgString (the cleaned <svg>…</svg>) plus optimized (a short report, e.g. SVG optimized: 1234 to 567 bytes (54.1% reduction)). Uploading alone doesn't render anything yet; a set
has to point a name at it (next step).
Use solid fill-based SVGs. A stroke-drawn icon comes out filled solid — a circle outline becomes a disk. See Troubleshooting.
Create a set, name each icon, then activate and publish
An iconSet maps lookup names to icons. In order:
- Add an
iconSet. In the Icons tab, add one row per icon. - Type a name and select an uploaded icon. The name is the lookup key
<Icon name>uses, is independent of the uploaded filename, and is auto kebab-cased — type whatever you'll reference in code. - In Settings, toggle the set active. Activating one set deactivates the rest.
- Publish the set.
Sets are drafts-enabled by default, and the published frontend only reads a published active set. A draft-only set won't show until you publish it.
Render it
The active set is now resolvable. Build the component once from the createIcon factory — pass it a
Payload handle — then render it in any server component or page:
import config from '@payload-config'
import { getPayload } from 'payload'
import { createIcon } from '@pro-laico/payload-icons/components/Icon'
export const Icon = createIcon(getPayload({ config }))import { Icon } from '@/components/Icon'
// resolves `arrow-right` through the active set and inlines a real <svg>
<Icon name="arrow-right" className="size-6 text-primary" /><Icon> is an async server component — render it in a server component or page, not inside a
'use client' component.
That's the whole loop: upload → set → render. See Using icons for props, fallbacks, and styling.
Explore
Using icons
The <Icon> component, its props, rendering an icon yourself, and styling with CVA + Tailwind.
Collections
The icon, iconSet, and iconRequest collections, and icon-use detection.
How it works
Resolving an icon through the active set, revalidation, and declarative seeding.
Reference
Plugin options, environment variables, CLI commands, endpoints, and exports.
Troubleshooting
Fallback glyphs, solid-blob renders, blank admin previews, and build-time config errors.
Plugin options
Zero-config by default — every option has a sensible default. See Reference for options, environment variables, CLI, and exports.