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. Hover any file to see what it holds.
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
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 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.
// 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" }
]
}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 /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.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 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.
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 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