How it works
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
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.
Generous free tiers:
Most content sites fit entirely within these free tiers.
Bucket layout
Everything Blobify manages is just files in your bucket: schemas, content, summaries, the asset catalog, and routing. Published artifacts sit at the bucket root; authoring artifacts sit in a single private scope. Hover any file to see what it holds.
S3 or Cloudflare R2
Optional namespace for keeping multiple organizations in one bucket.
A space is an independent content area with its own content, assets, and published files.
Optional path-hardening segment for content JSON. Asset media stays outside it.
Drafts, history, and full schemas that only the dashboard and authenticated tools use.
The pipeline
From raw JSON files in your bucket to fully resolved content in one SDK call.
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).
{
"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
]
}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.
{
"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" }
}
}For indexed fields like slug, Blobify jumps straight through field index pointer objects 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.
// Indexed lookup path (for fields like slug)
GET /spaces/main/lookups/published/article/slug/en/afa27b44d43b02a9fea41d13cedc2e4016cfcf87c5dbf990e593669aa8ce286d.json
{
"v": 2,
"key": "hello-world",
"id": "c4f2e8a1",
"status": "live"
}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.
// Sorted, paginated feed via a list index
GET /spaces/main/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 /spaces/main/list-indexes/published/article/latest/en/g7/p00001.jsonURL patterns defined per model with per-locale overrides. English gets /blog/:slug, German gets /de/blog/:slug.
{
"article": {
"path": "/:locale/blog/:slug",
"locales": {
"en": "/blog/:slug"
}
},
"page": {
"path": "/:locale/:slug",
"locales": {
"en": "/:slug"
}
}
}One SDK call triggers parallel fetches. For indexed lookups like slug, Blobify can jump straight through field-index pointer objects. 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.
const article = await cms.findOne('article', { slug: 'hello-world' }, 'en');
// Behind the scenes:
// 1. Fetch summary manifest + shards (cached after first call)
GET /spaces/main/summaries/published/index.json // shard discovery
GET /spaces/main/summaries/published/article/s000.1783796607793.json
// versioned shard file named by the manifest
// 2. Find by slug in merged summary → id: "c4f2e8a1"
// 3. Fetch content + assets in parallel
GET /spaces/main/content/article/c4f2e8a1/published.json
GET /spaces/main/assets/catalog.json // asset URLs (logical key)
// when contentPrefix is enabled:
// GET /spaces/main/cp_{prefix}/assets/catalog.json
// 4. Resolve references (fetches dependent summaries)
GET /spaces/main/summaries/published/author/s000.1783796607793.json
// 5. Resolve asset refs → URL strings
// 6. Return resolved contentThe 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.
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