Skip to content

cwp content

shipped 1.0.0
cwp content [flags]

cwp content groups the commands below and takes no action of its own; run it alone and it prints its help.

SubcommandWhat it does
cwp content listList the posts in scope on a site, and whether cwp manages them
cwp content statusCompare the site against the committed tree, in both directions, without writing
cwp content pullRead named posts down into the committed tree
cwp content pushWrite named posts from the committed tree to an environment

What it does

Moves individual pages and posts between an environment and a committed tree of JSON. This is the command for the work that is most of the work: building a page and getting it live.

pull and push name the direction relative to the tree, not to your laptop. cwp content pull dev brings dev’s pages into content/; cwp content push dev sends them there. Omitting the environment means the local site.

cwp content pull --post about-us       # from the local site into content/
git add content/ && git commit -m "…"
cwp content status dev                 # what differs, both directions, no writes
cwp content push dev --post about-us

This is not a hole in the safety model

The database flows downward. What that rule protects against is a write of unbounded extent — one where nobody can say beforehand what will be different afterwards.

A content push is a named list of posts, printed before anything happens. cwp has shipped a bounded upward database write since 0.4.0: cwp bricks push replaces global classes and templates on a protected environment under the full guard set, and nobody calls that a breach. The only structural difference to a page is which table the row sits in. So:

Bulk database state flows downward. Individually named items may flow upward, under the full guard set, with the affected items listed before the write.

The artifact

content/<type>/<slug>.json, one file per post, committed. That is half the point of the feature: a Bricks page is database state, so without this it has no diff, no review and no history — you build one and git status stays empty.

The file is environment-neutral. The site URL is stored as {{site}} and every attachment id as its upload path, so the same page pulled from two environments produces the same bytes. Without that, every push would look like a conflict.

It is canonical: sorted keys throughout, two-space indent, trailing newline — and a pull that changes nothing writes nothing, so re-running one does not dirty the tree or move an mtime.

Identity is _cwp_uid, a UUID in post meta, so a renamed slug moves the file and keeps the item. A post that has none is adopted — named in the output and confirmed, because an adoption that guesses wrong marries two unrelated pages.

media describes each attachment by path, with a note field that is yours: cwp reads it, carries it through a round trip and never writes it. It is there because the committed file is the only place a remark about an image survives a cwp pull, which replaces the local database wholesale.

Configuration

Absent means the project does not use the transfer, and cwp content says so. One group inline is the common case:

content:
  dir: content/
  post_types: [page, post]
  meta:
    include: ["_bricks_*", "_thumbnail_id", "_wp_page_template"]
    exclude: ["_edit_lock", "_edit_last"]

…or a map of named groups, which is what a custom post type wants — its own directory and usually its own meta rules:

content:
  pages:
    post_types: [page]
  event:
    dir: events/
    post_types: [acme_event]
    meta:
      include: ["_bricks_*", "_event_*"]

A group’s dir defaults to its own name. The two shapes are told apart by whether dir/post_types/meta sit at the top, and they live under content: rather than at the top level of .cwp.yml because the top level is a reserved namespace — a post type called bricks or pull would collide with a real key.

The meta allowlist is short, and every export names what it skipped, once, with the key to add. Losing a site’s SEO fields silently on the first push is the failure class this project does not ship. Bricks’ own page content and settings never travel as raw meta — they go through Bricks’ abilities, which is what normalises them and takes the revision.

What it does not do

It deletes nothing on the target and has no --prune. Files are replaceable; content is not.

It does not merge two versions of a page, and it does not transfer post types no group claims. Editorial content that lives and changes on production should keep doing that — this serves the page you build, which is a design artifact stored as a post.