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
π±οΈ
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
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
| Aspect | Admin UI | Code-Based |
|---|---|---|
| Best for | Rapid prototyping, non-developers, quick iterations | Production apps, team collaboration, CI/CD |
| Version control | Not version controlled | Full git history |
| Editing | Fully editable in admin | Read-only in admin (edit code instead) |
| Type safety | None | Full TypeScript support with IDE autocomplete |
| Deployment | Manual recreation per environment | Automatic sync across environments |
| Field types | Limited to active editor plugins | All 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:
/api/collections/{collection}/content/api/content/{id}/admin/contentAuth required/admin/content/{id}Auth required/admin/content/{id}Auth requiredBest 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
listFieldsto improve list performance - Use appropriate field types (e.g.,
selectinstead ofstringfor fixed options) - Index frequently queried fields in your database