Models and blocks

Models define independent content items. Blocks define reusable structures embedded inside a model's blocks or richtext field.

Choose a model for an independent item

Create a model when an item needs its own identity or lifecycle. Articles, pages, authors, products, categories, and events are typical models.

A model is the right boundary when the item needs one or more of these features:

  • its own draft and published document
  • its own route
  • direct references from other content
  • exact lookup by a field such as slug
  • inclusion in summary or list-index output

Choose a block for an embedded structure

Create a block when editors need a named, validated structure inside another entry. Heroes, callouts, quotes, FAQ items, galleries, and pricing rows are typical blocks.

A block instance has a block type, an instance ID, and the fields declared by its block schema. It does not have a separate publish lifecycle or content route.

jsoncode
{
  "type": "hero-block",
  "id": "blk_123",
  "heading": { "en": "Welcome" },
  "image": { "type": "asset", "assetId": "img_456" }
}

If an embedded item needs to be found, reused, or published independently, model it as content and reference it instead.

Blocks fields and rich text

Use a blocks field for an ordered page structure with clear section boundaries. Use richtext for editorial flow such as articles and documentation.

Both can restrict embedded block types with allowedBlocks. Rich text can also restrict internal content links with allowedModels and enable asset nodes with allowAssets.

By default, a blocks field is shared across locales while translatable fields inside each block store locale maps. Set the outer field to translatable: true only when each locale needs a different block tree. A translatable blocks field is stored as a locale map of block values.

jsoncode
{
  "sections": {
    "type": "blocks",
    "multiple": true,
    "allowedBlocks": ["hero-block", "faq-block"]
  },
  "regionalSections": {
    "type": "blocks",
    "multiple": true,
    "translatable": true,
    "allowedBlocks": ["hero-block", "faq-block"]
  }
}

Model example

This model has lightweight summary fields, an exact slug index, and a route. fieldIndexes is the current schema name for exact lookup indexes.

jsoncode
{
  "model": "article",
  "name": "Article",
  "displayField": "title",
  "summaryFields": ["title", "slug", "excerpt", "coverImage"],
  "fields": {
    "title": { "type": "text", "translatable": true, "required": true },
    "slug": {
      "type": "slug",
      "sourceField": "title",
      "required": true,
      "unique": true
    },
    "excerpt": { "type": "text", "translatable": true },
    "coverImage": { "type": "asset", "accept": "image" },
    "author": { "type": "reference", "model": "author" },
    "body": {
      "type": "richtext",
      "translatable": true,
      "allowAssets": true,
      "allowedBlocks": ["callout-block", "quote-block"],
      "allowedModels": ["article"]
    }
  },
  "routing": { "path": "/:locale/blog/:slug" },
  "fieldIndexes": [
    { "field": "slug", "normalizer": "slug-v1", "unique": true }
  ]
}

A field marked unique: true gets a required field index derived when the model is saved. Existing models created before that behavior may need to be saved again and have field indexes rebuilt before uniqueness is enforced against existing data.

Block example

jsoncode
{
  "id": "hero-block",
  "name": "Hero",
  "fields": {
    "heading": { "type": "text", "translatable": true, "required": true },
    "subheading": { "type": "text", "translatable": true },
    "image": { "type": "asset", "accept": "image" },
    "ctaLabel": { "type": "text", "translatable": true },
    "ctaLink": {
      "type": "link",
      "models": ["page", "article"],
      "allowExternal": true
    }
  }
}

Block field definitions cannot use the reserved names type or id. Blobify supplies those properties on every block instance.

Organize a large model

fieldGroups is model-level editor metadata. A group with presentation: "section" stays in the main form. A group with presentation: "tab" gets a separate editor tab. Assign a model field with groupId.

jsoncode
{
  "fieldGroups": [
    { "id": "content", "title": "Content", "presentation": "section" },
    { "id": "seo", "title": "SEO", "presentation": "tab" }
  ],
  "fields": {
    "title": { "type": "text", "groupId": "content" },
    "metaTitle": { "type": "text", "groupId": "seo" }
  }
}

Groups affect only the dashboard layout. They do not change stored content JSON, and block schemas do not support model field groups.

Start from a preset

Blobify currently ships four schema presets: Blog, Simple Website, Journal, and Status Page. Presets create ordinary model and block schemas that you can edit after import.

For stored value shapes, continue to Field types and Content JSON.