The breaking point
At work, we have over 100 content types in Contentful. Many of them are siblings. They share the same fields, the same validations, the same help texts for editors, the same allowed embedded entries. Consistency across all of them is what makes the system work.
Maintaining that by hand is a nightmare. A typo in a validation. A wrong checkbox. A copy-paste where you forgot to update one field. Your standardization is gone, and so is trust in whoever set it up in the first place.
I was leading a project that required 15 new content types. Three levels of specificity, five variations. Same fields across all of them, with one level differing in validation. That's roughly 150 fields to configure by hand. Field names, validations, rich text settings, help texts. Each one a chance to mess up.
I wasn't going to click through that.
Contentful is just a database
Here's the thing people don't say enough: Contentful is just a database with a UI on top. A nice UI, sometimes. But still a database. And we've solved the "manage your database schema in code" problem years ago.
Drizzle Kit does this for SQL databases. You define your schema in TypeScript, run a command, and it generates migrations. Your schema file is the source of truth. You can read it, diff it, review it in a PR, and know exactly what your database looks like right now.
I wanted that for Contentful. I was surprised nothing like it existed. So I built ctkit — Content Type Kit.
The name went through a few iterations. The first prototype was called "Can'tentful" — I felt limited by Contentful's interface. That felt a bit aggressive. "Schemful" had no good domain. "ctkit" stuck: works for "Content Type Kit" and "Contentful Kit." Maybe Contentful is just the start.
The migration trap
Contentful does have a migration CLI. I tried it. Had Claude write the migration scripts. It worked, technically.
But there's a fundamental problem: migrations only give you diffs, never the full picture. To understand what a content type looks like today, you have to replay every migration that ever touched it.
It's like a git repo where you can only see commits but never the actual files. You can trace the history of changes, but you never get a clean snapshot of the current state. That's not a source of truth. That's archaeology.
I wanted .ts files I could open and immediately see: here's every field, every validation, every setting. The full picture.
The workflow
Install @ctkit/cli, run ctkit init, and you get a small example: a single blogPost content type with its migration already generated. Run ctkit migrate and the type appears in your Contentful space.
The real moment is when you create your own type, push it, and then need to tweak something. A validation change. A new allowed entry type. It's a one-line change in your schema file, a couple CLI commands, and it's live. No clicking through 10 screens. No risk of fat-fingering a checkbox.
import { defineContentType, FieldType } from "@ctkit/core";
export const blogPost = defineContentType({
id: "blogPost",
name: "Blog Post",
fields: [
{ id: "title", name: "Title", type: FieldType.SYMBOL, required: true },
{ id: "slug", name: "Slug", type: FieldType.SYMBOL, required: true },
{ id: "body", name: "Body", type: FieldType.RICH_TEXT },
],
});That's your source of truth. Readable, diffable, reviewable.
AI writes your content model
This is the real superpower. Because your content model is just TypeScript, AI can manage it. AI knows how to write code. AI can read ctkit's documentation. It's better than you at writing schemas, and it's better than me. And unlike clicking through a UI, you can hand AI a description of what you need and get back a valid, consistent schema in seconds.
How ctkit was built
I didn't write a single line of ctkit by hand. Claude did the building. I was the architect — long planning sessions to iron out the CLI interface, the schema API, the homepage, the docs. Then Claude wrote every line of TypeScript, every test, every documentation page.
The rough prototype came together in H2 2025. A week of prompting with Sonnet. That prototype created the content types that are now running on seatgeek.com, powering millions of pages. At that point it was a scrappy tool that worked for my use case and nothing else.
In May 2026, I spent a single day to productionize it. Split the monorepo into @ctkit/cli and @ctkit/core. Built a homepage, docs site, and a demo video. All in one session. The gap between "rough prototype" and "published open-source tool" collapsed from months to hours. AI got better, and so did my ability to direct it.
What's next
Right now I'm focused on battle-hardening the core. Stress-testing content model creation, updates, field changes, deletions, and migration edge cases. Eventually, data migrations. The kind of boring, essential work that makes a tool trustworthy.
I'm also working on making adoption easier for people who already have a Contentful space full of content types. ctkit has a pull command that imports your existing content model into schema files, so you don't have to start from scratch. That's the bridge between "I've been using Contentful for years" and "now my content model is in code."
ctkit needs to be bulletproof before it grows.
Try it
The source is on GitHub: gcampes/ctkit. The CLI is on npm as @ctkit/cli, and the core types and constants are at @ctkit/core. Docs and homepage live at gcampes.github.io/ctkit.
Built with Claude through OpenCode. Inspired by Drizzle Kit.