npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

payload-plugin-icons

v1.1.5

Published

Icon picker field for Payload CMS with Lucide and Phosphor providers.

Readme

payload-plugin-icons

An icon picker field for Payload 3. It stores { provider, name }, discovers icons from the provider versions installed in your app, and renders provider-neutral serialized SVGs.

Sponsor

payload-plugin-icons is sponsored by BrainyBuilds. Thank you for supporting the project!

Features

  • Lucide and Phosphor icon providers
  • Searchable, paginated admin picker
  • Provider and icon preview in collection cells
  • Typed field data and React rendering component
  • Server-side custom provider support
  • No Lucide or Phosphor modules in admin or frontend client bundles
  • Batched, cached picker previews

Installation

npm install payload-plugin-icons lucide-react @phosphor-icons/react

Requirements: Payload ^3.37.0, React 19, Lucide React >=0.400.0, and Phosphor React >=2.0.0.

Configuration

import { buildConfig } from 'payload'
import { createIconPlugin } from 'payload-plugin-icons'

const { iconField, iconPlugin } = createIconPlugin()

export default buildConfig({
  plugins: [iconPlugin],
  collections: [
    {
      slug: 'pages',
      fields: [
        iconField({
          name: 'icon',
          label: 'Icon',
          required: false,
        }),
      ],
    },
  ],
})

Configure next.config so provider packages are loaded natively only when an icon endpoint runs. (Lucide uses Node runtime loading because Next optimizes it by default; other providers are added to serverExternalPackages.)

import { withPayload } from '@payloadcms/next/withPayload'
import { withPayloadIcons } from 'payload-plugin-icons'

const nextConfig = {}

export default withPayload(
  withPayloadIcons(nextConfig),
  { devBundleServerPackages: false },
)

Standalone and Docker deployments

Next.js output tracing cannot discover the provider packages because the plugin loads them through opaque runtime imports. This does not affect next dev or a conventional next start deployment where the project's complete node_modules remains available. However, output: 'standalone' copies only files discovered by output tracing into .next/standalone.

Explicitly include each configured provider package when building a standalone deployment:

import { withPayload } from '@payloadcms/next/withPayload'
import type { NextConfig } from 'next'
import { withPayloadIcons } from 'payload-plugin-icons'

const nextConfig: NextConfig = {
  output: 'standalone',
  outputFileTracingIncludes: {
    '/*': [
      './node_modules/lucide-react/**/*',
      './node_modules/@phosphor-icons/react/**/*',
    ],
  },
}

export default withPayload(
  withPayloadIcons(nextConfig),
  { devBundleServerPackages: false },
)

If the plugin is configured with only one built-in provider, include only that provider's package. Add equivalent patterns for custom provider packages. Patterns are resolved from the Next.js project root; monorepos may also need Next.js's outputFileTracingRoot setting.

withPayloadIcons configures provider packages for native server loading, but opaque runtime imports still require these explicit tracing includes in standalone builds.

packageImport defaults to payload-plugin-icons. Set it when the package is exposed through a different monorepo alias:

createIconPlugin({ packageImport: '@acme/payload-icons' })

After changing the field configuration, regenerate Payload's import map and types:

payload generate:importmap
payload generate:types

Rendering an icon

Icon is a Client Component. It fetches a small serialized SVG definition from the plugin endpoint; provider packages stay on the server. Requests mounted in the same turn are automatically batched and definitions are shared in a module-level cache.

'use client'

import type { IconData } from 'payload-plugin-icons'
import { Icon } from 'payload-plugin-icons/client'

export function FeatureIcon({ icon }: { icon?: IconData | null }) {
  return <Icon aria-hidden className="feature-icon" icon={icon} size={24} />
}

SVG props and event handlers are applied to the outer <svg>. Phosphor's weight is included in the request and cache key.

Customization

Use a subset of the built-in providers or change their labels:

import { createIconPlugin, lucideProvider } from 'payload-plugin-icons'

const { iconField, iconPlugin } = createIconPlugin({
  providers: [lucideProvider({ label: 'Interface icons' })],
})

Field options:

  • name: stored field name
  • label: admin label
  • required: require an icon name
  • defaultProviderId: provider selected for new documents
  • providerIds: provider subset for this field
  • labelsById: field-specific provider labels
  • packageImport: field-specific package name or alias
  • admin: additional Payload field admin options

Custom providers

A custom provider implements the server-only IconProvider contract. Keep its package import inside loadCatalog / loadIcons, serialize only validated names from your catalog, and add the package name to withPayloadIcons.

import type { IconProvider } from 'payload-plugin-icons'
import { createIconPlugin, serializeIconComponent } from 'payload-plugin-icons'

const customProvider: IconProvider = {
  id: 'custom',
  label: 'Custom',
  packageName: '@acme/icon-library',
  async loadCatalog() {
    return { provider: this.id, version: '1', icons: [{ name: 'Home' }] }
  },
  async loadIcons({ names }) {
    const icons = await import('@acme/icon-library')
    return Object.fromEntries(names.flatMap((name) =>
      name === 'Home' ? [[name, serializeIconComponent(icons.Home)]] : [],
    ))
  },
}

const { iconField, iconPlugin } = createIconPlugin({
  providers: [customProvider],
})

Call withPayloadIcons(nextConfig, ['@acme/icon-library']) for this provider.

Troubleshooting

Icon endpoints return 500 only in standalone or Docker

If /api/payload-icons/:provider/catalog or /api/payload-icons/:provider/icons works in development but returns 500 from a container that copies only .next/standalone, the provider package was probably omitted by Next.js output tracing. The endpoint may report that it is unable to load the icon catalog or icons.

Add the provider package to outputFileTracingIncludes as shown in Standalone and Docker deployments, rebuild the image, and confirm that its directory exists under the deployed node_modules. A typical standalone image copies .next/standalone and .next/static; copying .next/static does not add missing server dependencies.

Development

npm ci
npm run dev

The development app uses a local SQLite database at dev/payload.db, so no external database is required. You can optionally copy dev/.env.example to dev/.env to set PAYLOAD_SECRET or override DATABASE_URL. The development admin runs at http://localhost:2515.

Useful commands:

  • npm run check: lint and create a clean package build
  • npm run generate:importmap: regenerate the development import map
  • npm run generate:types: regenerate development Payload types
  • npm run build:next: build the development application

dist is committed and verified in CI.

License

MIT