# Collections

URL: /docs/plugins/payload-icons/collections

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

`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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `filename` | `upload` |  | The uploaded SVG (the upload). The admin useAsTitle. |
| `iconPreview` | `ui` |  | Theme-aware inline preview of the sanitized SVG (edit view + list cell), inheriting the admin theme's text color so icons stay visible in dark mode. |
| `svgString` | `code` |  | The cleaned, sanitized <svg>…</svg>, ready to inline. Read-only output; its list cell renders the themed preview. |
| `optimized` | `text` |  | Human-readable optimization report. Read-only output, shown once present. |
| `filesize` | `number` |  | Updated to the optimized byte size. |

**Hooks**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `formatSVGHook` | `beforeChange` |  | Optimizes + sanitizes each uploaded SVG, folding svgString / optimized / filesize into the doc. A no-op for changes that carry no new file. |

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](#icon-use-detection) 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](#icon-use-detection), 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`](#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.

```bash
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 `import`ed 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](/docs/plugins/payload-icons/reference#environment-variables).
