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.
Traditional headless CMSes removed the frontend but kept the content on their own infrastructure. Blobify publishes into storage you control.
We charge for the editing experience and user management - not for storing or delivering your content.
Your content lives in S3 or Cloudflare R2 buckets that you own and control. We never store your content on our servers.
Generous free tiers:
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.
Use Blobify to manage images, audio, video, PDFs, and uploaded files in the same bucket-first model. Think Dropbox-style asset workflows, but the files live in your own bucket.
Asset Library
main / press-kit
Press hero image
image/jpeg2.4 MBMetadata, display names, folders, and direct asset links
Podcast intro take
audio/wav87 MBPublic player links, shareable asset URLs, and drop zone uploads
A complete content platform — from modeling to delivery.
Define models for your content types and reusable blocks like Hero, CTA, or FAQ
AST-based editor with embedded blocks, links, and references
Multi-language content with per-locale publishing
Link content together with validated cross-model references
Track changes and restore previous versions
Preview changes before going live
Images and files with metadata and transforms
Dropzones, public player links, and shareable asset URLs
Generated types and typed client from your schema
Cloudflare Images for resize, crop, and format conversion
Notify external services when content changes
Roles and per-space access control
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 shard fetch gives you everything needed for list pages, search indexes, and sitemaps.
{
"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" }
}
}Blobify supports both read paths. For indexed fields like slug, it can jump straight through field index shards to find the entry ID. For list pages and non-indexed queries, it uses summary manifests and shards.
// 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 page path
GET /summaries/published/index.json
GET /summaries/published/article/s000.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" }
}
}
}Content lives on S3 - fetch it however you want. Or use our typed client with full autocomplete for your content types and fields.
// 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));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');Automatic image optimization via Cloudflare Images. Resize, convert to WebP/AVIF, and serve from the edge - all with a simple helper function.
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, … }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.
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 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"Pure usage pricing. Start free, then scale by team, content volume, and write activity.
Adjust each slider to match your needs.
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.
For teams that need a more tailored setup, pricing shape, or rollout.