Photo Gallery
Give every item an unlimited, sortable set of photos, stored in a table of its own.
A photoField holds one image in a column. A gallery holds any number of photos per item: editors drag-and-drop uploads, reorder them, and delete them from the edit page.
Basic usage
import { hasItemsSection } from 'nextjs-cms/core/sections'
import { textField } from 'nextjs-cms/core/fields'
const title = textField({ name: 'title', required: true })
export default hasItemsSection({
name: 'properties',
order: 2,
title: { section: 'Properties', singular: 'Property', plural: 'Properties' },
gallery: {
db: { tableName: 'property_photos' },
},
headingField: title,
db: { table: 'properties' },
fields: [title],
})Run update-sections and it creates the gallery table next to the section's table.
The gallery table
property_photos
┌──────────────┬───────────────────────────────────────────────────────────┐
│ reference_id │ the item's identifier (same type as the section's) │
│ photo │ generated file name, primary key │
│ meta │ LONGTEXT, free-form per-photo data (nullable) │
│ sort_order │ INT, position set by drag-and-drop │
│ locale │ only for localized galleries (part of the primary key) │
│ created_at │ │
│ created_by │ │
└──────────────┴───────────────────────────────────────────────────────────┘Files are written like any photo field's: the full image to <uploads>/.photos/<section>/ and a thumbnail to <uploads>/.thumbs/<section>/.
Options
| Option | Type | Default | Description |
|---|---|---|---|
db.tableName | string | required | Gallery table name. |
db.identifierField | string | 'reference_id' | Column pointing to the item. |
db.photoNameField | string | 'photo' | Column with the file name. |
db.metaField | string | 'meta' | Free-form metadata column. |
thumbnail | { width, height, fit: 'cover' | 'contain', quality } | media.images.thumbnail from cms.config.ts | Thumbnail generated for each photo. |
watermark | false | WatermarkObject | media.images.watermark | Override the global watermark. false turns it off for this gallery. See watermarks. |
localized | boolean | false | A separate set of photos per locale. |
Gallery uploads accept jpg, jpeg, png and webp files up to 2 MB each. The full-size image is stored as uploaded (not resized); only the thumbnail is generated.
Examples
Custom column names
Match an existing table or your naming style:
gallery: {
db: {
tableName: 'product_images',
identifierField: 'product_id',
photoNameField: 'file_name',
metaField: 'caption',
},
}Square thumbnails and a watermark
gallery: {
db: { tableName: 'listing_photos' },
thumbnail: { width: 320, height: 320, fit: 'cover', quality: 75 },
watermark: {
imagePath: './public/watermark.png',
position: 'bottom-right',
size: 0.18,
opacity: 0.6,
margin: 24,
},
}A different gallery per language
gallery: {
db: { tableName: 'campaign_visuals' },
localized: true, // each locale gets its own photos
}Requires content localization. The table gains a locale column, and the primary key becomes (photo, locale).
Reading a gallery on your website
import { asc, eq } from 'drizzle-orm'
import { db } from './db'
import { PropertyPhotosTable } from './cms-schema'
export function getPropertyPhotos(propertyId: number) {
return db
.select()
.from(PropertyPhotosTable)
.where(eq(PropertyPhotosTable.referenceId, propertyId))
.orderBy(asc(PropertyPhotosTable.sortOrder))
}
// photo.photo → `${MEDIA_URL}/.photos/properties/${photo.photo}`
// thumbnail → `${MEDIA_URL}/.thumbs/properties/${photo.photo}`See Media & Uploads for serving the uploads folder.
When an item is deleted, its gallery rows and files are deleted too.