Schema-as-code
for Contentful
Define content models in TypeScript. Generate versioned migrations. The Drizzle ORM workflow you know — for your CMS.
Features
Everything you need
The Contentful workflow, done right.
Schema-as-code
Define content types in TypeScript. Full type safety, autocompletion, and version control.
Auto-generated migrations
Diff local schemas against Contentful. Get timestamped migration files automatically.
Migration tracking
SHA-256 checksums, execution history, and status. Tracked in Contentful itself.
Pull from Contentful
Reverse-engineer existing content types into local TypeScript schemas.
Direct push
Skip migrations during prototyping. Push schemas directly with dry-run support.
Full validation support
Regex, ranges, unique constraints, enums, image dimensions, rich text nodes. All typed.
Workflow
The workflow you already know
If you've used Drizzle Kit, you're already home.
Define your schema
Write content types as TypeScript. Get type safety, autocompletion, and version control.
schemas/blogPost.ts Generate a migration
ctkit diffs your local schemas against Contentful and produces a timestamped migration file.
$ ctkit generate Review and apply
Check the generated migration, then apply it. Migration history is tracked automatically.
$ ctkit migrate Verify
Confirm your local schemas match Contentful. Done.
$ ctkit check — All schemas are up to date Examples
Expressive schema definitions
Every Contentful field type, validation, and property — fully typed.
import {
ContentTypeSchema,
FieldType, LinkType, Mark, NodeType,
validators, richTextValidators,
} from '@ctkit/core';
const blogPost: ContentTypeSchema = {
id: 'blogPost',
name: 'Blog Post',
displayField: 'title',
fields: [
{
id: 'title',
name: 'Title',
type: FieldType.Symbol,
required: true,
validations: [
validators.unique(),
validators.textLength(1, 200),
],
},
{
id: 'slug',
name: 'Slug',
type: FieldType.Symbol,
required: true,
validations: [
validators.unique(),
validators.slug(),
],
},
{
id: 'body',
name: 'Body',
type: FieldType.RichText,
required: true,
validations: [
richTextValidators.allowedMarks([
Mark.Bold, Mark.Italic, Mark.Code,
]),
richTextValidators.allowedNodeTypes([
NodeType.Heading2, NodeType.Heading3,
NodeType.OrderedList, NodeType.UnorderedList,
NodeType.Blockquote, NodeType.Hyperlink,
]),
],
},
{
id: 'author',
name: 'Author',
type: FieldType.Link,
linkType: LinkType.Entry,
required: true,
validations: [
{ linkContentType: ['author'] },
],
},
],
};
export default blogPost;import {
ContentTypeSchema,
FieldType, LinkType, MimeType,
validators,
} from '@ctkit/core';
const product: ContentTypeSchema = {
id: 'product',
name: 'Product',
description: 'An e-commerce product',
displayField: 'name',
fields: [
{
id: 'name',
name: 'Name',
type: FieldType.Symbol,
required: true,
},
{
id: 'price',
name: 'Price',
type: FieldType.Number,
required: true,
validations: [validators.numberRange(0)],
},
{
id: 'sku',
name: 'SKU',
type: FieldType.Symbol,
required: true,
validations: [validators.unique()],
},
{
id: 'images',
name: 'Images',
type: FieldType.Array,
required: false,
items: {
type: FieldType.Link,
linkType: LinkType.Asset,
validations: [
{ linkMimetypeGroup: [MimeType.Image] },
],
},
validations: [validators.arraySize(0, 10)],
},
{
id: 'brand',
name: 'Brand',
type: FieldType.Link,
linkType: LinkType.Entry,
required: false,
validations: [
{ linkContentType: ['brand'] },
],
},
],
};
export default product;CLI
Familiar commands
The same command structure as Drizzle Kit.
| Command | Description |
|---|---|
| ctkit generate | Generate migration from schema diff |
| ctkit migrate | Apply pending migrations |
| ctkit push | Push schemas directly to Contentful |
| ctkit check | Diff local schemas vs Contentful |
| ctkit pull | Pull content types into local schemas |
| ctkit status | Show migration status |
| ctkit drop | Delete content types |
Get started in seconds
Install, init, and push your first schema.