No description
  • TypeScript 99.6%
  • Just 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-06-10 16:37:53 +02:00
src fix: center empty-state dropzone content, keep dropzone outline with images, fix stale package-name references after rename 2026-06-10 16:18:20 +02:00
.gitignore feat: initial gallery field plugin 2026-06-10 15:34:03 +02:00
CLAUDE.md fix: center empty-state dropzone content, keep dropzone outline with images, fix stale package-name references after rename 2026-06-10 16:18:20 +02:00
justfile feat: initial gallery field plugin 2026-06-10 15:34:03 +02:00
package.json chore: release v0.1.2 2026-06-10 16:37:53 +02:00
README.md fix: center empty-state dropzone content, keep dropzone outline with images, fix stale package-name references after rename 2026-06-10 16:18:20 +02:00
tsconfig.json feat: initial gallery field plugin 2026-06-10 15:34:03 +02:00
tsdown.config.ts feat: initial gallery field plugin 2026-06-10 15:34:03 +02:00
vitest.config.ts feat: initial gallery field plugin 2026-06-10 15:34:03 +02:00

@bl0cka.de/payload-image-gallery-field

Visual image gallery field for Payload CMS v3: a thumbnail grid with drag reorder, multi-select picking, in-place uploads, and inline image editing — built almost entirely on Payload's own admin primitives, so it follows the admin UI's look, theming (light/dark), i18n, and permissions automatically.

Why

Payload's native upload field with hasMany: true is great data-model-wise, but renders selected media as text rows — unusable for curating an image gallery visually. This field keeps the exact same data model (an array of media document IDs) and only replaces the admin component.

Because the data model is the native one:

  • required, minRows, maxRows validation works out of the box
  • REST/GraphQL output is identical to a native upload field
  • removing the plugin falls back to the native UI without a migration

Features

  • Thumbnail grid with square previews (Payload Thumbnail component)
  • Drag & drop reorder via Payload's dnd-kit DraggableSortable (same as the native admin — keyboard accessible)
  • Choose from existing via Payload's ListDrawer: full list view with search, filters, pagination, and bulk row selection
  • Upload in place via Payload's BulkUpload drawer — including dropping files straight onto the field (Dropzone)
  • Click a thumbnail to edit the image document in a DocumentDrawer (alt text, caption fields, crop / focal point — whatever the collection has); composes nicely with @bl0cka.de/payload-alt-text-plugin
  • Already-selected images are filtered out of the picker
  • Respects readOnly, collection create permissions, locale, and drafts
  • Uses Payload's built-in translation keys — no hardcoded strings

Install

npm install @bl0cka.de/payload-image-gallery-field

Usage

import { galleryField } from '@bl0cka.de/payload-image-gallery-field'

export const Projects: CollectionConfig = {
  slug: 'projects',
  fields: [
    galleryField({
      name: 'gallery',
      label: 'Bildergalerie',
      relationTo: 'images',
    }),
  ],
}

After adding the field, regenerate the import map:

npx payload generate:importmap

Options

Option Type Default Description
name string 'gallery' Field name
relationTo CollectionSlug — (required) Upload collection the gallery references (single collection)
label Payload label Field label (string, i18n record, or function)
required boolean Native Payload validation
minRows / maxRows number Native Payload validation; the UI hides "add" when maxRows is reached
localized boolean Localize the selection per locale
filterOptions upload filterOptions Restrict pickable documents
admin UploadField['admin'] Description, position, condition, …
allowCreate boolean true Show upload/create UI (still gated by the user's create permission)
enableDropzone boolean true Allow dropping files onto the field
enableEdit boolean true Thumbnail click opens the image doc in a drawer
gridCellMinWidth number 140 Minimum grid cell width in px
overrides Partial<UploadField> Escape hatch, merged last

Data model & migration

The field stores (string \| number)[] — an array of media document IDs — identical to both a native upload + hasMany: true field and a relationship + hasMany: true field pointing at the same collection. If you are migrating from either, no data migration is needed; only the generated TypeScript types change shape slightly (relationshipupload semantics).

Per-image captions/credits belong on the image document itself (where they are reusable and localizable), not on the gallery. If you need per-usage metadata (same image, different caption per gallery), that requires an array-of-objects data model — see the roadmap.

Roadmap

  • Per-usage metadata variant (array of { image, caption, … }) behind the same grid UX
  • Bulk sub-selection actions (select N images → remove)
  • Sort helpers (by filename / date, reverse)
  • List-view Cell component (stacked thumbnails + count)

Development

npm run dev    # tsdown --watch
npm test       # vitest unit tests for src/lib
npm run build  # tsdown → dist/
just publish   # release-it → private registry