Payload CMS Custom Collections Development

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
Payload CMS Custom Collections Development
Medium
~2-3 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Custom Payload CMS Collections

A Collection in Payload is a content type with a REST API, GraphQL schema, and admin panel interface automatically generated from TypeScript configuration. Each collection is stored in a separate PostgreSQL table or MongoDB collection.

Collection Structure

// collections/Products.ts
import { CollectionConfig } from 'payload/types'

const Products: CollectionConfig = {
  slug: 'products',           // URL segment: /api/products
  labels: {
    singular: 'Product',
    plural: 'Products',
  },
  admin: {
    useAsTitle: 'name',
    defaultColumns: ['name', 'price', 'category', 'inStock'],
    group: 'Catalog',
  },
  // ...
}

Field Types

fields: [
  // Text
  { name: 'name', type: 'text', required: true },
  { name: 'description', type: 'textarea' },
  { name: 'content', type: 'richText' },

  // Numbers and dates
  { name: 'price', type: 'number', min: 0, required: true },
  { name: 'publishedAt', type: 'date' },

  // Selection
  {
    name: 'status',
    type: 'select',
    options: [
      { label: 'Active', value: 'active' },
      { label: 'Archived', value: 'archived' },
    ],
    defaultValue: 'active',
  },

  // Media
  { name: 'image', type: 'upload', relationTo: 'media' },

  // Relationships
  {
    name: 'category',
    type: 'relationship',
    relationTo: 'categories',
    hasMany: false,
  },
  {
    name: 'tags',
    type: 'relationship',
    relationTo: 'tags',
    hasMany: true,
  },

  // Array of objects
  {
    name: 'variants',
    type: 'array',
    fields: [
      { name: 'sku', type: 'text', required: true },
      { name: 'color', type: 'text' },
      { name: 'size', type: 'text' },
      { name: 'stock', type: 'number', defaultValue: 0 },
    ],
  },

  // Blocks (like Gutenberg)
  {
    name: 'sections',
    type: 'blocks',
    blocks: [TextBlock, ImageBlock, CTABlock],
  },

  // Field group
  {
    name: 'seo',
    type: 'group',
    fields: [
      { name: 'title', type: 'text' },
      { name: 'description', type: 'textarea' },
    ],
  },
]

Access Control

access: {
  // Reading — public
  read: () => true,

  // Creation — authenticated users only
  create: ({ req: { user } }) => Boolean(user),

  // Update — owner or admin only
  update: ({ req: { user }, id }) => {
    if (!user) return false
    if (user.role === 'admin') return true
    return { author: { equals: user.id } }  // filter condition
  },

  // Deletion — admin only
  delete: ({ req: { user } }) => user?.role === 'admin',
},

Collection Hooks

hooks: {
  beforeChange: [
    async ({ data, req, operation }) => {
      // Automatic slug generation
      if (operation === 'create' && !data.slug) {
        data.slug = data.name
          .toLowerCase()
          .replace(/\s+/g, '-')
          .replace(/[^\w-]/g, '')
      }
      // Set author
      if (operation === 'create' && req.user) {
        data.author = req.user.id
      }
      return data
    },
  ],

  afterChange: [
    async ({ doc, operation }) => {
      // Invalidate Next.js cache
      if (operation === 'update') {
        await fetch(`/api/revalidate?path=/products/${doc.slug}`, {
          method: 'POST',
        })
      }
    },
  ],

  afterDelete: [
    async ({ doc }) => {
      // Clean up related data
      console.log(`Product ${doc.id} deleted`)
    },
  ],
},

Versioning

versions: {
  maxPerDoc: 20,
  drafts: {
    autosave: {
      interval: 2000,  // autosave every 2 seconds
    },
  },
},

Collection Queries

// On the server (Next.js Server Component)
import { getPayload } from 'payload'
import config from '@payload-config'

const payload = await getPayload({ config })

// Find published products in a category
const result = await payload.find({
  collection: 'products',
  where: {
    and: [
      { status: { equals: 'active' } },
      { category: { equals: categoryId } },
      { price: { less_than: 10000 } },
    ],
  },
  sort: '-createdAt',
  limit: 20,
  page: 1,
  depth: 2,  // relationship population depth
})

const { docs, totalDocs, hasNextPage } = result

REST API (Auto-generated)

# List
GET /api/products?where[status][equals]=active&limit=20

# Single document
GET /api/products/:id

# Create (requires auth)
POST /api/products
Authorization: Bearer <token>
Content-Type: application/json

# Update
PATCH /api/products/:id

# Delete
DELETE /api/products/:id

Timeline

Configuring one collection with fields, access, and hooks — 2–4 hours. Full catalog (5–10 interconnected collections) — 2–4 days.