Content Sync
Blockstudio is built around a file-first workflow: blocks, fields, templates, assets, patterns, and file-based pages can all live in your project. Content Sync extends that model to selected WordPress database content for teams that also want curated posts, terms, relationships, and declared metadata in git.
It is a WP-CLI workflow, not live database replication. On pull, Content Sync projects allowlisted posts, postmeta, terms, termmeta, and post-term relationships to portable files in your theme. On push, it applies those files to another environment, using stable UIDs and only rewriting references you explicitly declare.
Content Sync is on-demand. It does not run on every admin request.
Commands return a skipped row until content.enabled is true.
When to use Content Sync
Use Content Sync for content sets that are important to version, review, and move between environments:
- structured content such as team members, locations, case studies, products, documentation entries, or campaign pages
- taxonomies and relationships that belong with those content sets
- allowlisted metadata that is safe to commit and needs stable references
- small to medium content sets where reviewing files in git is still practical
As a rule of thumb, Content Sync is comfortable for dozens or hundreds of rows, and can work into the low thousands when the data is structured, low-churn, and split into focused post types. It is not meant to mirror an entire production database, high-traffic editorial content, logs, orders, analytics, form submissions, sessions, or other high-volume/high-churn data. If the generated files become noisy to review, pushes take too long to reason about, or most rows change outside your deployment workflow, use a database backup, migration, or import/export tool instead.
Media binaries are not copied in 7.4. Attachment references can be recorded in a manifest and validated, but the files themselves need to exist in the target environment.
Configuration
Enable Content Sync in blockstudio.json:
{
"$schema": "https://blockstudio.dev/schema/blockstudio",
"content": {
"enabled": true,
"id": "default",
"path": "content",
"includePageSyncManaged": false,
"authors": "ignore",
"postTypes": ["team_member"],
"meta": {
"include": ["_my_*"],
"exclude": ["_edit_lock", "_edit_last", "_wp_old_slug"],
"references": {
"_thumbnail_id": { "kind": "attachment" },
"_related_posts": { "kind": "post", "path": "*" },
"_hero": { "kind": "attachment", "path": "image.id" }
}
},
"taxonomies": [],
"media": "manifest"
}
}postTypes is an allowlist. Empty means Content Sync does not touch posts.
taxonomies is an allowlist of already registered taxonomies whose terms and
post relationships should sync. Blockstudio does not register taxonomy
definitions from Content Sync files. Postmeta and termmeta are allowlisted
through meta.include; meta.exclude always wins.
Treat meta.include as a commit boundary. Synced files are meant to live in git,
so do not include keys that hold secrets, tokens, credentials, or PII. wp bs content status reports warning rows when an allowlisted meta key looks
sensitive.
content.id is stored on synced entities as _blockstudio_content_set. Prune
and ownership checks are scoped to that value, so one content set cannot delete
another set's content.
Page Sync managed posts are excluded by default. If
includePageSyncManaged is enabled, Content Sync can sync their configured meta
but Page Sync still owns their post_content.
Authors are ignored by default because users are not synced. Set
authors: "login" to store the author's login in post files and resolve that
login to an existing user on push. Missing users are a preflight error; Content
Sync does not create users.
Commands
wp bs content pull [--post-type=<type>] [--taxonomy=<taxonomy>] [--dry-run]
wp bs content push [--dry-run] [--prune] [--yes]
wp bs content statuspull captures database content into files. It assigns
_blockstudio_content_uid to in-scope posts that do not have one yet and reports
stale files whose database source no longer exists. Stale files are not deleted
by pull.
push applies files to the current database. It validates the plan before
writing and blocks unresolved declared references, slug conflicts, locked
entities, and missing post types.
status compares files with the database and reports missing, changed,
unchanged, orphaned, or conflicted entities. It also warns about allowlisted meta
keys that look like secrets before those values are committed, and about numeric
IDs inside block markup bodies because Content Sync does not rewrite IDs in
.html files.
Use --dry-run to inspect a pull or push without writing. Use push --prune --yes to remove content-set owned database entities that no longer exist in the
files.
Files
Content Sync writes files under the configured content.path:
content/
posts/
team_member/
jane.9b1c0e6e.json
jane.9b1c0e6e.html
terms/
category/
leadership.5e900000.json
media/
manifest.jsonPost .json files store identity, post fields, term relationships, declared
meta, and meta encoding. The sibling .html file stores post_content as raw
block markup and is omitted when the content is empty. Term .json files store
identity, taxonomy, slug, name, description, parent UID, declared termmeta, and
meta encoding.
{
"uid": "9b1c0e6e-0000-4000-9000-000000000000",
"type": "team_member",
"status": "publish",
"slug": "jane",
"title": "Jane",
"parent": null,
"menuOrder": 0,
"terms": {
"category": ["11110000-0000-4000-9000-000000000000"]
},
"meta": {
"_my_subtitle": "Creative Director",
"_thumbnail_id": "5e900000-0000-4000-9000-000000000000"
},
"metaEncoding": {
"_my_subtitle": "scalar",
"_thumbnail_id": "scalar"
}
}The stored sync state is:
| Meta key | Purpose |
|---|---|
_blockstudio_content_uid | Portable content identity |
_blockstudio_content_set | Content-set namespace from content.id |
_blockstudio_content_source | Theme-relative source file path |
_blockstudio_content_fingerprint | Last synced file/database fingerprint |
_blockstudio_content_locked | Truthy value prevents push from overwriting the entity |
References
Content Sync never guesses that an integer is an ID. References are rewritten
only when configured in meta.references.
{
"meta": {
"references": {
"_related_posts": { "kind": "post", "path": "*" },
"_topic": { "kind": "term" },
"_hero": { "kind": "attachment", "path": "image.id" }
}
}
}On pull, local IDs at those paths become portable UIDs. On push, the UIDs are resolved back to local IDs in the current database.
Post parents, term parents, and post-term relationships are structural references owned by Content Sync and are stored as UIDs automatically. Configured taxonomies are written to post files even when empty; an empty array clears that taxonomy's relationships on push.
Attachment references are validated, not imported. With media: "manifest",
referenced attachments are listed in content/media/manifest.json; push requires
the referenced attachment UID to exist locally. With media: "none", declared
attachment references are dropped.
Portability Workflow
- Register the target post type in every environment.
- Configure
content.postTypes,meta.include, and any declared references. - Run
wp bs content pullin the source environment. - Commit the generated files.
- Run
wp bs content pushin the target environment. - Run
wp bs content statusto verify the database matches the files.
Files are the source of truth for push. The database is captured back to files
with pull.
Scope
The 7.4.0 Content Sync surface is intentionally conservative:
- posts from allowlisted post types
- allowlisted postmeta
- terms from allowlisted registered taxonomies
- allowlisted termmeta
- post-term relationships
- declared
post,attachment, andtermreferences - portable post and term UIDs and content-set ownership
- Page Sync exclusion by default
- attachment manifest and preflight validation
pull,push,status,--dry-run,--prune, and locked-entity handling
Taxonomy definition capture, media binary copying, and block-markup ID rewriting are later phases.