Troubleshooting
Fix a fallback glyph, a solid-blob icon, a blank admin preview, or an empty requested-icons panel — symptom-first, with the exact strings to search for.
Most payload-icons problems come down to a set that isn't active, a stroke-drawn SVG, or an import map that's out of date. Find your symptom below, then apply the fix.
| Symptom | Cause | Fix |
|---|---|---|
| Fallback warning glyph instead of your icon | The active set doesn't resolve the name you passed. | Check three things in order: (1) a set is active, (2) it's published (not just saved as a draft), (3) the row name exactly matches the name you pass to <Icon> (both are kebab-cased). A name with no match always renders the fallback, never nothing. |
| Icon renders as a solid blob (an outline fills in — a circle becomes a disk) | The uploaded SVG is stroke-drawn (fill="none" stroke="…", Lucide/Feather-style). The optimizer themes filled glyphs by rewriting their colors to currentColor, so a stroke icon's enclosed shapes fill solid. | Upload solid fill-based glyphs instead (Noun Project-style filled paths). The upload detects the stroke signature and flags it in the doc's optimized report — check that field on the icon doc. |
| Blank admin icon preview (icon-preview component missing) | The preview components register by string path and the import map is stale. | Run pnpm payload generate:importmap, then restart the dev server. |
| Requested icons panel is empty in production | The static scan reads your source, which isn't on disk at runtime. | Run payload-icons-scan in your build to write icon-usage-manifest.json for the panel to read. |
| SVG upload rejected | Payload blocks SVG uploads by default (they're on its restricted-file-types list). | Nothing to do — the icon collection sanitizes every file and opts in automatically via upload.allowRestrictedFileTypes. If you overrode upload, keep that flag set. |
Building the <Icon> component
<Icon> is created from the createIcon factory, which takes a live Payload handle — the package never self-resolves your config. Build it once and import that module wherever you render icons:
import config from '@payload-config'
import { getPayload } from 'payload'
import { createIcon } from '@pro-laico/payload-icons/components/Icon'
export const Icon = createIcon(getPayload({ config }))See <Icon> for the full component contract.
Requested icons panel, in detail
The Requested icons panel draws from two sources, and each fails differently in production.
-
Static scan sees literal names only. It matches
name="x",name={'x'}, andname={`x`}. A dynamicname={expr}is invisible to it — that's the runtime recorder's job (theiconRequestcollection). -
In dev the panel scans live, so nothing is set up or run. In production the source isn't on disk, so run
payload-icons-scanduring your build:payload-icons-scan # scans ./src and ./app; -o sets the manifest path -
In prod the panel reads
icon-usage-manifest.jsonfrom cwd. Override the path with theICON_USAGE_MANIFESTenv var. Scanning another component? Pass--component Glyph. -
On standalone / Vercel builds the manifest can go missing. A JSON file that's never
imported isn't traced into the function bundle. Add it tooutputFileTracingIncludesinnext.configso the panel can read it there.
Behavior notes
These aren't bugs — they're how the plugin works.
- The lookup name is the set entry's name, not the filename. Renaming an uploaded file changes nothing; the
iconsArrayrow's name is the key your code references. - No revalidation is built in. A statically rendered page bakes the SVG at build time and won't reflect an edit or an active-set swap until the route revalidates. Install
@pro-laico/payload-revalidateto make the whole surface self-healing, or wirerevalidatePath/revalidateTagyourself. See Revalidation. - SVGs using
transform/clip-pathskip the geometry rewrite (it can't be applied safely) but are still sanitized before storage.