cwp bricks push
shipped 1.0.0cwp bricks push [env] [flags]
This name is an alias. The work moved to cwp push --only bricks, and that page documents it. This spelling still runs for one minor cycle.
Acts on the local site. Name an environment to act there instead.
| Argument | What it is | Default |
|---|---|---|
[env] | environment to push to (default: default_environment in cwp.yml) |
| Flag | What it does | Default |
|---|---|---|
--replace | overwrite items that already exist, instead of skipping them | off |
--delete | mirror: also remove items on the target the tree no longer describes | off |
--skip-in-use | with —delete: leave items the site still references in place instead of refusing | off |
--sensitive | does nothing since 2.0: credentials never enter the tree, and customCss travels on its own | off |
--skip-incomplete-fonts | push everything except fonts the tree carries no font faces for | off |
--keep-superseded-fonts | leave the font files this import replaces in the media library | off |
--force | override the protected-environment refusal | off |
--no-backup | skip the remote backup taken before the write | on |
--no-snapshot | skip the local design-system export taken before —replace or —delete | on |
--yes | skip the confirmation prompt | off |
--with-agent | with —dry-run on a host with no shell: install and remove the PHP agent so the plan is real | off |
Plus the shared flags --json, -v, --verbose, -q, --quiet and --dry-run.
What it does
cwp bricks push zips the design system the tree holds, inspects it, shows you
what collides, then imports it into the site.
Conflicts default to skip. --replace overwrites. The confirmation names
the items it would overwrite rather than leaving it to a flag you typed moments
earlier.
--dry-run inspects for real. The conflict counts it shows are the real ones.
What the target holds and the tree does not
Every push compares the two and says so. Without a flag it removes nothing. Additive drift stops being silent. The report answers “what is on the server that is not in git”:
! surplus — 4 item(s) on the target are not in the tree (classes .bg-legacy, variables --old-gap, …) — pass --delete to remove them
--delete mirrors. The confirmation names every item rather than leaving
it to a flag you typed moments earlier. The removals happen after the import:
the other order would remove what the import is about to recreate. The push
takes the design-system snapshot for --delete
as well as for --replace.
Three rules keep it from deleting things nobody meant:
-
The push compares only the types it carried. A project that manages templates as a content group ships a package with no templates in it, while the site has several. Reading that as “the tree has no templates” would delete all of them. The push names those types as not compared instead:
– not compared — templates — on the target, not carried by this push, so nothing in them was compared -
The push never mirrors
breakpointsandsettings. They are not sets: one is a singleton, the other a handful of named groups. “Absent from the tree” cannot mean anything for them. -
The push refuses to remove anything still in use, and no flag overrides it. Usage comes from
cwp bricks audit, which is Bricks’ own accounting of its own ids. A wrongly deleted file comes back. A deleted global class breaks every page using it, invisibly. The way past the refusal is to stop referencing the item and pull again.✗ refusing to remove 1 item(s) "prod" is still using: classes .hero → nothing has been written. They are kept because the audit did not report them as unused — read it yourself with `cwp bricks audit prod --scope unused`The reading is live. The audit rescans every Bricks post and template on each call. The same command run twice gives the same answer, and there is no cache to refresh before trying again.
--skip-in-useis the way past it. It is safe in the same way--skip-incomplete-fontsis: it takes those items out of the run rather than permitting their deletion, so they survive either way. Without it one referenced class blocks the removal of everything else. The mirror it blocks would have been correct. The confirmation and the run name the items the push skipped.
If a removal fails, for a locked class or a race with somebody in the builder, the run says so rather than claiming to have mirrored:
! 1 item(s) are still on the target — the mirror is partial:
classes .bg-legacy — the class is locked
cwp has no verified delete path for global-queries, icon-manager and
custom-capabilities. The push reports those types as surplus, leaves them
alone and names the reason.
The target’s own settings are kept
Bricks’ importer replaces a settings tab rather than merging it. It unsets every key the tab knows on the target, then writes the package’s keys. So the push would delete a key the tree holds back on purpose, the availability flag first of all. The site would then read as open. The push reads the target before the import, writes the target’s own values for those keys into the package, and says so:
✓ settings — 1 key(s) kept as dev holds them — maintenanceMode
The keys are the ones cwp bricks pull holds back: the
per-environment flag. The push does not touch the tree’s own file. The splice
lives in the archive it sends.
A post reference in the tree (login_page, maintenanceTemplate and seven
more) becomes the target’s own id the same way. The push resolves it through
the post’s uid and then its slug, and the run names the keys:
✓ settings — 2 post reference(s) resolved on dev — login_page, maintenanceExcludedPosts
A reference that means no post on the target refuses the push before it writes anything. Writing the resolver’s “not found” into a login-page setting would silently select the default:
✗ 1 post reference(s) in the builder's settings point at a post dev does not have: login_page → page/login
→ push the content first (cwp push --only content), or remove the reference from the settings file
A tree pulled with --sensitive before 2.0 still names the api-keys and
custom-code tabs. Bricks refuses to import those two tabs at all. The push
leaves them out and says so. The next pull rewrites the file without them.
Bricks’ admin save does two things after the option write that its importer
does not. The push does both afterwards and names them. It creates the
form-submissions and query-filter tables for a feature this push switched on.
It runs wp bricks regenerate_assets when the CSS loading method or the
cascade layer changed.
✓ settings — saveFormSubmissions switched on — its tables exist
Against a remote this is an upward write
The push carries the full guard set. A protected environment refuses
without --force. Nothing transfers without confirmation. cwp takes a remote
backup first. “It is only design data” is no reason to relax any of that:
--replace across a site’s global classes is worse than most file pushes.
A font the tree cannot carry is refused, not sent
Bricks’ importer reads an empty face list as an instruction to delete the
font’s faces on the target. The @font-face rules the frontend renders from
go with them.
cwp bricks pull fetches the font files before it exports. A tree
with an empty font is one where that fetch could not happen. The push refuses
to send it:
✗ refusing to push 1 font(s) with no faces over "dev", which has them: Example Sans
→ --replace overwrites what is there; it does not delete what the tree could
not bring. Bring the font files down first (cwp pull dev --uploads sync
--uploads-only, then cwp bricks pull), or push everything else with
--skip-incomplete-fonts
--replace does not override this. The flag answers “overwrite what is
there”, not “delete what I failed to bring”. --skip-incomplete-fonts leaves
those fonts out of the push and sends everything else. The target keeps the
font it has, and the confirmation names what the push left behind.
The other two cases destroy nothing, and the push reports them as warnings.
Without --replace Bricks skips a font that already exists. On a target that
does not have the family at all, an empty font entry is useless rather than
dangerous.
The font files an import replaces are removed
Bricks attaches a font binary to the media library every time it imports one.
It never checks whether the identical file is already there, and WordPress
renames on collision. Left alone, every push adds a new kula-1.woff,
kula-2.woff, kula-3.woff, forever. The next push orphans each one. Nobody
sees it: the font resolves to the newest file and renders.
The push removes those files and names each one:
✓ superseded font file — deleted attachment 12804 — 2026/02/kula-1.woff
Only ever the difference. The push asks the target which files its font faces use before the import and again after it. A path a face used before and no face uses now was replaced by this import and by nothing else. A font attachment your own CSS points at stays. No face ever pointed at it, so it is not in the “before” set.
This runs inside the push’s own guard set. The confirmation, the remote backup
and the design-system snapshot have all happened when it starts. The push
deletes nothing else. --keep-superseded-fonts leaves the files in place if
you would rather sweep up by hand.
The design-system snapshot
Bricks revisions the element tree of a post, and a bad page comes back. It does not revision global classes, variables, colours or theme styles. Deleting one of those is immediate and final. Without a snapshot, the only net is an export you remembered to take.
cwp exports for you. Before a destructive write against the local site, it
writes the current design system into cwp/bricks-snapshots/<timestamp>/:
✓ design-system snapshot — restore with: cwp bricks push --replace
- Two commands take it: this one with
--replace, andcwp abilitywhen the ability declares itself destructive and its category is one an export can restore. The plain ability door is the way a class gets deleted in practice. The snapshot has to cover that door as well. - A remote takes none. The host’s backup in the guard set covers it. The
split matches
cwp pull, which takes a local snapshot, andcwp push, which takes a remote backup. - A failed snapshot aborts the write. When cwp cannot make the restore point, it deletes nothing.
- It is a directory, not a commit. cwp touches no git repository. The
directory ignores itself and never shows up in
git status. Snapshots accumulate, and cwp never prunes them.
cwp takes the snapshot before --replace and before --delete. Both can
remove something no revision brings back.
To recover, point builder.options.export_dir at the snapshot and run this
command with --replace. Switch the snapshot off with --no-snapshot per run,
or with cwp config set defaults.snapshot_before_bricks_write false for good.
Example
cwp bricks push # into the local site
cwp bricks push --dry-run # what would happen, checked for real
cwp bricks push prod --replace --force # overwrite on a protected environment
Attachments resolve on the target
A media: path in the tree becomes the target’s own attachment before the
push inspects the package. The push resolves it by upload path, as a content
push does. {{site}} becomes the target’s host. An attachment the target does
not have refuses the push and names the path and the file:
✗ bricks references 1 attachment(s) that "dev" does not have: 2026/02/logo.png (styles/classes/classes.json)
→ bring the uploads to the target first, or take the image out of the item that names it
What it does not do
It does not keep a template’s element ids. Bricks’ importer generates a
fresh id for every element in a template it writes. No setting or argument
holds it back. The importer rewrites the references inside that template, and
they stay correct. It does not rewrite a selector elsewhere that names
#brxe-<id>, and that selector stops matching. The push warns whenever the
package contains templates:
! 3 template(s) in this push will arrive with new element ids
The way around it is not a flag. Declare a content group for bricks_template,
and templates travel with cwp content push instead.
That command writes the tree as it stands. The design system stops carrying
templates the moment that group exists.
It does not create post types. A template whose condition names a post type
the target does not register imports cleanly and then matches nothing. cwp
warns per unknown slug and tells you to cwp deploy the site
plugin first. It warns rather than aborting: an intentionally inactive template
is legitimate.