wp_block
Operate on one Gutenberg or ACF block per post with list, get, insert, replace, update-attrs, remove, move operations using WordPress core parsing to avoid post_content corruption.
Instructions
Surgically operate on ONE Gutenberg/ACF block of a single post without disturbing the rest of the body. This is the block-level primitive that whole-body wp_sync_post lacks: insert, replace, read, update-attrs, remove, or move exactly one block while every other block stays byte-identical. Use it INSTEAD of hand-written str_replace/eval surgery on post_content (which repeatedly corrupted markup: base64 transcription bugs, self-closing /--> terminators, multisite --url errors). All parsing/serialization runs in WordPress core (parse_blocks / serialize_blocks) via one PHP eval — never string surgery — so self-closing ACF blocks, inner blocks, and freeform HTML round-trip faithfully. ops: 'list' (ordered block summary: index, blockName, anchor, attr/ACF-data keys), 'get' (one block's parsed attrs + exact markup), 'insert' (add a block at a position), 'replace' (swap the matched block), 'update-attrs' (merge ACF field values into the matched block's attrs.data, preserving existing _field key pointers), 'remove' (delete the matched block), 'move' (reorder). Selectors: 'name:' (first of type), 'name:#' (Nth, 0-based), 'anchor:', or 'index:' (Nth top-level block). The block arg is either raw block markup OR an object {name, data} where data merges into the ACF attrs.data. Content never passes through the caller's text: post_content is read on the server, payloads are carried base64 in code, and every write RE-PARSES the saved post and reports verified=true only if the round-trip md5 matches. Read ops (list/get) are never guarded; write ops on target='production' require confirm=true when PROD_GUARD is enabled (a preview of the change is returned instead when blocked). Pass preview=true to get the intended change without writing. Transport (local Docker / Terminus / SSH) is resolved from wp-cli.conf exactly like wp_cli.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| op | Yes | The block operation to perform. | |
| env | No | Terminus environment override (e.g. 'dev', 'test', 'live') for target='production' on a Terminus site. Omit for the site's TERMINUS_ENV. | |
| data | No | For op='update-attrs': field -> value pairs merged into the matched block's attrs.data (ACF). Existing _field key pointers are preserved. | |
| site | No | Which configured site to target (matches a [site:NAME] section in wp-cli.conf). Omit for DEFAULT_SITE or the sole site. | |
| block | No | The block to insert/replace, as raw block markup (string) OR an object {"name": "acf/...", "data": {field: value}}. For the object form, data merges into the ACF attrs.data. Required for insert/replace. | |
| target | No | Where the post lives. 'local' = Docker container (default). 'production' = remote environment; transport (Terminus vs SSH) is chosen by the site's conf. | local |
| confirm | No | Required to write to a production post when the guard is enabled. Default: false. | |
| post_id | Yes | The post whose blocks to operate on. | |
| preview | No | For write ops: compute and return the intended new post_content (new_md5, new_content_b64) WITHOUT saving. Default false. | |
| position | No | Where to insert/move: 'first', 'last', 'index:<N>' (before top-level index N), 'before:<selector>', or 'after:<selector>'. For insert, defaults to 'last'. Required for move. | |
| selector | No | Which block to target. 'name:<blockName>' (first of that type), 'name:<blockName>#<N>' (Nth, 0-based), 'anchor:<anchor>', or 'index:<N>' (Nth top-level block). Required for get/replace/update-attrs/remove/move. | |
| dedupe_by | No | For op='insert': skip the insert (idempotent) if a block already matches by 'anchor' or 'name'. Omit to always insert. |