How it works

Your content, your bucket

Blobify is a contentless CMS. You get the editing experience; your content lives as plain JSON in a storage bucket you own. Here is the whole picture — from a bucket, to the file layout, to one resolved SDK call.

Your storage

Bring your own bucket

A bucket is just a folder in the cloud, hosted by Amazon (S3) or Cloudflare (R2). You sign up, get a bucket, and Blobify writes your content into it as plain JSON. Your app reads it directly. We never store your content on our servers.

BBlobifyDashboardMCPfor humans and AI</>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 sovereigntyContent stays in your AWS account or Cloudflare zone
  • No vendor lock-inPlain JSON files, readable by any system
  • Generous free tiersBoth AWS and Cloudflare offer free storage to get started
  • Compliance readyChoose 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.

Bucket layout

How your content is laid out

Everything Blobify manages is just files in your bucket: schemas, content, summaries, the asset catalog, and routing. Hover any file to see what it holds.

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

Optional root prefix — many orgs, one bucket

Give each organization its own root prefix and the whole tree above nests under it, so a single bucket can host many orgs side by side. Every key lives under the prefix — content, summaries, indexes, routing, and even asset media. Without one, the org owns the bucket root.

Optional content prefix — path hardening

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.

The pipeline

How your data is stored, indexed, and resolved

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

Summaries → Lists & Search

Each model gets summary shards with only the fields you pick. One manifest plus a shard fetch gives you the whole set for search, sitemaps, and getAll. Large, sorted, paginated feeds are served by list indexes (below).

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. One JSON file per content entry. If you index a field like slug, Blobify can jump straight to the entry by that field and then fetch the full JSON only for the detail page.

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" }
  }
}

Indexes → Fast Lookups

For indexed fields like slug, Blobify jumps straight through field index shards to find the entry ID, then fetches just that entry’s JSON for the detail page. Non-indexed lookups fall back to a summary scan.

indexes/article/slug/en/s3.json
// Indexed lookup path (for fields like slug)
GET /indexes/article/slug/en/s3.json

{
  "items": [
    { "key": "hello-world", "id": "c4f2e8a1" },
    { "key": "getting-started", "id": "d7a1c3b9" }
  ]
}

List indexes → Numbered pagination

For sorted feeds and numbered pagination, each index and locale is pre-paginated into ordered pages plus a manifest of page counts. listByIndex jumps to any page with direct offset math, so page 24 of a million entries is one or two static file fetches. No scan, no query engine.

list-indexes/article/latest/en/index.json1M items
// Sorted, paginated feed via a list index
GET /list-indexes/published/article/latest/en/index.json

{
  "total": 1000000,
  "pages": [
    { "key": "g7/p00000.json", "count": 1000 },
    { "key": "g7/p00001.json", "count": 1000 }
    // ~1000 ordered pages
  ]
}

// listByIndex('article', 'latest', 'en', { page: 24, pageSize: 50 })
// offset 1150 -> walk page counts -> fetch only the page that covers it
GET /list-indexes/published/article/latest/en/g7/p00001.json

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. For indexed lookups like slug, Blobify can jump straight through field index shards. For list pages and non-indexed lookups, it uses summary manifests and shards. Then it loads content and assets in parallel, with dependent summaries for references fetched automatically and cached.

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.

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

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"

// resolved article
{
  "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" }
    }
  }
}

FAQ

Questions about buckets