← Back to articles

June 3, 2026

9 min read

Content Modeling in PayloadCMS: Collections, Globals, and Relations That Scale

How to structure your content model in PayloadCMS using collections, globals, relationships, and advanced field types for projects that grow.

Leer en espanol

Collections vs Globals: when to use each

In Payload, a Collection is a content type of which multiple instances can exist: articles, products, users, categories. A Global is a content type of which exactly one instance exists: site configuration, navigation menu, homepage. If it makes sense to have ten instances, it is a Collection; if only one makes sense, it is a Global.

A common mistake is modeling as a Collection things that should be Globals. Social media configuration or legal text are Globals because it makes no sense to have ten active versions simultaneously. Modeling them as Collections forces extra logic to determine which one is "active" — a problem Payload already solves with Globals.

Advanced field types: blocks, arrays, and tabs

The `blocks` field is the most powerful in Payload for structured content. It lets you define reusable blocks with their own schema — a rich text block, an image block with caption, a CTA block — and compose pages by mixing those blocks in any order. It is the equivalent of a page builder, but controlled in code.

The `array` field works when content is a list of elements with the same structure: FAQ items, benefits, steps. The difference from blocks is that all array elements share the same schema, while each block in a blocks field can have a different schema.

Collection with relationships, blocks, and an audit hook.

import { CollectionConfig } from 'payload/types';

export const Posts: CollectionConfig = {
  slug: 'posts',
  fields: [
    { name: 'title', type: 'text', required: true },
    {
      name: 'author',
      type: 'relationship',
      relationTo: 'users',
      required: true,
    },
    {
      name: 'categories',
      type: 'relationship',
      relationTo: 'categories',
      hasMany: true,
    },
    {
      name: 'content',
      type: 'blocks',
      blocks: [RichTextBlock, ImageBlock, CallToActionBlock],
    },
  ],
  hooks: {
    beforeChange: [
      ({ data, req }) => ({
        ...data,
        lastEditedBy: req.user?.id ?? null,
      }),
    ],
  },
};

Relationships and avoiding N+1 queries

Relationships in Payload use the `relationship` field type. With `hasMany: false` it is a many-to-one relation (an article belongs to one author). With `hasMany: true` it is many-to-many (an article can have multiple categories). Payload handles persistence automatically.

The N+1 problem appears when Payload makes one extra query per record to load its relations. The solution is using `depth` in the query: `depth: 1` loads direct relations with joins. For large lists, use `depth: 0` and load relations only when needed to avoid the problem entirely.

  • Use depth: 1 to load relationships in detail views.
  • Use depth: 0 in list views to avoid unnecessary queries.
  • Group polymorphic relationships with relationTo as an array of slugs.

Access control built into the content model

Payload lets you define access control at the collection, document, and field level using the `access` property. A field only admins can write, a document only its author can update, a collection authenticated users can read but not create — all defined in the same configuration file.

This model-level access control is more robust than validating in external middleware because it is coupled to the schema. When you add a new field, the access control already applies without additional intervention.

More articles

Back to articles