Collections

Define content types with TypeScript schemas and field validation. Collections are the foundation of your content model in SonicJS.

Overview

Collections define your content types using TypeScript schemas. Each collection automatically gets:

  • Automatic API endpoints
  • Admin UI for content management
  • Field validation and constraints
  • Version control through git

Creating Collections

Collections are defined in TypeScript and registered in code. The /admin/collections page is read-only — it shows all registered collections but does not allow creating or editing them.

Step 1: Create Collection File

Create a new file in src/collections/ with a .collection.ts extension:

Blog Posts Collection

// src/collections/blog-posts.collection.ts
import type { CollectionConfig } from '@sonicjs-cms/core'

export default {
  name: 'blog_posts',
  displayName: 'Blog Posts',
  description: 'Articles and blog content',
  icon: '📝',

  schema: {
    type: 'object',
    properties: {
      title: {
        type: 'string',
        title: 'Title',
        required: true,
        minLength: 3,
        maxLength: 200,
      },
      slug: {
        type: 'slug',
        title: 'URL Slug',
        required: true,
      },
      content: {
        type: 'richtext',
        title: 'Content',
        required: true,
      },
      excerpt: {
        type: 'textarea',
        title: 'Excerpt',
        maxLength: 500,
        helpText: 'A short summary of the post',
      },
      author: {
        type: 'reference',
        title: 'Author',
        collection: 'users',
        required: true,
      },
      publishDate: {
        type: 'datetime',
        title: 'Publish Date',
        required: true,
      },
      status: {
        type: 'select',
        title: 'Status',
        enum: ['draft', 'published', 'archived'],
        enumLabels: ['Draft', 'Published', 'Archived'],
        default: 'draft',
      },
    },
    required: ['title', 'slug', 'content', 'author', 'publishDate'],
  },

  // Display configuration
  listFields: ['title', 'author', 'publishDate', 'status'],
  searchFields: ['title', 'excerpt', 'content'],
  defaultSort: 'createdAt',
  defaultSortOrder: 'desc',

  // Mark as config-managed (read-only in admin UI)
  managed: true,
  isActive: true,
} satisfies CollectionConfig

Step 2: Register the Collection

In your application entry point, register your collections:

Register Collections

// src/index.ts
import { createSonicJSApp, registerCollections } from '@sonicjs-cms/core'
import blogPostsCollection from './collections/blog-posts.collection'

// Register custom collections
registerCollections([blogPostsCollection])

// Create and export the app
export default createSonicJSApp({})

Step 3: Restart Dev Server

Collections are registered at bootstrap. Restart the dev server to pick up new collections:

npm run dev

Admin UI

Registered collections appear in the admin UI at /admin/collections (read-only). Content is stored in the shared documents table — no per-collection DB table is created.


Field Types

SonicJS supports 30+ field types for building rich content schemas:

Text Fields

  • Name
    string
    Type
    string
    Description

    Single-line text input. Use for titles, names, short text.

  • Name
    textarea
    Type
    string
    Description

    Multi-line plain text. Use for descriptions, notes, long text.

  • Name
    email
    Type
    string
    Description

    Email with validation. Use for contact emails, author info.

  • Name
    url
    Type
    string
    Description

    URL with validation. Use for links, external resources.

  • Name
    slug
    Type
    string
    Description

    URL-friendly identifier. Use for page URLs, SEO paths.

  • Name
    color
    Type
    string
    Description

    Color picker. Use for theme colors, UI customization.

Rich Content

  • Name
    richtext
    Type
    string
    Description

    WYSIWYG HTML editor. Use for blog posts, articles, formatted content.

  • Name
    markdown
    Type
    string
    Description

    Markdown editor. Use for documentation, technical content.

  • Name
    json
    Type
    object
    Description

    JSON editor. Use for structured data, API responses.

Numbers and Dates

  • Name
    number
    Type
    number
    Description

    Numeric input. Use for prices, quantities, ratings.

  • Name
    date
    Type
    string
    Description

    Date picker (no time). Use for birthdays, deadlines.

  • Name
    datetime
    Type
    string
    Description

    Date and time picker. Use for publish dates, events.

Selections

  • Name
    select
    Type
    string
    Description

    Dropdown (single choice). Use for categories, status fields.

  • Name
    multiselect
    Type
    array
    Description

    Dropdown (multiple choices). Use for tags, multiple categories.

  • Name
    radio
    Type
    string
    Description

    Radio buttons. Use for status, visibility options.

  • Name
    checkbox
    Type
    boolean
    Description

    Boolean checkbox. Use for feature toggles, flags.

Media and Files

  • Name
    media
    Type
    string
    Description

    Image/media picker. Use for featured images, avatars.

  • Name
    file
    Type
    string
    Description

    File upload. Use for PDFs, documents, downloads.

Relationships

  • Name
    reference
    Type
    string
    Description

    Reference to another collection. Use for authors, categories.

  • Name
    array
    Type
    array
    Description

    Array of items. Can contain any field type.


Content Blocks & Repeatable Fields

There are three main patterns to model structured content in a page: blocks for mixed layouts, arrays for repeatable items, and objects for grouped fields.

When to use each:

  • Blocks - Mixed layouts in a single list (page builder sections).
  • Arrays - Repeatable items with the same shape (team members, FAQs, links).
  • Objects - Grouped fields that belong together (SEO, settings).
Blocks can include nested arrays/objects for repeatable sub-fields inside a block.

Content Blocks

Use an array field with items.blocks to define reusable content blocks (hero, text + image, testimonials, CTAs) with add/remove/reorder controls.

Blocks Field

body: {
  type: 'array',
  title: 'Content Blocks',
  items: {
    type: 'object',
    discriminator: 'blockType',
    blocks: {
      text: {
        label: 'Text',
        properties: {
          heading: { type: 'string', title: 'Heading', required: true },
          body: { type: 'textarea', title: 'Body text', required: true },
        }
      },
      callToAction: {
        label: 'Call To Action',
        properties: {
          title: { type: 'string', title: 'Heading', required: true },
          body: { type: 'textarea', title: 'Body text', required: true },
          buttonLabel: { type: 'string', title: 'Button label',  required: true },
          buttonUrl: { type: 'url', title: 'Button link',  required: true },
        }
      }
    }
  }
}

Repeatable Lists (Arrays)

Use arrays for repeatable items that share the same structure.

Team Members List

teamMembers: {
  type: 'array',
  title: 'Team Members',
  items: {
    type: 'object',
    properties: {
      name: { type: 'string', title: 'Name', required: true },
      role: { type: 'string', title: 'Role' },
      bio: { type: 'textarea', title: 'Bio' },
      photo: { type: 'media', title: 'Photo' },
    },
  },
},

Grouped Fields (Objects)

Use objects to group related fields under a single heading.

Grouped SEO Fields

seo: {
  type: 'object',
  title: 'SEO',
  properties: {
    title: { type: 'string', title: 'SEO title' },
    description: { type: 'textarea', title: 'SEO description' },
  },
},

Validation

Built-in Validators

Field Validation

export const productsCollection: CollectionConfig = {
  name: 'products',
  displayName: 'Products',
  schema: {
    type: 'object',
    properties: {
      name: {
        type: 'string',
        required: true,
        minLength: 3,
        maxLength: 200,
      },
      sku: {
        type: 'string',
        required: true,
        pattern: '^[A-Z0-9-]+$', // Only uppercase, numbers, hyphens
      },
      price: {
        type: 'number',
        required: true,
        minimum: 0,
        maximum: 999999,
      },
      email: {
        type: 'email',
        required: true,
      },
      url: {
        type: 'url',
        required: false,
      },
      status: {
        type: 'select',
        enum: ['draft', 'active', 'archived'],
        default: 'draft',
      },
    },
  },
  managed: true,
  isActive: true,
}

Validation Rules

  • required - Field must have a value
  • minLength / maxLength - String length constraints
  • minimum / maximum - Number range constraints
  • pattern - Regular expression validation
  • enum - Must be one of specified values
  • default - Default value if not provided

Examples

E-commerce Product

Product Collection

export const productsCollection: CollectionConfig = {
  name: 'products',
  displayName: 'Products',
  description: 'E-commerce product catalog',
  schema: {
    type: 'object',
    properties: {
      name: {
        type: 'string',
        required: true,
      },
      sku: {
        type: 'string',
        required: true,
      },
      price: {
        type: 'number',
        required: true,
        minimum: 0,
      },
      description: {
        type: 'richtext',
      },
      images: {
        type: 'array',
        items: {
          type: 'media',
          accept: 'image/*',
        },
      },
      category: {
        type: 'select',
        enum: ['electronics', 'clothing', 'home', 'sports'],
      },
      inStock: {
        type: 'boolean',
        default: true,
      },
      tags: {
        type: 'array',
        items: { type: 'string' },
      },
    },
  },
  icon: '🛍️',
  managed: true,
  isActive: true,
}

Documentation Page

Docs Collection

export const docsCollection: CollectionConfig = {
  name: 'documentation',
  displayName: 'Documentation',
  schema: {
    type: 'object',
    properties: {
      title: {
        type: 'string',
        required: true,
      },
      slug: {
        type: 'slug',
        required: true,
      },
      content: {
        type: 'markdown',
        required: true,
      },
      category: {
        type: 'select',
        enum: ['getting-started', 'api', 'guides', 'reference'],
      },
      order: {
        type: 'number',
        default: 0,
      },
      published: {
        type: 'boolean',
        default: false,
      },
    },
  },
  icon: '📚',
  managed: true,
  isActive: true,
}

Event Management

Events Collection

export const eventsCollection: CollectionConfig = {
  name: 'events',
  displayName: 'Events',
  schema: {
    type: 'object',
    properties: {
      title: {
        type: 'string',
        required: true,
      },
      description: {
        type: 'richtext',
      },
      startDate: {
        type: 'datetime',
        required: true,
      },
      endDate: {
        type: 'datetime',
        required: true,
      },
      location: {
        type: 'string',
      },
      virtualLink: {
        type: 'url',
      },
      capacity: {
        type: 'number',
        minimum: 1,
      },
      registration: {
        type: 'boolean',
        default: false,
      },
      organizer: {
        type: 'reference',
        collection: 'users',
      },
    },
  },
  icon: '📅',
  managed: true,
  isActive: true,
}

Using Collections

API Endpoints

Collections automatically get REST API endpoints:

GET/api/{collection}
List all content in a collection
GET/api/{collection}/{id}
Get single content item
POST/api/{collection}Auth required
Create new content
PUT/api/{collection}/{id}Auth required
Update content
DELETE/api/{collection}/{id}Auth required
Delete content

Best Practices

Collection Design Tips

  • Use descriptive field names that reflect your content model - Set appropriate validation rules to maintain data quality - Use references for relationships between collections - Enable search on fields that users will query frequently - Set reasonable defaults for optional fields

Performance Considerations

  • Limit the number of fields in listFields to improve list performance - Use appropriate field types (e.g., select instead of string for fixed options)
  • Index frequently queried fields in your database

Next Steps

Was this page helpful?