Skip to content

Defining Schemas

Schemas are the foundation of your content model in Trokky. They define what content types exist and what data each type can hold.

import { Schema } from '@trokky/core';
const schema: Schema = {
name: 'article', // Required: unique identifier
title: 'Article', // Required: display name
fields: [ // Required: array of fields
{ name: 'title', type: 'string', required: true },
],
};
PropertyTypeRequiredDescription
namestringYesUnique identifier (lowercase, no spaces)
titlestringYesHuman-readable name for Studio
fieldsField[]YesArray of field definitions
singletonbooleanNoOnly one document can exist
previewobjectNoConfigure document previews
orderingsarrayNoCustom sort options in Studio
iconstringNoIcon identifier for Studio

Single-line text input.

{
name: 'title',
type: 'string',
title: 'Title',
required: true,
validation: {
min: 1,
max: 200,
},
}

Multi-line text area.

{
name: 'description',
type: 'text',
title: 'Description',
rows: 4, // Number of visible rows
}

Numeric input with optional constraints.

{
name: 'price',
type: 'number',
title: 'Price',
validation: {
min: 0,
max: 10000,
precision: 2, // Decimal places
},
}

Toggle or checkbox.

{
name: 'featured',
type: 'boolean',
title: 'Featured',
default: false,
}
// Date only
{
name: 'birthday',
type: 'date',
title: 'Birthday',
}
// Date and time
{
name: 'publishedAt',
type: 'datetime',
title: 'Published At',
}

URL-friendly identifier, auto-generated from source field.

{
name: 'slug',
type: 'slug',
title: 'Slug',
options: {
source: 'title', // Field to generate from
maxLength: 96, // Maximum characters
},
}

WYSIWYG editor for formatted content.

{
name: 'content',
type: 'richtext',
title: 'Content',
}

Image or file upload.

{
name: 'image',
type: 'media',
title: 'Featured Image',
options: {
accept: ['image/*'], // Allowed MIME types
maxSize: 5 * 1024 * 1024, // 5MB limit
},
}

Link to another document.

{
name: 'author',
type: 'reference',
title: 'Author',
options: {
to: 'author', // Target schema name
},
}
// Multiple possible targets
{
name: 'related',
type: 'reference',
options: {
to: ['post', 'page'], // Can reference either type
},
}

Nested structure with its own fields.

{
name: 'seo',
type: 'object',
title: 'SEO Settings',
fields: [
{ name: 'metaTitle', type: 'string', title: 'Meta Title' },
{ name: 'metaDescription', type: 'text', title: 'Meta Description' },
{ name: 'ogImage', type: 'media', title: 'OG Image' },
],
}

List of items.

// Array of strings
{
name: 'tags',
type: 'array',
title: 'Tags',
of: [{ type: 'string' }],
}
// Array of objects
{
name: 'links',
type: 'array',
title: 'Links',
of: [
{
type: 'object',
fields: [
{ name: 'label', type: 'string' },
{ name: 'url', type: 'string' },
],
},
],
}
// Array of references
{
name: 'relatedPosts',
type: 'array',
title: 'Related Posts',
of: [{ type: 'reference', options: { to: 'post' } }],
}

Predefined options (dropdown or radio).

{
name: 'status',
type: 'select',
title: 'Status',
options: {
list: [
{ value: 'draft', title: 'Draft' },
{ value: 'review', title: 'In Review' },
{ value: 'published', title: 'Published' },
],
},
}

All fields support these common properties:

PropertyTypeDescription
namestringUnique field identifier
typestringField type
titlestringDisplay label
descriptionstringHelp text shown below field
requiredbooleanWhether field must have a value
hiddenbooleanHide from Studio
readOnlybooleanPrevent editing
defaultanyDefault value for new documents
validationobjectValidation rules

Add validation rules to ensure data integrity:

{
name: 'email',
type: 'string',
validation: {
email: true, // Must be valid email
},
}
{
name: 'url',
type: 'string',
validation: {
url: true, // Must be valid URL
},
}
{
name: 'title',
type: 'string',
validation: {
min: 5, // Minimum length
max: 100, // Maximum length
pattern: '^[A-Z]', // Regex pattern
},
}

For content that should only have one instance (site settings, homepage, etc.):

{
name: 'siteSettings',
title: 'Site Settings',
singleton: true, // Only one document allowed
fields: [
{ name: 'siteName', type: 'string' },
{ name: 'logo', type: 'media' },
{ name: 'footer', type: 'text' },
],
}

Customize how documents appear in lists:

{
name: 'post',
title: 'Post',
preview: {
select: {
title: 'title',
subtitle: 'author.name',
media: 'featuredImage',
},
prepare({ title, subtitle, media }) {
return {
title,
subtitle: `By ${subtitle}`,
media,
};
},
},
fields: [/* ... */],
}

Here’s a complete blog schema set:

import { Schema } from '@trokky/core';
export const schemas: Schema[] = [
// Author
{
name: 'author',
title: 'Author',
preview: {
select: { title: 'name', media: 'avatar' },
},
fields: [
{ name: 'name', type: 'string', required: true },
{ name: 'slug', type: 'slug', options: { source: 'name' } },
{ name: 'email', type: 'string', validation: { email: true } },
{ name: 'avatar', type: 'media' },
{ name: 'bio', type: 'text' },
{
name: 'social',
type: 'object',
fields: [
{ name: 'twitter', type: 'string' },
{ name: 'github', type: 'string' },
{ name: 'linkedin', type: 'string' },
],
},
],
},
// Category
{
name: 'category',
title: 'Category',
fields: [
{ name: 'name', type: 'string', required: true },
{ name: 'slug', type: 'slug', options: { source: 'name' } },
{ name: 'description', type: 'text' },
],
},
// Post
{
name: 'post',
title: 'Blog Post',
preview: {
select: {
title: 'title',
subtitle: 'author.name',
media: 'featuredImage',
},
},
fields: [
{ name: 'title', type: 'string', required: true },
{ name: 'slug', type: 'slug', options: { source: 'title' } },
{ name: 'author', type: 'reference', options: { to: 'author' } },
{ name: 'categories', type: 'array', of: [{ type: 'reference', options: { to: 'category' } }] },
{ name: 'featuredImage', type: 'media' },
{ name: 'excerpt', type: 'text' },
{ name: 'content', type: 'richtext' },
{ name: 'publishedAt', type: 'datetime' },
{
name: 'status',
type: 'select',
options: {
list: [
{ value: 'draft', title: 'Draft' },
{ value: 'published', title: 'Published' },
],
},
default: 'draft',
},
{
name: 'seo',
type: 'object',
fields: [
{ name: 'metaTitle', type: 'string' },
{ name: 'metaDescription', type: 'text' },
],
},
],
},
// Site Settings (singleton)
{
name: 'siteSettings',
title: 'Site Settings',
singleton: true,
fields: [
{ name: 'siteName', type: 'string', required: true },
{ name: 'tagline', type: 'string' },
{ name: 'logo', type: 'media' },
{ name: 'postsPerPage', type: 'number', default: 10 },
],
},
];