Skip to content

Custom Fields

Rather than repeating field definitions across every collection, the boilerplate centralises common patterns in src/fields/. You spread or call them wherever needed. Change the definition in one place and everything stays in sync.

src/collections/pages.ts
import { slugField } from '@/fields/slug';
export const pages: CollectionConfig = {
fields: [
{ name: 'title', type: 'text', required: true },
...slugField('title'),
],
};

slugField spreads two fields into your collection: a read-only slug input and a hidden sync button. It watches the source field ('title' here) and auto-populates the slug as the editor types. The editor can also override it manually.

Options:

...slugField('title', {
slug: {
admin: { placeholder: 'custom-placeholder' },
},
})
src/collections/pages.ts
import { linkField } from '@/fields/link';
export const pages: CollectionConfig = {
fields: [
{
name: 'cta',
type: 'group',
...linkField(),
},
],
};

linkField adds a complete link group — type selector, reference picker, URL input, link text, style, and new-tab toggle — in a single spread. It handles the internal/external distinction so you do not have to build that logic per collection.

Link structure:

FieldTypeDescription
typeselectinternal_link or custom_url
referencerelationshipInternal page/post link
urltextExternal URL (validated)
texttextLink text
styleselectLink style variant
newTabcheckboxOpen in new tab

Render it in the frontend with CMSLink, which resolves internal references to their correct path automatically:

import CMSLink from '@/components/cms-link/cms-link';
<CMSLink link={doc.cta} />;
src/blocks/image/config.ts
import { imageUpload } from '@/fields/upload/image';
export const image: Block = {
slug: 'image',
fields: [
imageUpload({
name: 'image',
relationTo: 'media',
}),
],
};

imageUpload and videoUpload are pre-configured upload fields that include focal point support and the correct relation to the media collection. Use them instead of raw upload fields so focal point handling stays consistent.

  1. Create the field config in src/fields/

    src/fields/my-field/my-field.ts
    import type { Field } from 'payload';
    export const myField = (name: string, options?: Partial<Field>): Field => ({
    name,
    type: 'text',
    required: true,
    ...options,
    });

    The function accepts a name and an optional overrides object. Spreading options last means callers can override any default — required, admin settings, validation hooks — without touching the definition itself.

  2. Export from the fields index

    src/fields/index.ts
    export { slugField } from './slug/slug';
    export { linkField } from './link/link';
    export { imageUpload, videoUpload } from './upload';
    export { myField } from './my-field/my-field';

    All fields are exported from a single index so imports stay clean across collections: import { myField } from '@/fields'.

  3. Use in collections

    src/collections/events.ts
    import { myField } from '@/fields';
    fields: [myField('title')];