Data Store

Overview

The data store gives plugins a write path for user-generated content (UGC) that is separate from the CMS content directory. Data is stored in a _data/ directory as JSON files, organized by namespace and key.

Publisher mutation guards block writes to content/ but allow writes to _data/, so UGC works in all server roles.

Configuration

data:
  enabled: true
  directory: "./_data"     # default
  rate_limit: 60           # max writes per minute, default: 60

Endpoints

Two localhost-only HTTP endpoints are available for plugins:

Write

POST /_internal/data/write
Content-Type: application/json

{
  "namespace": "comments",
  "key": "post-123",
  "value": {"author": "alice", "text": "Great post!"},
  "mode": "append"
}

Modes:

  • replace (default) – store the value as-is, replacing any existing data
  • append – maintain a JSON array and push the new value onto it

Read

GET /_internal/data/read?namespace=comments&key=post-123

Returns {"value": [...]} or {"value": null} if the key does not exist.

Namespace and key rules: lowercase alphanumeric, hyphens, and underscores only (a-z, 0-9, -, _). Max 128 characters each.

Template Function

Use load_data(namespace, key) in Jinja templates to read stored data:

{% set comments = load_data("comments", page.slug) %}
{% if comments %}
  <h3>Comments ({{ comments | length }})</h3>
  {% for comment in comments %}
    <div class="comment">
      <strong>{{ comment.author }}</strong>
      <p>{{ comment.text }}</p>
    </div>
  {% endfor %}
{% endif %}

When the key does not exist, load_data() returns an empty array [].

Git Export

When git deployment is active, a background task can periodically commit _data/ snapshots to a dedicated git branch for backup and moderation.

data:
  enabled: true
  git_export:
    enabled: true
    branch: ugc          # default: "ugc"
    interval: 300        # seconds between exports, default: 300
    push: true           # push to remote after commit, default: true

Requirements:

  • data.enabled: true
  • git.enabled: true
  • --production flag (export does not run in dev mode)

The export creates an orphan branch (no shared history with the main content branch). Each cycle:

  1. Scans _data/ for .json files
  2. Compares with the previous commit’s tree
  3. Creates a new commit only if something changed
  4. Pushes to the remote (when push: true)

Push failures are logged and retried on the next interval.

Moderation Workflow

  1. UGC arrives on the publisher via plugin writes to _data/
  2. The background task commits to the ugc branch and pushes
  3. Content team reviews UGC in git (pull request or direct)
  4. Approved UGC is optionally merged into the content branch

Storage Layout

_data/
  comments/
    post-123.json
    post-456.json
  forms/
    contact-789.json

Each (namespace, key) pair maps to _data/{namespace}/{key}.json.

Edition Requirements

The data store itself is compiled into the Standard and Pro binaries. There is no build-feature gate – the store and the load_data() template function work in accent build and dev serve with no license, controlled at runtime by data.enabled. Only the git export of _data/ requires a Standard or Pro license.

ComponentLicense
Data store endpointsNone (runtime: data.enabled)
load_data() template functionNone (runtime: data.enabled)
Git exportStandard or Pro (requires git-aware)