Creating Collections: Reusable Data Entities — Types & Examples

Step-by-step guide to define collection types, create example data, export for blocks, and prepare for Payload…

·Matija Žiberna·

📚 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.

Part 4 of the Design to Code series — Following Create Custom Block Types

Collections are the data that blocks display. While blocks define page layouts and sections, collections define the actual entities: Industry, Client, Product, Team Member. A single collection can be used across multiple blocks and pages.

This guide walks through creating a collection from scratch.

Collections vs Blocks: Understanding the Relationship

Think of it this way:

Collections = Data (the "what")

  • Industry (Retail, Healthcare, Hospitality)
  • Client (Company A, Company B)
  • Team Member (John, Sarah, etc.)

Blocks = Display (the "how")

  • FeaturedIndustries block displays industries
  • ClientGrid block displays clients
  • TeamCarousel block displays team members

One collection (Industry) can be displayed by multiple blocks (FeaturedIndustries, IndustryDetail page, FilterBar). Collections are reusable; blocks are layout-specific.

When to Create a Collection

Create a collection when:

  • You have multiple instances of the same data type
  • That data appears in multiple places (different blocks, pages)
  • The data is meaningful on its own (like a detail page)

Examples from this project:

  • Industry: Used in FeaturedIndustries block, Industry detail page, Filter component
  • Client: Used in Clients carousel, Client testimonials, About page
  • Team Member: Used in Team grid, Bio pages, Staff directory

Don't create a collection if:

  • The data only appears in one place
  • It's just internal configuration (feature flags, settings)
  • It's content specific to one block

The Process: Four Steps

Building a collection is straightforward:

  1. Define the type based on the data structure
  2. Create example data for development
  3. Export from collections index so blocks can use it
  4. Use in blocks that display the collection

Let's walk through each.

Step 1: Define the Type

Look at what you're modeling. What fields does this entity have?

File: src/types/collections/industry.ts

import type { Media } from '@payload-types';
import type { CTA } from '@/types/blocks';

/**
 * Industry Collection
 * Represents an industry category (Retail, Healthcare, etc.)
 */
export interface Industry {
  // Core identification
  id: number;                    // Unique identifier
  title: string;                 // Industry name (e.g., "Retail")
  slug?: string;                 // URL slug (e.g., "retail")

  // Content
  description?: string;          // Short description (for cards)
  longDescription?: string;      // Detailed description (for detail page)

  // Media & Icon
  image?: Media;                 // Featured image (for cards, detail page)
  icon?: string;                 // Lucide icon name (e.g., "ShoppingBag")

  // Link
  link?: CTA;                    // Link to detail page or related resource

  // Status & metadata (for Payload compatibility)
  _status?: 'draft' | 'published';
  meta?: {
    title?: string;
    description?: string;
  };

  // Timestamps
  createdAt: string;
  updatedAt: string;
}

Design decisions:

  • id: number matches Payload conventions and makes references easy
  • slug is auto-generated (from title) but helpful for URLs
  • description is short (for card display)
  • longDescription is detailed (for dedicated detail page)
  • image is optional but expected when displayed
  • icon is a Lucide icon name (string), not a component
  • link uses the reusable CTA type
  • Status and timestamps prepare for Payload migration

Step 2: Create Example Data

Create realistic example data for development:

File: src/types/collections/industry.ts (add to same file)

export const retailIndustry: Industry = {
  id: 1,
  title: 'Retail',
  slug: 'retail',
  description: 'Custom signage for retail storefronts and shopping centers',
  longDescription: `Transform your retail space with custom signage solutions.
    From storefront displays to interior wayfinding, we create signage
    that enhances customer experience and drives foot traffic.`,
  image: {
    url: 'https://example.com/retail-industry.jpg',
    alt: 'Retail storefront with custom signage',
    width: 1200,
    height: 800,
  },
  icon: 'ShoppingBag',
  link: {
    label: 'Explore Retail Solutions',
    href: '/industries/retail',
  },
  _status: 'published',
  createdAt: new Date().toISOString(),
  updatedAt: new Date().toISOString(),
};

export const healthcareIndustry: Industry = {
  id: 2,
  title: 'Healthcare',
  slug: 'healthcare',
  description: 'Professional signage for hospitals and medical facilities',
  longDescription: `Healthcare facilities require clear, compliant signage.
    We specialize in wayfinding, room identification, and ADA-compliant
    signage that meets healthcare standards.`,
  image: {
    url: 'https://example.com/healthcare-industry.jpg',
    alt: 'Hospital hallway with professional signage',
    width: 1200,
    height: 800,
  },
  icon: 'Heart',
  link: {
    label: 'Explore Healthcare Solutions',
    href: '/industries/healthcare',
  },
  _status: 'published',
  createdAt: new Date().toISOString(),
  updatedAt: new Date().toISOString(),
};

export const industriesData: Industry[] = [
  retailIndustry,
  healthcareIndustry,
  // Add more as needed
];

Why detailed examples:

  • Shows exactly what each field should contain
  • Demonstrates real-world data structure
  • Can be copied and modified for other entries
  • Documents expected format for future developers

Step 3: Export from Collections Index

Make your collection available to blocks and pages:

File: src/types/collections/index.ts

export type { Industry } from './industry';
export {
  retailIndustry,
  healthcareIndustry,
  industriesData,
} from './industry';

Now blocks can import:

import type { Industry } from '@/types/collections';
import { industriesData } from '@/types/collections';

Step 4: Use in Blocks

Blocks display collections. Update your block type to accept collection data:

File: src/types/blocks/featured-industries.ts

import type { Industry } from '@/types/collections';

export interface FeaturedIndustriesBlock {
  blockType: 'featuredIndustries';
  template: 'grid' | 'carousel';
  title?: string;
  selectedIndustries?: Industry[];  // ← Use collection type here
  itemsPerView?: number;
}

Now the block knows it displays Industry items. Blocks that display collections should have a field like selectedIndustries, items, or clients containing the collection type.

In your component:

File: src/components/blocks/featured-industries/featured-industries-template-1.tsx

import type { FeaturedIndustriesBlock } from '@/types/blocks';

export function FeaturedIndustriesTemplate1({ data }: { data: FeaturedIndustriesBlock }) {
  const { selectedIndustries = [] } = data;

  return (
    <div className="grid grid-cols-3 gap-8">
      {selectedIndustries.map((industry) => (
        <div key={industry.id}>
          <h3>{industry.title}</h3>
          <p>{industry.description}</p>
          {industry.image && (
            <img
              src={industry.image.url}
              alt={industry.image.alt}
              className="w-full h-48 object-cover"
            />
          )}
          {industry.link && (
            <a href={industry.link.href}>{industry.link.label}</a>
          )}
        </div>
      ))}
    </div>
  );
}

The component receives blocks with selectedIndustries (collection data) and renders each one. No hardcoding, just data-driven rendering.

Using Collections in Page Data

When you add the block to your page, you provide the collection data:

File: src/app/data.ts

import type { Page } from '@payload-types';
import { industriesData } from '@/types/collections';

export const homePageData: Page = {
  id: 'home',
  slug: '/',
  title: 'Home',
  layout: [
    {
      blockType: 'featuredIndustries',
      template: 'grid',
      title: 'Industries We Serve',
      selectedIndustries: industriesData,  // ← Pass collection data
    } as FeaturedIndustriesBlock,
  ],
};

This is powerful: the same industriesData collection can be used by:

  • FeaturedIndustries block (grid view)
  • IndustryCarousel block (carousel view)
  • Filter component (dropdown)
  • Detail page (single industry)
  • Team association (which industries does each team member work with?)

One definition, multiple uses.

Collections with Relationships

Collections often reference each other. For example:

Team Member collection references Industry:

export interface TeamMember {
  id: number;
  name: string;
  role: string;
  photo?: Media;
  bio?: string;
  industries?: Industry[];    // ← References Industry collection
  socialLinks?: Link[];
}

Client collection references Team Member:

export interface Client {
  id: number;
  name: string;
  logo?: Media;
  industry?: Industry;        // ← References Industry collection
  primaryContact?: TeamMember; // ← References TeamMember collection
  testimonial?: string;
}

Collections can reference each other by ID or by including the entire referenced object. For development, include full objects. For Payload, you'd use IDs and resolve them in queries.

File Structure

When you're done creating a collection, you have:

src/types/collections/
├─ index.ts              ← Exports all collections
├─ industry.ts           ← Collection type + examples
├─ client.ts             ← Another collection
└─ team-member.ts        ← Yet another collection

Each collection file is self-contained but exports from the index for easy access.

Naming Conventions

  • Type names: Singular (Industry, Client, TeamMember)
  • Collections (arrays): Plural with "Data" suffix (industriesData, clientsData)
  • Example items: Describe the item (retailIndustry, johnSmithTeamMember)
  • File names: kebab-case matching type (industry.ts, team-member.ts)
// File: industry.ts
export interface Industry { ... }
export const retailIndustry: Industry = { ... }
export const industriesData: Industry[] = [ ... ]

Testing Collections

Before using a collection in a block:

  1. Create example data with realistic values
  2. All required fields are present
  3. Export from src/types/collections/index.ts
  4. Import in block type correctly
  5. Add to page data with full collection
  6. Component renders correctly
  7. All fields display as intended

Moving to Payload

Your collection becomes a Payload collection config later:

// Your manual collection type
export interface Industry {
  id: number;
  title: string;
  slug: string;
  description: string;
  image: Media;
  icon: string;
}

// Later: Payload collection config follows the same structure
const IndustryCollection = {
  slug: 'industries',
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'slug', type: 'text' },
    { name: 'description', type: 'textarea' },
    { name: 'image', type: 'upload' },
    { name: 'icon', type: 'text' },
  ]
};

Your code doesn't change. Just swap the data source:

// Before: example data
import { industriesData } from '@/types/collections';

// After: Payload query
const industriesData = await getCollection('industries').find();

Multiple Collections Example

Here's how three collections might work together:

// Collection 1: Industry
interface Industry {
  id: number;
  title: string;
  industries: Industry[];
}

// Collection 2: TeamMember
interface TeamMember {
  id: number;
  name: string;
  industries?: Industry[];  // References Industry
}

// Collection 3: Client
interface Client {
  id: number;
  name: string;
  industry?: Industry;      // References Industry
  accountManager?: TeamMember;  // References TeamMember
}

// Block: displays industries
interface FeaturedIndustriesBlock {
  selectedIndustries?: Industry[];
}

// Block: displays team
interface TeamGridBlock {
  teamMembers?: TeamMember[];
}

// Block: displays clients
interface ClientPortfolioBlock {
  clients?: Client[];
}

Multiple blocks, multiple collections, all interconnected through references.

0

Frequently Asked Questions

Comments

Leave a Comment

Your email will not be published

10-2000 characters

• Comments are automatically approved and will appear immediately

• Your name and email will be saved for future comments

• Be respectful and constructive in your feedback

• No spam, self-promotion, or off-topic content

Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

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.