Templates Guide
What are Templates?
Templates in Sailor are TypeScript definitions that describe your content structure. Think of them as blueprints that tell the CMS:
- What content types you want to manage (posts, pages, products, etc.)
- What fields each content type should have (title, content, images, etc.)
- How the content should behave (SEO options, blocks support, URL structure)
How Templates Work
- Define: Write TypeScript templates describing your content structure
- Generate: Sailor automatically creates database tables, admin forms, and TypeScript types
- Use: Start creating and managing content through the admin interface
- Display: Use the generated utilities to fetch and display content in your frontend
Benefits
- No Database Setup: Sailor handles all the drizzle database schema generation
- Type Safety: Full TypeScript support with auto-generated types
- Admin UI: Beautiful, responsive admin interface automatically generated
- Flexible: Easy to modify and extend as your needs change
- Consistent: Standardized approach across all your content types
Template Types
Sailor supports three main template types:
- Collections: Content with multiple entries (blog posts, pages, products)
- Blocks: Reusable content components (hero sections, galleries, features)
- Globals: Site-wide content (settings, menus, configuration)
Common Template Structure
All templates share these core elements:
export const myTemplate = {
name: { singular: 'Item', plural: 'Items' },
slug: 'items',
description: 'Description of this content type',
icon: 'FileText', // Optional: sidebar icon
options: {
// Template-specific options
},
fields: {
// Your custom fields
}
};
Available Icons
Collections and globals automatically appear in the admin sidebar with these icons: FileText, Layout, FolderTree, HelpCircle, Menu, Settings, Image, Users, Calendar, Tag, Database, Globe, ShoppingCart, BarChart
Core Fields
Collections & Repeatable Globals automatically include:
id- UUID primary keytitle- Display name (required)slug- URL-friendly identifier (required, unique)-
status- Published/draft status (enum: ‘draft’‘published’ ‘private’ ‘archived’) author- Content creator (user ID, auto-populated, hidden by default)sort- Manual ordering for drag-and-dropparent_id- Parent item for hierarchical relationships (hidden by default)created_at/updated_at- Timestampslast_modified_by- User who last modified the item (hidden by default)
Flat Globals (dataType: 'flat') only include:
id- UUID primary keycreated_at/updated_at- Timestamps
Blocks include:
title- Optional display namesort- Manual ordering (required)
Collections
Content types with multiple entries (posts, pages, products).
Basic Collection
// src/lib/sailor/templates/collections/posts.ts
import type { CollectionDefinition } from 'sailorcms/core/types';
export const postsCollection: CollectionDefinition = {
name: { singular: 'Post', plural: 'Posts' },
slug: 'posts',
description: 'Blog posts with rich content',
icon: 'FileText',
options: {
titleField: 'title',
seo: true,
blocks: true,
basePath: '/blog/',
sortable: true
},
fields: {
excerpt: {
type: 'textarea',
label: 'Excerpt',
description: 'Short summary of the post'
},
content: {
type: 'richText',
label: 'Content'
},
featured_image: {
type: 'file',
label: 'Featured Image',
items: { fileType: 'image' }
},
tags: {
type: 'tags',
label: 'Tags'
}
}
};
Collection Options
| Option | Type | Description |
|---|---|---|
titleField | string | Field to display in admin lists and overviews |
seo | boolean | Adds SEO fields (meta_title, meta_description, og_title, og_description, og_image, canonical_url, noindex) |
blocks | boolean | Enable/disable blocks functionality (default: true) |
basePath | string | Base URL path for preview links and SEO canonical URLs |
sortable | boolean | Enable drag-and-drop sorting on the collection table |
nestable | boolean | Enable parent-child hierarchical relationships |
revisions | boolean \| { keep: number } | Snapshot a revision on every save. true keeps the last 50; pass { keep: N } to override. See Revisions below. |
Registration
Register in src/lib/sailor/templates/collections/index.ts:
export { postsCollection as posts } from './posts';
export { productsCollection as products } from './products';
Blocks
Reusable content components for flexible layouts.
Basic Block
// src/lib/sailor/templates/blocks/hero.ts
export const heroBlock = {
name: 'Hero Section',
slug: 'hero',
description: 'Large banner with title and background image',
options: {
titleField: 'title' // Field to display as title in block view
},
fields: {
title: {
type: 'string',
required: true,
label: 'Title'
},
subtitle: {
type: 'string',
label: 'Subtitle'
},
background_image: {
type: 'file',
label: 'Background Image',
items: { fileType: 'image' }
},
cta_buttons: {
type: 'array',
label: 'CTA Buttons',
items: {
type: 'object',
label: 'Button',
properties: {
text: { type: 'string', label: 'Button Text' },
url: { type: 'string', label: 'Button URL' },
style: {
type: 'select',
label: 'Button Style',
options: [
{ label: 'Primary', value: 'primary' },
{ label: 'Secondary', value: 'secondary' }
]
}
}
}
}
}
};
Block Scoping
Limit which block types are available for specific collections:
fields: {
layout: {
type: 'blocks',
label: 'Page Layout',
blocks: ['hero', 'richText', 'gallery'] // Only these block types allowed
}
}
Registration
Register in src/lib/sailor/templates/blocks/index.ts:
export { heroBlock as hero } from './hero';
export { galleryBlock as gallery } from './gallery';
Globals
Site-wide settings and repeatable content.
Flat Global (Single Entry)
// src/lib/sailor/templates/globals/settings.ts
import type { GlobalDefinition } from 'sailorcms/core/types';
export const settingsGlobal: GlobalDefinition = {
name: { singular: 'Settings', plural: 'Settings' },
slug: 'settings',
description: 'Global site configuration',
icon: 'Settings',
dataType: 'flat', // Single entry, no title/slug/status fields
options: {
titleField: 'site_name'
},
fields: {
site_name: {
type: 'string',
required: true,
label: 'Site Name'
},
tagline: {
type: 'string',
label: 'Tagline'
},
logo: {
type: 'file',
label: 'Site Logo',
items: { fileType: 'image' }
}
}
};
Repeatable Global (Multiple Entries)
// src/lib/sailor/templates/globals/menus.ts
export const menusGlobal: GlobalDefinition = {
name: { singular: 'Menu', plural: 'Menus' },
slug: 'menus',
description: 'Navigation menus',
icon: 'Menu',
dataType: 'repeatable', // Multiple entries with title/slug/status fields
options: {
sortable: true // Enable sorting for menu items
},
fields: {
name: { type: 'string', required: true, label: 'Menu Name' },
items: {
type: 'array',
label: 'Menu Items',
items: {
type: 'object',
properties: {
label: { type: 'string', label: 'Link Text' },
url: { type: 'string', label: 'URL' }
}
}
}
}
};
Global Options
| Option | Type | Description |
|---|---|---|
titleField | string | Field to display in admin lists and overviews |
sortable | boolean | Enable manual drag-and-drop sorting in the admin UI |
nestable | boolean | Enable parent-child hierarchical relationships (repeatable only) |
inline | boolean | Edit items inline in the list view rather than navigating to a per-item page (repeatable only) |
readonly | boolean | Hide create/edit UI; items are display-only |
defaultView | 'edit' \| 'read' | 'read' opens existing items in a compact static view with an Edit toggle (good for write-once-then-read content like form submissions). New items always start in edit mode regardless. Default: 'edit' |
defaultSort | { field, direction} | Default sort order for list views |
searchable | boolean | Include this global in the frontend search() utility |
revisions | boolean \| { keep: number } | Coming next round — declared in types, not yet wired into the global save path. See Revisions below. |
Registration
Register in src/lib/sailor/templates/globals/index.ts:
export { settingsGlobal as settings } from './settings';
export { menusGlobal as menus } from './menus';
Revisions
Sailor can snapshot every save of a collection item so editors can browse history, see what changed, and roll back. Opt in per template:
export const pagesCollection: CollectionDefinition = {
// ...
options: {
revisions: true // keeps the last 50 revisions per item
}
};
// or with a custom cap
options: {
revisions: {
keep: 200;
}
}
What it stores. Each save writes one row to a polymorphic revisions table containing the entire submitted form payload (row fields + array fields + blocks + file relations + tags). One save = one row, no timestamp pairing or partial diff merging.
The History dialog. When revisions are enabled and the item has been saved at least once, a History icon appears in the admin header next to the Payload action. The dialog opens to the most recent revision and lets editors arrow-navigate back through history. The source view is syntax-highlighted JSON; for any older revision it switches to a unified diff against the current state (added lines green, removed lines red).
Restore. Clicking Restore this version re-runs the save command with the snapshot’s payload — the same code path as a normal save, so search-index updates and a fresh revision row both happen automatically. The restored save itself becomes the new tip of history, so undo-of-restore is just navigating back one entry and restoring again.
Pruning. After every save, the writer keeps the newest keep revisions for that item and drops the rest. Default is 50.
Soft-delete vs hard-delete. Revisions persist through soft-delete so a recovered item still has its history. Permanent purge (/sailor/recovery) drops the entity’s revisions alongside the row.
Globals. Coverage for globals is on the next-round list — the type system already accepts options.revisions on GlobalDefinition so templates compile, but the save path doesn’t write yet.
Common Field Types
Basic Fields
fields: {
// Text
title: { type: 'string', required: true, label: 'Title' },
description: { type: 'textarea', label: 'Description' },
content: { type: 'richText', label: 'Content' },
// Media
image: { type: 'file', label: 'Image', items: { fileType: 'image' } },
gallery: { type: 'file', label: 'Gallery', items: { fileType: 'image', multiple: true } },
// Selection
category: { type: 'select', label: 'Category', options: ['News', 'Tutorial', 'Review'] },
tags: { type: 'tags', label: 'Tags' },
// Relationships
author: { type: 'relation', label: 'Author', relation: { type: 'manyToOne', targetCollection: 'users' } },
// Layout
layout: { type: 'blocks', label: 'Page Layout' }
}
Field Options
Most fields support these common options:
required: boolean- Make field mandatorylabel: string- Display label in admindescription: string- Help text below fieldhidden: boolean- Hide field from admin interfacedefault: any- Default value for new entries
Settings Configuration
Customize CMS behavior in templates/settings.ts:
// src/lib/sailor/templates/settings.ts
import type { CMSSettings } from 'sailorcms/core/settings/types';
export const settings: Partial<CMSSettings> = {
storage: {
images: {
formats: ['webp', 'jpg', 'png'],
maxFileSize: '10.0MB',
maxWidth: 2560,
maxHeight: 2560,
defaultQuality: 85
},
upload: {
maxFileSize: '10.0MB',
allowedTypes: ['*/*'],
folderStructure: 'flat'
}
},
system: {
debugMode: false
}
};
Examples
E-commerce Product
export const productsCollection: CollectionDefinition = {
name: { singular: 'Product', plural: 'Products' },
slug: 'products',
description: 'E-commerce products',
icon: 'ShoppingCart',
options: {
titleField: 'name',
seo: true,
blocks: false,
basePath: '/shop/',
sortable: true
},
fields: {
name: { type: 'string', required: true, label: 'Product Name' },
price: { type: 'number', required: true, label: 'Price' },
description: { type: 'wysiwyg', label: 'Description' },
images: {
type: 'file',
label: 'Product Images',
items: { fileType: 'image', multiple: true }
},
category: { type: 'select', label: 'Category', options: ['Electronics', 'Clothing', 'Books'] }
}
};
Simple Blog Post
export const postsCollection: CollectionDefinition = {
name: { singular: 'Post', plural: 'Posts' },
slug: 'posts',
description: 'Blog posts',
icon: 'FileText',
options: {
titleField: 'title',
seo: true,
blocks: true,
basePath: '/blog/',
sortable: false
},
fields: {
excerpt: { type: 'textarea', label: 'Excerpt' },
content: { type: 'richText', label: 'Content' },
featured_image: { type: 'file', label: 'Featured Image', items: { fileType: 'image' } },
tags: { type: 'tags', label: 'Tags' }
}
};
Note: Many settings like storage provider, S3 credentials, and database URL are configured via environment variables. See
environment-variables.mdfor details.