Skip to content

Defining Collections

Learn how to define collection structures in BlocksWeb that content editors can populate in the CMS.

Where to Define Collections

Collections are defined in your blocksweb.config.ts file using the CollectionDefinition type:

typescript
// blocksweb.config.ts
import { CollectionDefinition } from '@blocksweb/core';

// Define the collection structure
const productCollection: CollectionDefinition = {
  name: 'products',
  displayName: 'Products',
  fields: [
    { name: 'name', type: 'string', label: 'Product Name', required: true },
    { name: 'price', type: 'number', label: 'Price', required: true },
    { name: 'description', type: 'richtext', label: 'Description' },
    { name: 'image', type: 'image', label: 'Product Image' }
  ]
};

// Register with a provider (see BlocksWeb Configuration guide)
export const collections = [
  {
    definition: productCollection,
    provider: yourProvider  // More on this below
  }
];

Need Help with blocksweb.config.ts?

See the BlocksWeb Configuration guide for complete setup instructions, including how to register collections with providers.

CollectionDefinition Structure

Each collection definition has the following structure:

typescript
type CollectionDefinition = {
  name: string;                // Unique identifier (lowercase, no spaces)
  displayName: string;         // Human-readable name shown in CMS
  description?: string;        // Optional description for editors
  fields: FieldDefinition[];   // Array of field definitions
  icon?: string;               // Optional icon for CMS UI
};

Example

typescript
{
  name: 'blog',                      // Used in code: collectionName: 'blog'
  displayName: 'Blog Posts',         // Shown in CMS: "Add Blog Posts"
  description: 'Manage blog posts and articles',
  icon: '📝',
  fields: [
    // ... field definitions
  ]
}

Field Types

BlocksWeb supports 8 field types for collection fields:

String Field

Text input for short text values.

typescript
{
  name: 'title',
  type: 'string',
  label: 'Title',
  required: true,
  default: '',
  maxLength: 100,
  placeholder: 'Enter title...'
}

Properties:

  • required?: boolean - Field must have a value
  • default?: string - Default value
  • maxLength?: number - Maximum characters
  • placeholder?: string - Placeholder text

Use cases: Names, titles, URLs, slugs, short descriptions

Number Field

Numeric input for integers and decimals.

typescript
{
  name: 'price',
  type: 'number',
  label: 'Price',
  required: true,
  default: 0,
  min: 0,
  max: 10000,
  step: 0.01
}

Properties:

  • required?: boolean
  • default?: number
  • min?: number - Minimum value
  • max?: number - Maximum value
  • step?: number - Increment step

Use cases: Prices, quantities, ratings, counts, percentages

Boolean Field

Checkbox for true/false values.

typescript
{
  name: 'featured',
  type: 'boolean',
  label: 'Featured Product',
  default: false
}

Properties:

  • default?: boolean

Use cases: Feature flags, visibility toggles, status indicators

Date Field

Date picker for dates and timestamps.

typescript
{
  name: 'publishedAt',
  type: 'date',
  label: 'Publish Date',
  required: true,
  default: () => new Date().toISOString()
}

Properties:

  • required?: boolean
  • default?: string | (() => string) - ISO date string or function

Use cases: Publish dates, event dates, timestamps, deadlines

Image Field

Image picker integrated with asset manager.

typescript
{
  name: 'thumbnail',
  type: 'image',
  label: 'Thumbnail Image',
  required: false,
  default: '/placeholder.jpg'
}

Properties:

  • required?: boolean
  • default?: string - Default image URL

Use cases: Thumbnails, featured images, avatars, logos

Richtext Field

Rich text editor for formatted content.

typescript
{
  name: 'content',
  type: 'richtext',
  label: 'Article Content',
  required: true,
  default: '<p>Start writing...</p>'
}

Properties:

  • required?: boolean
  • default?: string - HTML string

Use cases: Blog content, product descriptions, long-form text

Reference Field

Reference to another collection record.

typescript
{
  name: 'category',
  type: 'reference',
  label: 'Category',
  collectionName: 'categories',  // Which collection to reference
  required: false
}

Properties:

  • collectionName: string - Collection to reference
  • required?: boolean

Use cases: Categories, authors, related items, parent-child relationships

Asset Field

File upload for documents, videos, etc.

typescript
{
  name: 'downloadFile',
  type: 'asset',
  label: 'Downloadable PDF',
  accept: '.pdf,.doc,.docx',  // Accepted file types
  maxSize: 10485760           // Max size in bytes (10MB)
}

Properties:

  • accept?: string - Accepted MIME types or extensions
  • maxSize?: number - Maximum file size in bytes
  • required?: boolean

Use cases: PDFs, videos, audio files, downloadable resources

Complete Examples

Blog Collection

typescript
{
  name: 'blog',
  displayName: 'Blog Posts',
  description: 'Articles and blog posts',
  icon: '📝',
  fields: [
    {
      name: 'title',
      type: 'string',
      label: 'Post Title',
      required: true,
      maxLength: 100
    },
    {
      name: 'slug',
      type: 'string',
      label: 'URL Slug',
      required: true,
      placeholder: 'my-blog-post'
    },
    {
      name: 'author',
      type: 'string',
      label: 'Author Name',
      required: true
    },
    {
      name: 'publishedAt',
      type: 'date',
      label: 'Publish Date',
      required: true
    },
    {
      name: 'featuredImage',
      type: 'image',
      label: 'Featured Image',
      required: false
    },
    {
      name: 'excerpt',
      type: 'string',
      label: 'Short Description',
      maxLength: 200
    },
    {
      name: 'content',
      type: 'richtext',
      label: 'Post Content',
      required: true
    },
    {
      name: 'category',
      type: 'reference',
      label: 'Category',
      collectionName: 'categories',
      required: false
    },
    {
      name: 'featured',
      type: 'boolean',
      label: 'Featured Post',
      default: false
    },
    {
      name: 'tags',
      type: 'string',
      label: 'Tags (comma-separated)',
      placeholder: 'react, typescript, web'
    }
  ]
}

E-commerce Product Collection

typescript
{
  name: 'products',
  displayName: 'Products',
  description: 'E-commerce product catalog',
  icon: '🛍️',
  fields: [
    {
      name: 'name',
      type: 'string',
      label: 'Product Name',
      required: true,
      maxLength: 100
    },
    {
      name: 'sku',
      type: 'string',
      label: 'SKU',
      required: true,
      placeholder: 'PROD-001'
    },
    {
      name: 'price',
      type: 'number',
      label: 'Price',
      required: true,
      min: 0,
      step: 0.01
    },
    {
      name: 'compareAtPrice',
      type: 'number',
      label: 'Compare at Price',
      min: 0,
      step: 0.01
    },
    {
      name: 'description',
      type: 'richtext',
      label: 'Product Description',
      required: true
    },
    {
      name: 'image',
      type: 'image',
      label: 'Product Image',
      required: true
    },
    {
      name: 'inStock',
      type: 'boolean',
      label: 'In Stock',
      default: true
    },
    {
      name: 'stockQuantity',
      type: 'number',
      label: 'Stock Quantity',
      min: 0,
      default: 0
    },
    {
      name: 'category',
      type: 'reference',
      label: 'Category',
      collectionName: 'productCategories'
    },
    {
      name: 'featured',
      type: 'boolean',
      label: 'Featured Product',
      default: false
    }
  ]
}

Team Members Collection

typescript
{
  name: 'team',
  displayName: 'Team Members',
  description: 'Company team members and staff',
  icon: '👥',
  fields: [
    {
      name: 'name',
      type: 'string',
      label: 'Full Name',
      required: true
    },
    {
      name: 'role',
      type: 'string',
      label: 'Job Title',
      required: true,
      placeholder: 'Software Engineer'
    },
    {
      name: 'email',
      type: 'string',
      label: 'Email Address',
      placeholder: 'name@company.com'
    },
    {
      name: 'photo',
      type: 'image',
      label: 'Profile Photo',
      required: true
    },
    {
      name: 'bio',
      type: 'richtext',
      label: 'Biography',
      default: '<p>Tell us about yourself...</p>'
    },
    {
      name: 'linkedin',
      type: 'string',
      label: 'LinkedIn URL',
      placeholder: 'https://linkedin.com/in/username'
    },
    {
      name: 'twitter',
      type: 'string',
      label: 'Twitter Handle',
      placeholder: '@username'
    },
    {
      name: 'featured',
      type: 'boolean',
      label: 'Show on About Page',
      default: true
    },
    {
      name: 'order',
      type: 'number',
      label: 'Display Order',
      default: 0,
      min: 0
    }
  ]
}

Testimonials Collection

typescript
{
  name: 'testimonials',
  displayName: 'Testimonials',
  description: 'Customer testimonials and reviews',
  icon: '⭐',
  fields: [
    {
      name: 'customerName',
      type: 'string',
      label: 'Customer Name',
      required: true
    },
    {
      name: 'company',
      type: 'string',
      label: 'Company Name',
      placeholder: 'Acme Inc.'
    },
    {
      name: 'role',
      type: 'string',
      label: 'Job Title',
      placeholder: 'CEO'
    },
    {
      name: 'photo',
      type: 'image',
      label: 'Customer Photo'
    },
    {
      name: 'rating',
      type: 'number',
      label: 'Rating (1-5)',
      required: true,
      min: 1,
      max: 5,
      default: 5
    },
    {
      name: 'quote',
      type: 'richtext',
      label: 'Testimonial Quote',
      required: true
    },
    {
      name: 'featured',
      type: 'boolean',
      label: 'Featured Testimonial',
      default: false
    },
    {
      name: 'createdAt',
      type: 'date',
      label: 'Date Submitted',
      default: () => new Date().toISOString()
    }
  ]
}

TypeScript Typing

Define TypeScript types that match your collections:

typescript
// types/collections.ts

export type BlogPost = {
  id: string;
  title: string;
  slug: string;
  author: string;
  publishedAt: string;
  featuredImage?: string;
  excerpt?: string;
  content: string;
  category?: string;
  featured: boolean;
  tags?: string;
};

export type Product = {
  id: string;
  name: string;
  sku: string;
  price: number;
  compareAtPrice?: number;
  description: string;
  image: string;
  inStock: boolean;
  stockQuantity: number;
  category?: string;
  featured: boolean;
};

export type TeamMember = {
  id: string;
  name: string;
  role: string;
  email?: string;
  photo: string;
  bio: string;
  linkedin?: string;
  twitter?: string;
  featured: boolean;
  order: number;
};

Use these types in your components:

typescript
import { IBlockswebComponent } from '@blocksweb/core';
import { Product } from '@/types/collections';

type ProductGridProps = {
  products: Product[];  // Type-safe!
};

const ProductGrid: IBlockswebComponent<ProductGridProps> = ({ products }) => {
  return (
    <div className="grid grid-cols-3 gap-4">
      {products.map(product => (
        <div key={product.id}>
          <h3>{product.name}</h3>
          <p>${product.price}</p>
        </div>
      ))}
    </div>
  );
};

Best Practices

1. Use Clear Names

typescript
// ✅ Good - Clear and descriptive
{
  name: 'blog',
  displayName: 'Blog Posts',
  fields: [
    { name: 'publishedAt', type: 'date', label: 'Publish Date' }
  ]
}

// ❌ Bad - Unclear abbreviations
{
  name: 'blg',
  displayName: 'BP',
  fields: [
    { name: 'pubDt', type: 'date', label: 'Pub' }
  ]
}

2. Mark Required Fields

typescript
// ✅ Good - Clear requirements
{
  name: 'title',
  type: 'string',
  label: 'Title',
  required: true  // Editor must fill this
}

3. Provide Defaults

typescript
// ✅ Good - Sensible defaults
{
  name: 'featured',
  type: 'boolean',
  label: 'Featured',
  default: false  // Clear default state
}

{
  name: 'publishedAt',
  type: 'date',
  label: 'Publish Date',
  default: () => new Date().toISOString()  // Auto-set to now
}

4. Add Validation

typescript
// ✅ Good - Constrained inputs
{
  name: 'rating',
  type: 'number',
  label: 'Rating',
  min: 1,
  max: 5,  // Can't be outside this range
  required: true
}

{
  name: 'title',
  type: 'string',
  label: 'Title',
  maxLength: 100,  // SEO-friendly length
  required: true
}

5. Use Placeholders

typescript
// ✅ Good - Helpful hints
{
  name: 'email',
  type: 'string',
  label: 'Email',
  placeholder: 'name@company.com'  // Shows expected format
}

{
  name: 'slug',
  type: 'string',
  label: 'URL Slug',
  placeholder: 'my-blog-post'
}
typescript
// ✅ Good - Hierarchical structure
export const collections = [
  // Products
  { name: 'products', displayName: 'Products', ... },
  { name: 'productCategories', displayName: 'Product Categories', ... },

  // Blog
  { name: 'blog', displayName: 'Blog Posts', ... },
  { name: 'blogCategories', displayName: 'Blog Categories', ... },

  // Team
  { name: 'team', displayName: 'Team Members', ... },
  { name: 'departments', displayName: 'Departments', ... }
];

Complete Configuration Example

Here's a full blocksweb.config.ts with multiple collections:

typescript
// blocksweb.config.ts
import { CollectionDefinition } from '@blocksweb/core';
import { MemoryCollectionProvider } from '@blocksweb/core';

export const collections: CollectionDefinition[] = [
  {
    name: 'blog',
    displayName: 'Blog Posts',
    description: 'Articles and blog posts',
    icon: '📝',
    fields: [
      { name: 'title', type: 'string', label: 'Title', required: true },
      { name: 'slug', type: 'string', label: 'URL Slug', required: true },
      { name: 'content', type: 'richtext', label: 'Content', required: true },
      { name: 'publishedAt', type: 'date', label: 'Publish Date', required: true },
      { name: 'featured', type: 'boolean', label: 'Featured', default: false }
    ]
  },
  {
    name: 'products',
    displayName: 'Products',
    description: 'Product catalog',
    icon: '🛍️',
    fields: [
      { name: 'name', type: 'string', label: 'Name', required: true },
      { name: 'price', type: 'number', label: 'Price', required: true, min: 0 },
      { name: 'description', type: 'richtext', label: 'Description' },
      { name: 'image', type: 'image', label: 'Image', required: true },
      { name: 'inStock', type: 'boolean', label: 'In Stock', default: true }
    ]
  },
  {
    name: 'team',
    displayName: 'Team Members',
    icon: '👥',
    fields: [
      { name: 'name', type: 'string', label: 'Name', required: true },
      { name: 'role', type: 'string', label: 'Role', required: true },
      { name: 'photo', type: 'image', label: 'Photo', required: true },
      { name: 'bio', type: 'richtext', label: 'Bio' }
    ]
  }
];

// For development - use MemoryCollectionProvider
export const collectionProvider = new MemoryCollectionProvider({
  data: {
    blog: [],
    products: [],
    team: []
  }
});

Next Steps

Common Pitfalls

❌ Wrong: Using Spaces in Names

typescript
// ❌ Don't do this
{
  name: 'blog posts',  // Spaces not allowed!
  displayName: 'Blog Posts'
}

✅ Correct: Use camelCase or kebab-case

typescript
// ✅ Do this
{
  name: 'blogPosts',   // camelCase
  // or
  name: 'blog-posts', // kebab-case
  displayName: 'Blog Posts'
}

❌ Wrong: Missing Required Fields

typescript
// ❌ Editor won't know this is required
{
  name: 'title',
  type: 'string',
  label: 'Title'
  // Missing required: true
}

✅ Correct: Mark Required Fields

typescript
// ✅ Clear requirements
{
  name: 'title',
  type: 'string',
  label: 'Title',
  required: true
}