Skip to content

cwp bricks push

shipped 1.0.0
cwp 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.

tree → site

ArgumentWhat it isDefault
[env]environment to push to (default: default_environment in cwp.yml)
FlagWhat it doesDefault
--replaceoverwrite items that already exist, instead of skipping themoff
--deletemirror: also remove items on the target the tree no longer describesoff
--skip-in-usewith —delete: leave items the site still references in place instead of refusingoff
--sensitivedoes nothing since 2.0: credentials never enter the tree, and customCss travels on its ownoff
--skip-incomplete-fontspush everything except fonts the tree carries no font faces foroff
--keep-superseded-fontsleave the font files this import replaces in the media libraryoff
--forceoverride the protected-environment refusaloff
--no-backupskip the remote backup taken before the writeon
--no-snapshotskip the local design-system export taken before —replace or —deleteon
--yesskip the confirmation promptoff
--with-agentwith —dry-run on a host with no shell: install and remove the PHP agent so the plan is realoff

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 breakpoints and settings. 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-use is the way past it. It is safe in the same way --skip-incomplete-fonts is: 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, and cwp ability when 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, and cwp 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.