npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ctkit/cli

v0.2.0

Published

Content Type Kit — a Drizzle ORM-style workflow for managing Contentful content models

Downloads

64

Readme

ctkit

CI npm version License: MIT

A Drizzle ORM-style workflow for managing Contentful content models. Define content types as TypeScript, generate versioned migrations, and keep your schema in source control.

schemas/blogPost.ts  -->  ctkit generate  -->  ctkit migrate  -->  Contentful

Why ctkit?

If you've used Drizzle Kit for database schemas, you already know this workflow. ctkit brings the same developer experience to Contentful:

  • Schema-as-code -- define content types in TypeScript with full type safety
  • Auto-generated migrations -- diff local schemas against Contentful and generate migration files
  • Versioned migration tracking -- know which migrations have run, with checksums and history
  • Pull existing schemas -- reverse-engineer a Contentful space into local TypeScript files
  • Direct push for rapid prototyping -- bypass migrations when you just want to iterate fast

Installation

# Global (recommended for CLI usage)
npm install -g @ctkit/cli

# Local (for project-scoped usage)
npm install @ctkit/cli --save-dev

Installing @ctkit/cli automatically includes @ctkit/core (types, constants, and validators used in schema files).

Requires Node.js 18+.

Quick Start

1. Initialize a project

ctkit init

This creates a schemas/ directory with an example schema, a migrations/ directory, and a .env.example.

2. Configure your environment

Create a .env file with your Contentful credentials:

CONTENTFUL_SPACE_ID=your_space_id
CONTENTFUL_MANAGEMENT_TOKEN=your_management_token
CONTENTFUL_ENVIRONMENT_ID=master

You can get a management token from Contentful > Settings > API keys > Content management tokens.

3. Test the connection

ctkit test

4. Define a schema

// schemas/blogPost.ts
import { ContentTypeSchema } from "@ctkit/core";

const blogPost: ContentTypeSchema = {
  id: "blogPost",
  name: "Blog Post",
  description: "A blog post with rich content",
  displayField: "title",
  fields: [
    {
      id: "title",
      name: "Title",
      type: "Symbol",
      required: true,
      validations: [{ size: { min: 1, max: 200 } }],
    },
    {
      id: "slug",
      name: "Slug",
      type: "Symbol",
      required: true,
      validations: [
        { unique: true },
        { regexp: { pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" } },
      ],
    },
    {
      id: "body",
      name: "Body",
      type: "RichText",
      required: true,
      validations: [
        { enabledMarks: ["bold", "italic", "underline", "code"] },
        {
          enabledNodeTypes: [
            "heading-2",
            "heading-3",
            "ordered-list",
            "unordered-list",
            "blockquote",
            "hyperlink",
            "embedded-asset-block",
          ],
        },
      ],
    },
    {
      id: "author",
      name: "Author",
      type: "Link",
      linkType: "Entry",
      required: true,
      validations: [{ linkContentType: ["author"] }],
    },
    {
      id: "publishDate",
      name: "Publish Date",
      type: "Date",
      required: false,
    },
    {
      id: "featured",
      name: "Featured",
      type: "Boolean",
      required: false,
    },
  ],
};

export default blogPost;

5. Push to Contentful

# For rapid development -- push directly
ctkit push

# Or use the migration workflow
ctkit generate    # Generate a migration file
ctkit migrate     # Apply it to Contentful

Commands

| Command | Description | Drizzle Equivalent | |---------|-------------|-------------------| | ctkit generate | Generate migration from schema diff | drizzle-kit generate | | ctkit migrate | Apply pending migrations | drizzle-kit migrate | | ctkit push | Push schemas directly to Contentful | drizzle-kit push | | ctkit check | Show diff between local and remote | drizzle-kit check | | ctkit pull | Pull content types into local schemas | drizzle-kit pull | | ctkit status | Show migration status summary | -- | | ctkit history | Show migration execution history | -- | | ctkit drop | Delete content types from Contentful | -- | | ctkit init | Initialize a new project | -- | | ctkit test | Test Contentful connection | -- |

generate

Auto-detect changes between your local schemas and Contentful, then generate a timestamped migration file.

ctkit generate                        # Auto-generate from diff
ctkit generate --name add_categories  # Custom migration name
ctkit generate --custom               # Empty migration for manual edits
ctkit generate schema blogPost        # Generate a new schema file

migrate

Apply pending migrations to Contentful. Tracks execution history using a dedicated content type in your Contentful space.

ctkit migrate                                          # Apply all pending
ctkit migrate --target 20250702_crystal_lion_hammer     # Up to specific migration
ctkit migrate --dry-run                                 # Preview without applying
ctkit migrate --force                                   # Re-run already-executed

push

Push schemas directly to Contentful without going through the migration system. Useful during early development.

ctkit push             # Push all schemas
ctkit push --dry-run   # Preview changes
ctkit push --force     # Skip confirmation

check

Compare local schemas against what's in Contentful. Reports new, modified, unchanged, and remote-only content types.

ctkit check

pull

Reverse-engineer existing Contentful content types into local TypeScript schema files. Great for adopting ctkit on an existing project.

ctkit pull                          # Pull all content types
ctkit pull --content-type blogPost  # Pull specific type
ctkit pull --force                  # Overwrite existing files

drop

Delete content types from Contentful. Destructive -- requires typing "DELETE" to confirm.

ctkit drop                                   # Delete ctkit-managed types
ctkit drop --all-content-types               # Delete ALL content types
ctkit drop --content-type blogPost           # Delete specific type
ctkit drop --include-history                 # Also clear migration tracking
ctkit drop --yes-delete-everything           # Skip confirmation

Schema Reference

Field Types

ctkit supports all Contentful field types:

| Type | ctkit type | linkType | Notes | |------|-----------------|------------|-------| | Short text | "Symbol" | -- | Max 256 characters | | Long text | "Text" | -- | No character limit | | Rich text | "RichText" | -- | Structured content with marks and nodes | | Integer | "Integer" | -- | Whole numbers | | Decimal | "Number" | -- | Floating point | | Date | "Date" | -- | ISO 8601 date/time | | Boolean | "Boolean" | -- | true/false | | Location | "Location" | -- | Latitude/longitude | | JSON | "Object" | -- | Arbitrary JSON | | Entry reference | "Link" | "Entry" | Link to another content type | | Asset reference | "Link" | "Asset" | Link to a media file | | Array of text | "Array" | -- | items: { type: "Symbol" } | | Array of entries | "Array" | -- | items: { type: "Link", linkType: "Entry" } | | Array of assets | "Array" | -- | items: { type: "Link", linkType: "Asset" } |

Field Properties

{
  id: "fieldId",           // Unique field identifier
  name: "Field Name",      // Display name
  type: "Symbol",          // Field type (see table above)
  required: true,          // Required field
  localized: true,         // Enable localization
  disabled: true,          // Disable editing in the UI
  omitted: true,           // Hide from API responses
  helpText: "Hint text",   // Editor help text
  validations: [],         // Validation rules (see below)
  linkType: "Entry",       // For Link fields: "Entry" or "Asset"
  items: {},               // For Array fields: item type definition
}

Validations

Text fields (Symbol, Text)

validations: [
  { unique: true },                                    // Unique values
  { size: { min: 1, max: 200 } },                     // Character length
  { regexp: { pattern: "^[a-z0-9-]+$" } },            // Regex pattern
  { in: ["draft", "published", "archived"] },          // Allowed values
]

Number fields (Integer, Number)

validations: [
  { range: { min: 0, max: 10000 } },                  // Value range
  { in: [10, 20, 50, 100] },                          // Allowed values
  { unique: true },                                    // Unique values
]

Link fields (references)

// Entry reference -- restrict to specific content types
{
  type: "Link",
  linkType: "Entry",
  validations: [{ linkContentType: ["author", "editor"] }],
}

// Asset reference -- restrict to specific media types
{
  type: "Link",
  linkType: "Asset",
  validations: [
    { linkMimetypeGroup: ["image"] },                  // "image", "video", "audio", etc.
    { assetFileSize: { max: 5242880 } },               // Max 5MB
    {
      assetImageDimensions: {
        width: { min: 100, max: 2000 },
        height: { min: 100, max: 2000 },
      },
    },
  ],
}

Array fields

// Array of symbols (tags)
{
  type: "Array",
  items: { type: "Symbol" },
  validations: [{ size: { min: 1, max: 10 } }],       // Array length
}

// Array of entry references
{
  type: "Array",
  items: {
    type: "Link",
    linkType: "Entry",
    validations: [{ linkContentType: ["category"] }],
  },
}

Rich text fields

validations: [
  {
    enabledMarks: ["bold", "italic", "underline", "code"],
  },
  {
    enabledNodeTypes: [
      "heading-2", "heading-3",
      "ordered-list", "unordered-list",
      "blockquote", "hyperlink",
      "embedded-asset-block",
    ],
  },
  {
    // Restrict which content types can be embedded
    nodes: {
      "embedded-entry-inline": [{ linkContentType: ["variable"] }],
      "embedded-entry-block": [{ linkContentType: ["codeBlock", "callout"] }],
    },
  },
]

Validation Helpers

ctkit includes helper functions for common validation patterns:

import { validators, richTextValidators } from "@ctkit/core";

// Text validators
validators.snakeCase()        // ^[a-z][a-z0-9_]*$
validators.kebabCase()        // ^[a-z][a-z0-9-]*$
validators.camelCase()        // ^[a-z][a-zA-Z0-9]*$
validators.slug()             // ^[a-z0-9]+(?:-[a-z0-9]+)*$
validators.email()            // Email pattern
validators.url()              // URL pattern
validators.hexColor()         // #RRGGBB hex color
validators.uuid()             // UUID v4
validators.textLength(1, 200) // Size: { min, max }
validators.textIn(["a", "b"]) // Allowed values
validators.unique()           // Unique constraint

// Number validators
validators.numberRange(0, 100) // Range: { min, max }
validators.numberIn([10, 20])  // Allowed values

// Array validators
validators.arraySize(1, 5)     // Size: { min, max }

// Rich text validators
richTextValidators.basicFormatting()       // Bold, italic, underline, strikethrough
richTextValidators.paragraphsOnly()        // No headings, just paragraphs
richTextValidators.noHeadings()            // All formatting except headings
richTextValidators.headingLevels([2, 3])   // Specific heading levels only
richTextValidators.noEmbeddedContent()     // No embedded entries/assets
richTextValidators.embeddedEntries(["cta"]) // Restrict embedded entry types

Project Structure

my-project/
  schemas/              # Your content type definitions
    blogPost.ts
    author.ts
    category.ts
  migrations/           # Generated migration files (commit these)
    20250702T121925_crystal_lion_hammer.js
    20250703T143021_green_shark_tooth.js
  .env                  # Contentful credentials (DO NOT commit)
  .env.example          # Template for credentials

Schemas can be organized in nested folders:

schemas/
  blog/
    blogPost.ts
    author.ts
  ecommerce/
    product.ts
    brand.ts

All .ts and .js files are auto-discovered regardless of nesting depth.

Workflows

Migration workflow (recommended for teams)

Best for production environments and team collaboration. Migrations are committed to source control, reviewed in PRs, and applied in sequence.

# 1. Edit your schema
# 2. Generate a migration
ctkit generate

# 3. Review the generated migration file
# 4. Apply to Contentful
ctkit migrate

# 5. Verify everything is in sync
ctkit check

Push workflow (rapid prototyping)

Best for early development when you're iterating quickly and don't need migration history.

# 1. Edit your schema
# 2. Push directly
ctkit push

Pull-first workflow (existing projects)

Adopt ctkit on a Contentful space that already has content types.

# 1. Pull existing content types
ctkit pull

# 2. Review and adjust generated schema files
# 3. Continue with either workflow above

Migration Tracking

ctkit tracks which migrations have been applied using a dedicated content type (ctkit_migration_history) in your Contentful space. Each migration record includes:

  • Migration ID and filename
  • SHA-256 checksum (to detect tampering)
  • Execution time and status
  • ctkit version that ran it
ctkit status    # See pending and executed migrations
ctkit history   # Full execution history with timing

Examples

The examples/ directory contains complete schema sets for common use cases:

  • Basic -- Simple pages and sections
  • Blog -- Posts, authors, categories with rich text
  • E-commerce -- Products, categories, brands with number validations
  • Landing Page -- Hero sections, features, CTAs
  • Portfolio -- Projects with multiple rich text configs

Contributing

Contributions are welcome. See CONTRIBUTING.md for development setup, testing, and PR guidelines.

License

MIT