Collection Factories
posts and pages used to be ~85% identical, and the CLI templates were a third copy of the same config. Three factories now own those defaults, so a collection file contains only what is actually specific to it.
| Factory | Import from | Use for |
|---|---|---|
publicCollection() |
@/lib/payload/collections/public-collection |
collections rendered on a public route |
privateCollection() |
@/lib/payload/collections/private-collection |
collections managed in the admin panel only |
siteGlobal() |
@/lib/payload/globals/site-global |
site-wide globals |
import { image } from '@/blocks/image/config';import { text } from '@/blocks/text/config';import { publicCollection } from '@/lib/payload/collections/public-collection';import type { Post } from '@/payload-types';
export const posts = publicCollection<Post>({ slug: 'posts', contentFields: [{ name: 'blocks', type: 'blocks', blocks: [image, text] }],});A public collection’s slug is typed against the registry in src/lib/url/slug.ts, so forgetting to register a route is a compile error rather than a runtime SlugPathNotFound.
What you get for free
Section titled “What you get for free”publicCollection |
privateCollection |
siteGlobal |
|
|---|---|---|---|
title field |
✔ | ✔ | |
slug field |
✔ | ||
featuredImage field |
✔ | ||
| Content tab | ✔ | ✔ | |
excerpt field |
✔ | ||
| SEO tab + meta auto-generation | ✔ | ||
| Admin preview | ✔ | ||
| Path revalidation on save/delete | ✔ | ||
| Full layout revalidation on save | ✔ | ||
| Drafts, scheduled publish, trash | ✔ | ✔ | |
| Role-based access | ✔ | ✔ | ✔ |
hideAPIURL |
✔ | ✔ |
Adding fields
Section titled “Adding fields”Fields go in through named slots, never by passing a fields array:
| Slot | Where it lands |
|---|---|
sidebarFields |
root level, before featuredImage, defaulted to the sidebar |
rootFields |
root level, after featuredImage, left untouched |
contentFields |
inside the Content tab, before excerpt |
tabs |
extra tabs — see below |
An explicit admin.position on a sidebarFields entry is respected, so a sidebar field can opt back into the main column.
Adding tabs
Section titled “Adding tabs”Each factory assembles a single tabs field. The tabs slot adds your own tabs to it, so they share one tab bar with the built-in ones instead of rendering a second row:
export const posts = publicCollection<Post>({ slug: 'posts', contentFields: [{ name: 'blocks', type: 'blocks', blocks: [image, text] }], tabs: [ { label: 'Settings', fields: [{ name: 'featured', type: 'checkbox' }], }, { label: 'Relations', name: 'related', fields: [ { name: 'posts', type: 'relationship', relationTo: 'posts', hasMany: true }, ], }, ],});Where they land per factory:
| Factory | Position |
|---|---|
publicCollection |
after the Content tab, before the SEO tab |
privateCollection |
after the Content tab |
siteGlobal |
they are the global’s tabs — there is no built-in one |
disable: { seo: true } drops the SEO tab and leaves your tabs in place.
Overriding and disabling
Section titled “Overriding and disabling”Three separate mechanisms, so nothing is ever lost by accident:
| Want to | Use |
|---|---|
| add a hook | hooks — appended to the built-ins |
| remove a built-in | disable |
| change any other config key | overrides |
overrides is typed as Omit<Partial<Config>, 'slug' | 'fields' | 'hooks'>. Passing fields or hooks there is a compile error — a plain spread would silently drop the SEO and revalidation behaviour the factory promises.
// appends → [defaultRevalidate, mine]publicCollection({ slug: 'posts', hooks: { afterChange: [mine] } });
// replaces → [mine]publicCollection({ slug: 'posts', disable: { revalidate: true }, hooks: { afterChange: [mine] },});
// merges into the default admin config, keeping useAsTitle/group/previewpublicCollection({ slug: 'pages', overrides: { admin: { hideAPIURL: true } },});disable.seo removes the SEO tab and its beforeChange hook together, so the two cannot drift apart.
Built-ins always run before your hooks. To run first, disable the built-in and re-add both in your own order.
Escape hatch
Section titled “Escape hatch”Every piece a factory assembles is exported on its own — titleField, featuredImageField, seoTab, draftVersions, collectionAccess, revalidateHooks, globalAccess, mergeHooks and the rest. A collection that outgrows its factory can drop to a plain CollectionConfig and still reuse them rather than copying the defaults back in. See the Collections guide for what a hand-written config looks like.