O ponto de ruptura
No trabalho, a gente tem mais de 100 content types no Contentful. Muitos deles são irmãos. Compartilham os mesmos campos, as mesmas validações, os mesmos help texts pros editores, os mesmos embedded entries permitidos. A consistência entre todos eles é o que faz o sistema funcionar.
Manter isso na mão é um pesadelo. Um typo numa validação. Um checkbox errado. Um copy-paste onde você esqueceu de atualizar um campo. Sua padronização já era, e a confiança em quem configurou aquilo também.
Eu tava liderando um projeto que precisava de 15 novos content types. Três níveis de especificidade, cinco variações. Mesmos campos em todos, com um dos níveis diferindo nas validações. Isso dá mais ou menos 150 campos pra configurar na mão. Nomes de campo, validações, configurações de rich text, help texts. Cada um deles uma chance de errar.
Eu não ia ficar clicando naquilo tudo.
Contentful é só um banco de dados
O que pouca gente fala: Contentful é só um banco de dados com uma UI por cima. Uma UI bonita, às vezes. Mas ainda é um banco de dados. E a gente já resolveu o problema de "gerenciar schema de banco em código" faz tempo.
O Drizzle Kit faz isso pra bancos SQL. Você define seu schema em TypeScript, roda um comando, e ele gera as migrations. Seu arquivo de schema é a fonte da verdade. Dá pra ler, fazer diff, revisar num PR, e saber exatamente como seu banco tá agora.
Eu queria isso pro Contentful. Fiquei surpreso que nada parecido existia. Então construí o ctkit — Content Type Kit.
O nome passou por algumas iterações. O primeiro protótipo se chamava "Can'tentful" — eu me sentia limitado pela interface do Contentful. Soou meio agressivo. "Schemful" não tinha domínio bom. "ctkit" colou: funciona como "Content Type Kit" e "Contentful Kit." Vai saber, talvez o Contentful seja só o começo.
A armadilha das migrations
O Contentful tem uma migration CLI. Eu tentei. Pedi pro Claude escrever os scripts de migration. Funcionou, tecnicamente.
Mas tem um problema fundamental: migrations só te dão diffs, nunca o estado completo. Pra entender como um content type tá hoje, você precisa replay de cada migration que já tocou nele.
É como um repo git onde você só consegue ver os commits mas nunca os arquivos. Dá pra rastrear o histórico de mudanças, mas nunca ter um snapshot limpo do estado atual. Isso não é fonte da verdade. Isso é arqueologia.
Eu queria arquivos .ts que eu pudesse abrir e ver na hora: todos os campos, todas as validações, todas as configurações. O estado completo.
O workflow
Instala o @ctkit/cli, roda ctkit init, e você recebe um exemplo simples: um único content type blogPost com a migration já gerada. Roda ctkit migrate e o tipo aparece no seu space do Contentful.
O momento de verdade é quando você cria seu próprio tipo, faz push, e depois precisa ajustar alguma coisa. Uma mudança de validação. Um novo entry type permitido. É uma mudança de uma linha no seu schema, dois comandos no CLI, e tá no ar. Sem clicar em 10 telas. Sem risco de errar um 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 },
],
});Essa é sua fonte da verdade. Legível, diffável, revisável.
IA escreve seu content model
Esse é o verdadeiro superpoder. Como seu content model é TypeScript, IA consegue gerenciar ele. IA sabe escrever código. IA consegue ler a documentação do ctkit. É melhor que você escrevendo schemas, e melhor que eu. E diferente de ficar clicando numa UI, você pode entregar pra IA uma descrição do que precisa e receber um schema válido e consistente em segundos.
Como o ctkit foi construído
Eu não escrevi uma linha de código do ctkit na mão. O Claude fez a construção. Eu fui o arquiteto — longas sessões de planejamento pra definir a interface do CLI, a API dos schemas, o homepage, a documentação. Depois o Claude escreveu cada linha de TypeScript, cada teste, cada página de docs.
O protótipo veio no segundo semestre de 2025. Uma semana de prompting com o Sonnet. Esse protótipo criou os content types que estão rodando no seatgeek.com agora, por trás de milhões de páginas. Naquele ponto era uma ferramenta tosca que funcionava pro meu caso de uso e mais nada.
Em maio de 2026, eu gastei literalmente um dia pra produtizar tudo. Separei o monorepo em @ctkit/cli e @ctkit/core. Construí um homepage, site de docs e um vídeo demo. Tudo numa sessão só. A distância entre "protótipo tosco" e "ferramenta open-source publicada" colapsou de meses pra horas. A IA ficou melhor, e minha habilidade de direcioná-la também.
Próximos passos
Agora meu foco é blindar o core. Stress-testing de criação de content models, updates, mudanças de campo, deleções e edge cases de migrations. Eventualmente, data migrations. O tipo de trabalho chato e essencial que faz uma ferramenta ser confiável.
Também tô trabalhando pra facilitar a adoção pra quem já tem um space do Contentful cheio de content types. O ctkit tem um comando pull que importa seu content model existente pra arquivos de schema, então você não precisa começar do zero. Essa é a ponte entre "uso Contentful faz anos" e "agora meu content model tá em código."
O ctkit precisa ser à prova de bala antes de crescer.
Experimenta
O código tá no GitHub: gcampes/ctkit. O CLI tá no npm como @ctkit/cli, e os tipos e constantes do core em @ctkit/core. Docs e homepage em gcampes.github.io/ctkit.
Construído com o Claude pelo OpenCode. Inspirado no Drizzle Kit.