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 dataappend– 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: truegit.enabled: true--productionflag (export does not run in dev mode)
The export creates an orphan branch (no shared history with the main content branch). Each cycle:
- Scans
_data/for.jsonfiles - Compares with the previous commit’s tree
- Creates a new commit only if something changed
- Pushes to the remote (when
push: true)
Push failures are logged and retried on the next interval.
Moderation Workflow
- UGC arrives on the publisher via plugin writes to
_data/ - The background task commits to the
ugcbranch and pushes - Content team reviews UGC in git (pull request or direct)
- 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.
| Component | License |
|---|---|
| Data store endpoints | None (runtime: data.enabled) |
load_data() template function | None (runtime: data.enabled) |
| Git export | Standard or Pro (requires git-aware) |