{ "model": "article", "fields": [...] }
{ "type": "asset", "id": "img_a1b2" }
{ "title": { "en": "Hello", "de": "Hallo" } }
{ "slug": "hello-world", "state": "published" }
{ "model": "author", "id": "auth_x7y8" }
{ "path": "/:locale/blog/:slug" }
{ "type": "hero-block", "fields": {...} }
{ "items": [{ "id": "c4f2" }, ...] }
{ "locale": "en", "state": "published" }
{ "assetId": "img_a1b2", "alt": "..." }
Currently in private beta

Blobify Your Content

Headless & Contentless

The headless CMS that doesn't want your content. We provide the editing experience. Your S3 bucket stores the data. No vendor lock-in. No content hostage. Just JSON blobs you own.

What does “Contentless” mean?

Traditional headless CMSes removed the frontend but kept the content on their own infrastructure. Blobify publishes into storage you control.

Traditional Headless CMS

  • Content stored on their servers
  • Charged for storage ($/GB)
  • Charged for bandwidth ($/API call)
  • API rate limits & quotas
  • Vendor lock-in, proprietary formats

Blobify (Contentless)

  • Content in YOUR S3/R2 bucket
  • S3 storage is dirt cheap (free tier is generous)
  • No bandwidth fees through us
  • No API needed - fetch from S3
  • Plain JSON, walk away anytime

We charge for the editing experience and user management - not for storing or delivering your content.

Bring Your Own Bucket

Your content lives in S3 or Cloudflare R2 buckets that you own and control. We never store your content on our servers.

BBlobifyDashboardCMS interface</>Your AppNext / Astro / etc.SSR · SSG · CSRYour BucketS3 / Cloudflare R2{ .. }.json{ .. }.json{ .. }.jsonwritereadfetchno API neededYou own it. You control it.Plain JSON files in your cloud storage
  • Data sovereignty - Content stays in your AWS account or Cloudflare zone
  • No vendor lock-in - Plain JSON files, readable by any system
  • Generous free tiers - Both AWS and Cloudflare offer free storage to get started
  • Compliance ready - Choose your region, your encryption, your rules

Generous free tiers:

  • Cloudflare R2: 10GB storage + 10M reads/mo, plus zero egress fees
  • AWS S3: 5GB storage + 100GB transfer/mo (first 12 months only)

Most content sites fit entirely within these free tiers.

Optional content prefix (advanced)

You can add a per-space content prefix to add an extra folder segment for content JSON, summaries, indexes, and the asset catalog JSON, making direct bucket URLs harder to guess.

Asset files still stay in assets/media/.

Path hardening only. If your app fetches content JSON in the browser, the prefix can still be visible in network requests. SSR/ISR keeps it less obvious.

Your BucketS3 / Cloudflare R2
s3://your-bucket/

Everything you need

A complete content platform — from modeling to delivery.

Schemas

Define models for your content types and reusable blocks like Hero, CTA, or FAQ

Rich Text

AST-based editor with embedded blocks, links, and references

Localization

Multi-language content with per-locale publishing

References

Link content together with validated cross-model references

Version History

Track changes and restore previous versions

Draft/Published

Preview changes before going live

Asset Management

Images and files with metadata and transforms

TypeScript SDK

Generated types and typed client from your schema

Image Transforms

Cloudflare Images for resize, crop, and format conversion

Webhooks

Notify external services when content changes

User Management

Roles and per-space access control

How It All Works Together

From raw JSON files in your bucket to fully resolved content in one SDK call.

Summaries → List Pages

Each model gets summary shards with only the fields you pick. One manifest plus shard fetch gives you everything needed for list pages, search indexes, and sitemaps.

article-s000.json512 items
{
  "items": [
    { "id": "c4f2e8a1", "slug": "hello-world", "title": "Hello World" },
    { "id": "d7a1c3b9", "slug": "getting-started", "title": "Getting Started" },
    { "id": "e9b4d2f7", "slug": "advanced-tips", "title": "Advanced Tips" },
    // ... 512 more items
  ]
}

Content → Detail Pages

Full content documents with localized fields, rich text, and references. All in one JSON file per item. Your app fetches these only for detail pages.

content/article/c4f2e8a1/published.json
{
  "id": "c4f2e8a1",
  "model": "article",
  "fields": {
    "title": { "en": "Hello World", "de": "Hallo Welt" },
    "slug": { "en": "hello-world", "de": "hallo-welt" },
    "content": {
      "en": {
        "type": "root",
        "children": [
          { "type": "heading", "depth": 1,
            "children": [{ "type": "text", "value": "Hello, world!" }] },
          { "type": "paragraph",
            "children": [
              { "type": "text", "value": "This is an " },
              { "type": "text", "value": "example", "marks": ["bold"] },
              { "type": "text", "value": " richtext." }
            ] }
        ]
      }
    },
    "heroImage": { "type": "asset", "assetId": "img_a1b2" },
    "author": { "model": "author", "id": "auth_x7y8" }
  }
}

Routing → Final URLs

URL patterns defined per model with per-locale overrides. English gets /blog/:slug, German gets /de/blog/:slug.

routing/published.json
{
  "article": {
    "path": "/:locale/blog/:slug",
    "locales": {
      "en": "/blog/:slug"
    }
  },
  "page": {
    "path": "/:locale/:slug",
    "locales": {
      "en": "/:slug"
    }
  }
}

Behind the Scenes

One SDK call triggers parallel fetches. Summary manifests, shards, content, and assets are loaded concurrently and cached. Dependent summaries (for references) are fetched automatically. Subsequent calls hit the cache.

findOne: network requests
const article = await cms.findOne('article', { slug: 'hello-world' }, 'en');
// Behind the scenes:

// 1. Fetch summary manifest + shards (cached after first call)
GET /summaries/published/index.json             // shard discovery
GET /summaries/published/article/s000.json      // article summary shard

// 2. Find by slug in merged summary → id: "c4f2e8a1"

// 3. Fetch content + assets in parallel
GET /content/article/c4f2e8a1/published.json    // full document
GET /assets/catalog.json                        // asset URLs (logical key)
// when contentPrefix is enabled:
// GET /_blobify/{prefix}/assets/catalog.json

// 4. Resolve references (fetches dependent summaries)
GET /summaries/published/author/s000.json

// 5. Resolve asset refs → URL strings
// 6. Return resolved content

Typed Client → Resolved

The SDK resolves references automatically. Asset references become URL strings from your bucket, content references become resolved summary entries. Use localize() and resolveUrl() helpers for the rest.

console.log(article)
const article = await cms.findOne("article", { slug: "hello-world" }, "en");

console.log(article);

/* Console output
{
  id: "c4f2e8a1",
  model: "article",
  fields: {
    title: { en: "Hello World", de: "Hallo Welt" },
    slug: { en: "hello-world" },
    content: {
      en: {
        type: "root",
        children: [
          { type: "heading", depth: 1, children: [{ type: "text", value: "Hello, world!" }] },
          {
            type: "paragraph",
            children: [
              { type: "text", value: "This is an " },
              { type: "text", value: "example", marks: ["bold"] },
              { type: "text", value: " richtext." }
            ]
          }
        ]
      }
    },
    heroImage: "https://cdn.example.com/media/img_a1b2.jpg",
    author: {
      id: "auth_x7y8",
      fields: { name: "Jane Smith" }
    }
  }
}
*/

resolveUrl("article", article, "en")  // "/blog/hello-world"
resolveUrl("article", article, "de")  // "/de/blog/hello-world"

localize(article.fields.title, "en")  // "Hello World"
localize(article.fields.title, "de")  // "Hallo Welt"

Developer Experience First

Content lives on S3 - fetch it however you want. Or use our typed client with full autocomplete for your content types and fields.

Fetch from S3 directly

// No API needed - fetch directly from your bucket
const manifest = await fetch('.../summaries/published/index.json').then(res => res.json());
const shards = await Promise.all(
  manifest.models.article.shards.map(shard =>
    fetch('.../summaries/published/article/' + shard + '.json').then(res => res.json())
  )
);

const articles = shards.flatMap(shard => shard.items);
articles.forEach(article => console.log(article.fields.title.en, article.fields.slug.en));

Or use the typed client

import { createClient } from './blobify';

const cms = createClient({
  baseUrl: 'https://my-bucket.s3.amazonaws.com',
  spaceId: 'main',
});

// Full TypeScript autocomplete
const articles = await cms.getAll('article', 'en');
const page = await cms.findOne('page', { slug: 'about' }, 'en');

Cloudflare Images Integration

Automatic image optimization via Cloudflare Images. Resize, convert to WebP/AVIF, and serve from the edge - all with a simple helper function.

  • On-the-fly resizing
  • WebP/AVIF conversion
  • Edge caching
import { cfImage } from './blobify';

// Transform images via Cloudflare Images
const optimized = cfImage(article.fields.heroImage, {
  width: 800,
  height: 600,
  quality: 85,
  format: 'webp'
});

// Output: https://your-site.com/cdn-cgi/image/w=800,h=600,q=85,f=webp/...
// Typed client fetches summary manifests and shards once, builds hash maps
const cms = createClient({
  baseUrl,
  spaceId: 'main',
});

// Find by any summary field (slug, category, etc.)
const article = await cms.findOne(
  'article', { slug: 'my-post' }, 'en'
);
const frontpage = await cms.findOne(
  'page', { isFrontpage: true }, 'en'
);
const techPosts = await cms.findMany(
  'article', { category: 'tech' }, 'en'
);

// References resolved automatically (O(1) lookup)
article.fields.author    // → Author object
article.fields.heroImage // → { url, alt, width, … }

Automatic Reference Resolution

References and assets are resolved automatically from summaries. The typed client fetches summary manifests and shards at build time, then resolves IDs instantly with O(1) lookups.

  • Summaries fetched once at build time
  • Hash maps for O(1) lookups
  • References, assets, links all resolved
  • Scales to millions of items

Model-Based Routing

Define URL patterns directly in your model schema. Support for per-locale overrides means German URLs can use German paths while English uses English.

  • Define patterns in schema
  • Per-locale URL overrides
  • Resolve with typed helper
  • SEO-friendly localized paths
// Define routes in your model schema
{
  "model": "article",
  "routing": {
    "path": "/:locale/blog/:slug",
    "locales": {
      // en without the locale prefix
      "en": "/blog/:slug"
    }
  }
}

// Resolve URLs with the typed client
import { resolveUrl } from './blobify';

resolveUrl('article', article, 'en');
// → "/blog/hello-world"
resolveUrl('article', article, 'de');
// → "/de/blog/hello-world"

Simple, transparent pricing

Pure usage pricing. Start free, then scale by team, content volume, and write activity.

Build your plan

Adjust each slider to match your needs.

Editors?
1free
12381530
Entries?
2,000free
2,00025,000100,000500,0001m5m10m
Spaces?
1free
1241020
Locales?
1free
12481020
Free
No credit card required
i

Did you know?

We don't charge for requests, storage, or bandwidth. Content lives in your own S3/R2 bucket — you pay your cloud provider directly at their rates.

Enterprise

For teams with strict compliance and very high write volume.

  • Unlimited editors, records, locales, and write ops
  • SAML SSO, SCIM, and audit log export
  • Priority support and architecture reviews
  • Custom integrations
  • SLA, incident channel, and dedicated onboarding
Contact us
Private beta: billing is not active yet. Calculator reflects launch-target pricing.