BuildWithMatija
  1. Home
  2. Blog
  3. Payload
  4. Mastering Payload CMS Admin UI: Organize Complex Collections

Mastering Payload CMS Admin UI: Organize Complex Collections

Use unnamed tabs, rows, groups, and sidebar controls to visually structure collections without changing the DB schema.

1st September 2026·Updated on:14th September 2026··
Payload
Mastering Payload CMS Admin UI: Organize Complex Collections

Evaluating Payload CMS Implementation Costs?

Scope design, content structure, and migration hours to estimate a realistic production timeline and hosting setup.

Try the Cost EstimatorGet a Second Opinion

📚 Comprehensive Payload CMS Guides

Detailed Payload guides with field configuration examples, custom components, and workflow optimization tips to speed up your CMS development process.

No spam. Unsubscribe anytime.

📄View markdown version
0

Frequently Asked Questions

About the author

Matija Žiberna

Matija Žiberna

Full-stack developer, co-founder

AboutResume

Self-taught full-stack developer sharing lessons from building software and startups.

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

Contents

  • 1. Our Real-World Use Case
  • The Challenge
  • The Objective
  • 2. The Golden Rule: Presentational vs. Schema Containers
  • Named Containers (Schema Mutating)
  • Unnamed Containers (Purely Presentational)
  • 3. Native Admin UI Toolkit Reference
  • A. `tabs` (Multi-Panel Navigation)
  • B. `row` & `admin.width` (Responsive Grid Layouts)
  • C. Unnamed `group` (Visual Card Enclosures)
  • D. `admin.position: 'sidebar'` (The Control Deck)
  • E. Micro-Copy: `admin.description` & `admin.placeholder`
  • 4. Production Blueprint: Complex Collection Example
  • 5. 4 Critical Gotchas to Avoid
  • 1. The Named Container Trap
  • 2. Runtime Field-Level ABAC Traversal
  • 3. Unit Test Assertions Must Be Recursive
  • 4. Create View vs. Edit View Consistency
  • Summary & Checklist
On this page:
  • 1. Our Real-World Use Case
  • 2. The Golden Rule: Presentational vs. Schema Containers
  • 3. Native Admin UI Toolkit Reference
  • 4. Production Blueprint: Complex Collection Example
  • 5. 4 Critical Gotchas to Avoid
Build with Matija logo

Build with Matija

Senior-led B2B websites, applications, content systems, and digital infrastructure. Business-first, full-stack, AI-assisted, no handoffs.

Services

  • B2B Website Development
  • CMS Architecture Review & Platform Blueprint
  • Next.js + Payload Advisory
  • AI Integration & Implementation

Resources

  • CMS Hub
  • B2B Website Strategy
  • E-commerce Hub
  • Blog
  • Case Studies

Payload CMS

  • Payload CMS Developer
  • Payload CMS Migration
  • Payload CMS Demos
  • All Payload CMS Resources

Discuss your project

Planning a rebuild, migration, application, workflow change, or platform decision? Start with the business problem and the system behind it.

Book a discovery callContact me →
© 2026Build with Matija•All rights reserved•Privacy Policy•Terms of Service
BuildWithMatija
Get In Touch

When you build enterprise applications with Payload CMS, your collections can grow rapidly. What starts as a simple schema with 5 fields quickly turns into 25, 40, or 60+ fields covering:

  • Core Editorial Content: Titles, localized slugs, rich text, hero media, layout builder blocks.
  • Regulatory & Compliance Data: Health Canada NPNs, active/non-medicinal ingredient tables, allergen notices, contraindications, dosage instructions.
  • Physical & Commercial Attributes: Dimensions, weights, serving units, MSRP currencies.
  • Taxonomies & Relations: Product lines, categories, health goals, tags, authors, parent hierarchies.
  • Audit & ETL Provenance: External sync IDs, import run hashes, timestamps, moderation states, source tracking discriminators.

Without deliberate visual structure, Payload renders every field in a single, unformatted vertical list. Editors are forced to scroll endlessly, related fields are scattered, and operational metadata clutters the creative canvas.

In this guide, you'll learn how to organize any Payload CMS collection into a clean, intuitive, and ergonomic Admin UI using 100% native presentational capabilities—with zero database schema mutations and zero breaking API changes.


1. Our Real-World Use Case

We recently undertook a comprehensive Admin UI restructuring across 28 collections for a multi-site enterprise platform (CanPrev).

The Challenge

Our collections spanned diverse domains:

  • Content Hub: Pages, Blogs, Blog Comments, Recipes, Stories, Webinars, Ambassadors, Contributors, Events, Event Tickets, FAQs, Locations, Jobs, Giveaways, Scientific References.
  • Governance & Access: Approval Workflows, Notification Rules, Collection Access Rules, Field Access Rules, Field Groups, Roles, Departments, Sub-Departments, Event Registrations.
  • Codex-Backed Commerce: Product Families (products) and SKU Variants (product-variants / variants), each containing up to 40+ raw attributes synchronized from external PIM/regulatory APIs.

Before optimization, opening /admin/collections/approval-workflows/create or /admin/collections/products/1 presented an unorganized wall of inputs. Important checkboxes like enabled or isDefault sat below large arrays, and responsive pairs like startDate and endDate stacked vertically, tripling the page length.

The Objective

Transform the entire backoffice into an intuitive, visually grouped workspace while maintaining three strict architectural invariants:

  1. Zero Database Schema Mutation: No column renames, no forced data migrations, and no breaking changes to Local API / REST API document shapes.
  2. Create & Edit View Parity: Ensure /create and /:id views share the exact same clean layout.
  3. Enterprise Security & Governance Integrity: Ensure field-level Attribute-Based Access Control (ABAC) and unit test runners seamlessly traverse nested presentational containers.

2. The Golden Rule: Presentational vs. Schema Containers

Before touching collection configs, you must understand the distinction between Named Containers and Unnamed Presentational Containers in Payload CMS:

Diagram

Named Containers (Schema Mutating)

When you add a name property to a group or tab, Payload treats it as a real data structure.

ts
// ❌ WARNING: This mutates your database and API contract!
{
  name: "editorialData",
  type: "group",
  fields: [
    { name: "title", type: "text" }
  ]
}
// Local API Output: { editorialData: { title: "Hello" } }

Unnamed Containers (Purely Presentational)

When you omit the name property from a tabs, row, or group container, Payload uses it solely to structure the Admin UI DOM. The underlying database table and API payloads remain completely flat.

ts
// ✅ PURE PRESENTATION: Zero schema changes!
{
  type: "row",
  fields: [
    { name: "title", type: "text", admin: { width: "50%" } },
    { name: "slug", type: "text", admin: { width: "50%" } },
  ]
}
// Local API Output: { title: "Hello", slug: "hello" }

3. Native Admin UI Toolkit Reference

Payload CMS provides four core layout primitives to organize collection fields:

A. tabs (Multi-Panel Navigation)

Top-level tabs break long schemas into distinct cognitive contexts (e.g. Overview, Content, Taxonomies, SEO).

ts
{
  type: "tabs",
  tabs: [
    {
      label: "Article Content",
      description: "Primary editorial text, summary, and media.",
      fields: [
        titleField,
        slugField,
        bodyField,
      ],
    },
    {
      label: "Related Products",
      description: "Curated products recommended in this article.",
      fields: [
        relatedProductsField,
      ],
    },
  ],
}

Unnamed Tabs: Notice that neither the parent type: "tabs" nor individual tab objects have a name property. This ensures that title, slug, and body remain top-level fields on the document.


B. row & admin.width (Responsive Grid Layouts)

By default, every Payload field takes 100% width. Wrapping related fields in a type: "row" and applying admin.width arranges them horizontally.

Common Width Combinations:

  • 50% / 50%: Symmetrical pairs (firstName / lastName, city / province, startDate / endDate).
  • 70% / 30% or 60% / 40%: Primary input with modifier (product relationship 70% + rating select 30%, amount 60% + currency 40%).
  • 33% / 33% / 34%: Triads (format / flavour / dosingUnit, city / province / postalCode).
  • 25% / 25% / 25% / 25%: Compact 4-column metric sets (length / width / height / weight).
ts
{
  type: "row",
  fields: [
    {
      name: "name",
      type: "text",
      required: true,
      admin: {
        width: "50%",
        description: "Public display name.",
      },
    },
    {
      name: "slug",
      type: "text",
      required: true,
      admin: {
        width: "50%",
        description: "URL-safe identifier.",
      },
    },
  ],
}

C. Unnamed group (Visual Card Enclosures)

When you want to visually cluster fields inside a tab without creating sub-tabs, use an unnamed group. It renders as a bordered card with its own heading and description.

ts
{
  type: "group",
  admin: {
    description: "Physical event location and online broadcast links.",
  },
  fields: [
    {
      type: "row",
      fields: [
        { name: "isVirtual", type: "checkbox", admin: { width: "30%" } },
        { name: "location", type: "relationship", relationTo: "locations", admin: { width: "70%" } },
      ],
    },
    {
      type: "row",
      fields: [
        { name: "locationUrl", type: "text", admin: { width: "50%" } },
        { name: "locationUrlText", type: "text", admin: { width: "50%" } },
      ],
    },
  ],
}

D. admin.position: 'sidebar' (The Control Deck)

The right-hand sidebar is one of Payload's most powerful visual features. It should be reserved for high-frequency metadata, operational toggles, and provenance audit trails, keeping the main canvas uncluttered.

What Belongs in the Sidebar:

  • Publishing & Lifecycle: _status, archived, active, publishedAt.
  • System Configuration: isDefault, locale, approvalMode, separatePublisherRequired.
  • Taxonomies & Relations: parent, author, categories, tags.
  • Provenance & ETL Identifiers: externalId, legacySource, sourceReviewId, importedAt.
  • Audit Info: moderatedBy, moderatedAt, ipAddress.
ts
{
  name: "active",
  type: "checkbox",
  defaultValue: true,
  admin: {
    position: "sidebar",
    description: "When inactive, this item is hidden from public selection.",
  },
}

E. Micro-Copy: admin.description & admin.placeholder

Never leave editors guessing. Adding clear descriptions directly below inputs eliminates back-and-forth communication and onboarding overhead:

ts
{
  name: "stableKey",
  type: "text",
  required: true,
  unique: true,
  admin: {
    width: "50%",
    placeholder: "e.g. immune-support-formula",
    description: "Immutable machine identifier used by external integrations.",
  },
}

4. Production Blueprint: Complex Collection Example

Here is a real-world, copy-pasteable blueprint of a production Blog Collection that uses all four primitives together:

ts
import type { CollectionConfig } from "payload";

export const Blogs: CollectionConfig = {
  slug: "blogs",
  admin: {
    useAsTitle: "title",
    defaultColumns: ["title", "author", "categories", "_status", "updatedAt"],
  },
  fields: [
    // ==========================================
    // 1. SIDEBAR CONTROLS (Metadata & Relations)
    // ==========================================
    {
      name: "author",
      type: "relationship",
      relationTo: "contributors",
      required: true,
      admin: {
        position: "sidebar",
        description: "Author profile credited on the live site.",
      },
    },
    {
      name: "categories",
      type: "relationship",
      relationTo: "blog-categories",
      hasMany: true,
      admin: {
        position: "sidebar",
        description: "Primary topics for search and filtering.",
      },
    },
    {
      name: "tags",
      type: "relationship",
      relationTo: "blog-tags",
      hasMany: true,
      admin: {
        position: "sidebar",
      },
    },
    {
      name: "externalId",
      type: "text",
      admin: {
        position: "sidebar",
        readOnly: true,
        description: "Legacy WordPress post ID.",
      },
    },

    // ==========================================
    // 2. MAIN CONTENT TABS
    // ==========================================
    {
      type: "tabs",
      tabs: [
        {
          label: "Article Content",
          description: "Core editorial headline, localized URL, and narrative.",
          fields: [
            {
              type: "row",
              fields: [
                {
                  name: "title",
                  type: "text",
                  required: true,
                  localized: true,
                  admin: { width: "60%" },
                },
                {
                  name: "slug",
                  type: "text",
                  required: true,
                  localized: true,
                  admin: { width: "40%" },
                },
              ],
            },
            {
              name: "summary",
              type: "textarea",
              localized: true,
              admin: {
                description: "Short excerpt shown in blog cards and social previews.",
              },
            },
            {
              type: "row",
              fields: [
                {
                  name: "featuredMedia",
                  type: "relationship",
                  relationTo: "media",
                  localized: true,
                  admin: { width: "50%", description: "Hero banner image." },
                },
                {
                  name: "readingTimeMinutes",
                  type: "number",
                  admin: { width: "50%", description: "Estimated read time in minutes." },
                },
              ],
            },
            {
              name: "body",
              type: "richText",
              required: true,
              localized: true,
            },
          ],
        },
        {
          label: "Curated Recommendations",
          description: "Featured products and related clinical references.",
          fields: [
            {
              name: "relatedProducts",
              type: "relationship",
              relationTo: "products",
              hasMany: true,
              admin: {
                description: "Products displayed at the bottom of the article.",
              },
            },
            {
              name: "references",
              type: "relationship",
              relationTo: "scientific-references",
              hasMany: true,
              admin: {
                description: "Citations linked in the bibliography section.",
              },
            },
          ],
        },
      ],
    },
  ],
};

5. 4 Critical Gotchas to Avoid

When refactoring collection layouts in large enterprise repositories, beware of these common pitfalls:

1. The Named Container Trap

Never add a name property to a tabs container or a visual group unless you explicitly want to create a new nested object in your database table. Doing so alters the output of payload.find(), breaks frontend type safety, and forces database migrations.

2. Runtime Field-Level ABAC Traversal

If your platform dynamically attaches security hooks (like Attribute-Based Access Control) to collection fields during startup, flat field loops like collection.fields.forEach(...) will silently skip fields inside tabs or row containers!

You must implement recursive field traversal:

ts
// src/payload/access/field-access/apply-abac.ts
export function applyFieldABAC(fields: Field[]): Field[] {
  return fields.map((field) => {
    // 1. Recurse into presentational rows and unnamed groups
    if ("fields" in field && Array.isArray(field.fields)) {
      return {
        ...field,
        fields: applyFieldABAC(field.fields),
      };
    }

    // 2. Recurse into presentational tabs
    if (field.type === "tabs" && Array.isArray(field.tabs)) {
      return {
        ...field,
        tabs: field.tabs.map((tab) => ({
          ...tab,
          fields: applyFieldABAC(tab.fields),
        })),
      };
    }

    // 3. Attach security access gates to leaf fields
    if ("name" in field && field.name) {
      return attachAccessGate(field);
    }

    return field;
  });
}

3. Unit Test Assertions Must Be Recursive

If your test suite checks that collections contain specific fields (e.g. assert.ok(collection.fields.some(f => f.name === 'slug'))), shallow .find() or .map() calls will fail as soon as you place fields inside tabs or rows.

Always use a recursive field extractor helper in your test suites:

ts
function extractFieldNames(fields: unknown[]): string[] {
  const names: string[] = [];
  for (const field of fields as Array<{ name?: string; fields?: unknown[]; tabs?: Array<{ fields?: unknown[] }> }>) {
    if (field.name) names.push(field.name);
    if (Array.isArray(field.fields)) {
      names.push(...extractFieldNames(field.fields));
    }
    if (Array.isArray(field.tabs)) {
      for (const tab of field.tabs) {
        if (Array.isArray(tab.fields)) {
          names.push(...extractFieldNames(tab.fields));
        }
      }
    }
  }
  return names;
}

4. Create View vs. Edit View Consistency

Payload CMS uses the exact same field tree definition for /create and /:id. This means that by organizing your schema into logical tabs and sidebars, content creators immediately benefit during new document creation, without encountering massive vertical scroll fatigue.


Summary & Checklist

Before shipping collection schema updates to production, run through this quick checklist:

  • Are high-frequency toggles & metadata in the sidebar? (admin.position: 'sidebar')
  • Are adjacent inputs grouped into rows with matching widths? (admin.width: '50%')
  • Are all layout tabs and groups unnamed? (Ensure no accidental schema mutations)
  • Are inputs paired with descriptive micro-copy? (admin.description)
  • Do runtime hooks (ABAC, sync) recurse into tabs and rows?
  • Do test suites use recursive field extraction?
  • Did pnpm generate:types and pnpm typecheck pass with zero errors?