Content JSON

Blobify stores one mutable draft and, after the first publish, one published snapshot for each content item.

textcode
spaces/{spaceId}/content/{model}/{contentId}/draft.json
spaces/{spaceId}/content/{model}/{contentId}/published.json

The {contentId}/ directory is part of the path. History files live beside these documents under {contentId}/history/.

Draft document

The dashboard edits draft.json. Audit values use { "at", "by" }, where by identifies a user, API key, or system action. updated records the latest locale-specific edits. updatedShared records the latest edit to a non-translatable field.

jsoncode
{
  "id": "art_123",
  "model": "article",
  "created": {
    "at": "2026-03-01T10:00:00.000Z",
    "by": { "type": "user", "userId": "usr_1" }
  },
  "updated": {
    "en": {
      "at": "2026-03-02T08:55:00.000Z",
      "by": { "type": "user", "userId": "usr_1" }
    }
  },
  "updatedShared": {
    "at": "2026-03-02T08:55:00.000Z",
    "by": { "type": "user", "userId": "usr_1" }
  },
  "published": {
    "en": {
      "at": "2026-03-02T09:00:00.000Z",
      "by": { "type": "user", "userId": "usr_1" }
    }
  },
  "publishedLocales": ["en"],
  "firstPublished": {
    "at": "2026-03-02T09:00:00.000Z",
    "by": { "type": "user", "userId": "usr_1" }
  },
  "lastPublished": {
    "at": "2026-03-02T09:00:00.000Z",
    "by": { "type": "user", "userId": "usr_1" }
  },
  "fields": {
    "title": { "en": "Hello world" },
    "slug": "hello-world",
    "coverImage": { "type": "asset", "assetId": "img_456" },
    "author": { "model": "author", "id": "author_1" }
  }
}

The dashboard compares per-locale updated values and updatedShared with publish timestamps to identify drafts that need republishing.

Published document

published.json contains only the locales that are live. Public audit values are ISO timestamp strings, so actor identity is not exposed in the public document.

jsoncode
{
  "id": "art_123",
  "model": "article",
  "created": "2026-03-01T10:00:00.000Z",
  "publishedLocales": ["en"],
  "published": {
    "en": "2026-03-02T09:00:00.000Z"
  },
  "firstPublished": "2026-03-02T09:00:00.000Z",
  "lastPublished": "2026-03-02T09:00:00.000Z",
  "fields": {
    "title": { "en": "Hello world" },
    "slug": "hello-world",
    "coverImage": { "type": "asset", "assetId": "img_456" },
    "author": { "model": "author", "id": "author_1" }
  }
}

Publishing one locale does not leak unpublished edits from another locale. Blobify merges the requested locales with the previous published snapshot and preserves the other locales that were already live.

Localized fields

A field with translatable: true is a locale map:

jsoncode
{
  "title": {
    "en": "Welcome",
    "is": "Velkomin"
  }
}

A non-translatable field is stored once:

jsoncode
{
  "slug": "welcome"
}

This rule applies to every field type. A translatable rich-text field is a locale map of root AST values. A translatable blocks field is a locale map of block objects or arrays.

Content and asset references

Content references store identity, not an expanded document:

jsoncode
{
  "author": {
    "model": "author",
    "id": "author_1"
  }
}

Add spaceId when the target is in another allowed space. Generated clients can resolve references from summaries or full content according to client options.

Asset fields use a tagged reference:

jsoncode
{
  "coverImage": {
    "type": "asset",
    "assetId": "img_456"
  }
}

Localized alt text, intrinsic dimensions, and the media URL belong to the asset metadata document at spaces/{spaceId}/assets/{assetId}.json. They are not duplicated on every asset-field reference.

Focal points and crops

An image reference can carry optional per-use framing data. Values are fractions from 0 to 1 in the original image coordinate space.

jsoncode
{
  "coverImage": {
    "type": "asset",
    "assetId": "img_456",
    "focalPoint": { "x": 0.28, "y": 0.52 },
    "crop": { "x": 0.1, "y": 0, "width": 0.5, "height": 0.5 }
  }
}

focalPoint identifies the point to keep visible during a fixed-size cover crop. crop identifies the rectangular region to retain. The generated client applies these values when producing Cloudflare Images parameters. The original media file is unchanged.

Links

A link field stores one tagged value:

jsoncode
{
  "internalLink": {
    "type": "internal",
    "model": "page",
    "id": "page_1"
  },
  "assetLink": {
    "type": "asset",
    "assetId": "file_1"
  },
  "externalLink": {
    "type": "external",
    "url": "https://example.com",
    "newTab": true
  }
}

Internal content and asset links can also carry spaceId for a cross-space target.

Blocks

A multiple blocks field stores an ordered array. Block fields sit beside the required type and id properties.

jsoncode
{
  "sections": [
    {
      "type": "hero-block",
      "id": "blk_1",
      "heading": { "en": "Welcome" },
      "image": { "type": "asset", "assetId": "img_456" }
    }
  ]
}

With multiple: false, the field stores one block object. With translatable: true, the outer value is a locale map whose values follow the configured single or multiple shape.

Rich text

Rich text is a root AST. Inline text values are plain strings because localization happens at the field level.

jsoncode
{
  "body": {
    "en": {
      "type": "root",
      "children": [
        {
          "type": "paragraph",
          "children": [
            { "type": "text", "value": "Hello world", "marks": ["bold"] }
          ]
        },
        {
          "type": "paragraph",
          "children": [
            {
              "type": "contentRef",
              "ref": { "model": "article", "id": "art_999" },
              "children": [{ "type": "text", "value": "Read next" }]
            }
          ]
        },
        {
          "type": "block",
          "block": {
            "type": "callout-block",
            "id": "blk_2",
            "heading": { "en": "Important" }
          }
        }
      ]
    }
  }
}

The full validated AST also supports headings, lists, blockquotes, code, horizontal rules, and asset nodes. The model schema can restrict nodes, marks, heading levels, content-reference models, block types, and assets.

Physical keys with a content prefix

If the space sets contentPrefix, the logical content path is rewritten physically:

textcode
spaces/main/_blobify/site_a1/content/article/art_123/published.json

The generated client and dashboard handle this mapping. Use their path helpers instead of inserting _blobify by hand.

Archived documents

Deleting a live entry first moves draft.json and published.json from content/ to archive/. The archived draft receives an archived audit event. History remains under the content item history path until the already archived entry is permanently deleted.