Collections

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

Overview

✨

Unique Flexibility

Unlike most headless CMS platforms that only support one approach, SonicJS lets you create collections both through the admin UI and with code. Start prototyping in the UI, then migrate to code for productionβ€”or use both simultaneously.

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

  • Type-safe database schema
  • Automatic API endpoints
  • Admin UI for content management
  • Field validation and constraints
  • Version control through git

Two Types of Collections

πŸ’»

Code-Based Collections

  • βœ“Defined in TypeScript files (src/collections/*.collection.ts)
  • βœ“Version controlled with your codebase
  • βœ“Automatically synced on app startup
  • βœ“Read-only in admin UI (marked with "Config" badge)
  • βœ“Full TypeScript support with IDE autocomplete
Best for: Production apps, teams, CI/CD

πŸ–±οΈ

UI-Created Collections

  • βœ“Created and edited through the admin interface
  • βœ“Stored directly in the database
  • βœ“Fully editable in the UI anytime
  • βœ“No code or deployment required
  • βœ“Great for rapid prototyping
Best for: Prototyping, non-developers, quick iterations

Creating Collections

SonicJS offers two ways to create collections: through the admin UI or with code. Each approach has its own advantages depending on your workflow and requirements.

Comparison: UI vs Code-Based Collections

AspectAdmin UICode-Based
Best forRapid prototyping, non-developers, quick iterationsProduction apps, team collaboration, CI/CD
Version controlNot version controlledFull git history
EditingFully editable in adminRead-only in admin (edit code instead)
Type safetyNoneFull TypeScript support with IDE autocomplete
DeploymentManual recreation per environmentAutomatic sync across environments
Field typesLimited to active editor pluginsAll 30+ field types available

Option 1: Creating Collections via Admin UI

The admin UI provides a visual interface for creating and managing collections without writing code.

Step 1: Access Collections

Navigate to /admin/collections in your browser. You'll see a list of all existing collections.

Step 2: Create New Collection

Click the "New Collection" button in the top right corner.

Step 3: Fill in Collection Details

Collection Form Fields

Display Name: Blog Posts
Collection Name: blog_posts (lowercase, numbers, underscores only)
Description: Manage your blog content (optional)

Click "Create Collection" to save. You'll be redirected to the collection edit page.

Step 4: Add Fields

On the collection edit page, click "Add Field" to open the field modal. Configure each field with:

  • Field Name - Machine name (lowercase, underscores)
  • Field Type - Choose from: text, richtext, number, boolean, date, select, media
  • Field Label - Human-readable display name
  • Required - Whether the field must have a value
  • Searchable - Include in content search results
  • Field Options - JSON configuration for field-specific settings

Example Field Options

// For select fields
{"options": ["draft", "published", "archived"]}

// For media fields
{"accept": "image/*", "maxSize": "10MB"}

// For text fields
{"maxLength": 200, "placeholder": "Enter title..."}

Step 5: Manage Fields

After adding fields, you can:

  • Reorder fields using drag handles
  • Edit field settings by clicking the edit button
  • Delete fields using the delete button

UI-Created Collection Limitations

Collections created via UI have fewer field types available compared to code-based collections. Rich text editors (TinyMCE, Quill, EasyMDX) are only available if the corresponding plugin is active.


Option 2: Creating Collections with Code

Code-based collections provide type safety, version control, and the full range of field types.

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({
  collections: {
    autoSync: true
  }
})

Step 3: Sync Collections

Collections are automatically synced on server startup. You can also manually sync:

Sync Collections

# Collections auto-sync on server restart
npm run dev

# Or manually sync
npm run sync-collections

Config Badge in Admin UI

Code-based collections appear in the admin UI with a purple "Config" badge and are read-only. To modify them, edit the TypeScript file and restart the server.


When to Use Each Approach

Use the Admin UI when:

  • Prototyping a new content model quickly
  • Non-developers need to create collections
  • You need to iterate on schema design rapidly
  • Building a one-off project without deployment concerns

Use Code-Based collections when:

  • Building production applications
  • Working with a team (version control is essential)
  • You need type safety and IDE autocomplete
  • Deploying across multiple environments (staging, production)
  • Using CI/CD pipelines
  • You need advanced field types or validation

Migration Path

You can start with UI-created collections for prototyping, then convert them to code-based collections for production. Export the schema JSON from the database and convert it to a TypeScript collection file.


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.


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

Query Content

Fetch Collection Content

// Get all content from a collection
const posts = await db.select()
  .from(content)
  .where(eq(content.collection_id, 'blog_posts'))
  .orderBy(desc(content.created_at))
  .limit(10)

// Get single content item
const post = await db.select()
  .from(content)
  .where(eq(content.id, postId))
  .get()

// Search content
const results = await db.select()
  .from(content)
  .where(
    and(
      eq(content.collection_id, 'blog_posts'),
      like(content.title, '%search%')
    )
  )
  .all()

API Endpoints

Collections automatically get REST API endpoints:

GET/api/collections/{collection}/content
Get all content from a collection
GET/api/content/{id}
Get single content item
POST/admin/contentAuth required
Create new content
PUT/admin/content/{id}Auth required
Update content
DELETE/admin/content/{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?