Title: Content Relay – Push Pages and Posts from Staging to Live
Author: Mahesh Bohara
Published: <strong>October 29, 2026</strong>
Last modified: October 29, 2026

---

Search plugins

![](https://ps.w.org/content-relay/assets/banner-772x250.png?rev=3772480)

![](https://ps.w.org/content-relay/assets/icon-256x256.png?rev=3772480)

# Content Relay – Push Pages and Posts from Staging to Live

 By [Mahesh Bohara](https://profiles.wordpress.org/maheshbohara/)

[Download](https://downloads.wordpress.org/plugin/content-relay.1.0.0.zip)

 * [Details](https://wordpress.org/plugins/content-relay/#description)
 * [Reviews](https://wordpress.org/plugins/content-relay/#reviews)
 *  [Installation](https://wordpress.org/plugins/content-relay/#installation)
 * [Development](https://wordpress.org/plugins/content-relay/#developers)

 [Support](https://wordpress.org/support/plugin/content-relay/)

## Description

Content Relay pushes one piece of content from staging to live, or from live back
to staging or development, without a full database migration. It relays a single
page, post or custom post type entry, with every translation and its media, between
the environments of the same site, in either direction.

Typical uses:

 * A page built on staging is approved and has to go live on production.
 * A page edited on production is needed on development to work on it with real 
   content.
 * A batch of posts written on one environment has to appear on another.

In each case you sync only that content, without copying a whole database or overwriting
what else changed on the other site. Orders, form entries, users and settings on
the live site are left alone.

**The theme must match.** Content Relay only works between sites running the same
theme at the same version. A different active theme or theme version blocks the 
relay; the same version with different theme files gives a warning. Deploy the theme
first, then relay the content.

The same plugin runs on both sites. The receiving site issues a key; the sending
site stores it. From then on an administrator can hover an item in **Pages** (or
any other content list switched on in the settings), choose **Relay to…**, review
what the receiving site plans to do, and relay. Many posts can be relayed at once
with WP-CLI.

#### What a relay carries

 * The page or post and every translation of it (WPML), with fields, meta, Yoast
   SEO data and its terms.
 * The images and files it uses. The original upload is sent once; the receiving
   site regenerates the image sizes. A file with the same name but different bytes
   is saved under a new name, never overwritten.
 * References inside the content: block attributes, ACF fields (images, files, galleries,
   posts, terms, users), image IDs in HTML, and links to the sending site, all rewritten
   to the receiving site’s IDs and address.
 * Terms the content is assigned to that the receiving site lacks, created in the
   right language.

Content is written byte for byte. Block markup is edited only where a reference 
changes; it is never re-serialized.

#### Review before anything is written

The receiving site plans every relay and answers with a list of what it will create,
update, reuse or refuse, with a field, meta and content diff for anything it would
replace. Nothing is written until an administrator approves that plan. If the receiving
site changes in the meantime, the relay is refused and the plan must be approved
again.

Content that was edited on the receiving site since its last relay is blocked. It
can be relayed with **Overwrite**, which keeps a backup.

#### After a relay

 * Object caches, WPML caches and Yoast indexables of the relayed posts are cleared.
 * Page caches are purged for WP Rocket, LiteSpeed Cache, W3 Total Cache, WP Super
   Cache, SiteGround Optimizer and Breeze. Other cache layers can hook `content_relay_purge_page_cache`.
 * The sending site fetches each relayed page from the receiving site and checks
   the HTTP status and address, `<html lang>`, the hreflang alternates, that no 
   link still points at the sending site, and that its images load.
 * Every relay is logged on both sites under **Tools  Content Relay history** and
   can be rolled back from either: what it replaced is restored and what it created
   is removed.

#### What a relay does not carry

 * **The theme.** Deploy it first: a relay is blocked when the theme or its version
   differs.
 * Comments.
 * Menus, widgets, site options and plugin settings.
 * User accounts. Authors are matched by login and fall back to the key’s administrator.
 * Content a page lists by query instead of by ID, such as “the latest events tagged
   with this page’s term”. A theme can declare such dependencies with the `content_relay_query_dependencies`
   filter; the receiving site then warns when they find nothing.
 * Parent pages and referenced posts must already exist on the receiving site; the
   plan names any that are missing.
 * Images held in ACF fields of a term the relay has to create are not relayed; 
   those references are removed and reported.

#### Security

 * Every request is signed with Ed25519 and carries a timestamp and a single-use
   nonce. Refused requests are rate-limited per IP.
 * The receiving site keeps only the public half of a key; the key itself is shown
   once. The sending site stores it encrypted with `CONTENT_RELAY_ENCRYPTION_KEY`,
   or with the `AUTH_KEY` and `SECURE_AUTH_KEY` salts.
 * A key can only relay content: posts, their media and their terms. A signed request
   is not logged in as any user and cannot manage users, plugins, themes or settings.
 * Only administrators (`manage_options`) can relay, issue keys or add connections.
   A key stops working when it is revoked or when the administrator who issued it
   loses that role.
 * The receiving site must use HTTPS.
 * `CONTENT_RELAY_IP_ALLOWLIST` in wp-config.php limits which sending IPs are accepted.

#### Requirements and limits

 * PHP 7.4 or later, WordPress 6.4 or later, InnoDB tables.
 * WPML is supported; Polylang is not. ACF (including ACF blocks), core blocks and
   Yoast SEO are supported; Elementor content is not.
 * A receiving site behind HTTP basic authentication is not supported yet.
 * Tested on single sites.

#### Connections to other sites

Content Relay contacts only the sites you connect it to with a key: it sends the
relay to the receiving site and afterwards fetches the relayed pages from it to 
check them. It uses no third-party service, sends nothing to the plugin author and
does no tracking.

#### WP-CLI

 * `wp content-relay keys generate --label=<label>` · `keys list` · `keys revoke
   <kid>` — on the receiving site.
 * `wp content-relay connections add <key> --label=<label>` · `connections list`·`
   connections test <label>` · `connections remove <label>` — on the sending site.
 * `wp content-relay plan <page-id> --to=<label>` — ask the receiving site for its
   plan without relaying.
 * `wp content-relay push <page-id> --to=<label> [--overwrite] [--flush-cache] [--
   yes]` — relay, then check the pages.
 * `wp content-relay push-many [<id>...] --to=<label> [--post_type=<type>] [--tag
   =<slug>] [--category=<slug>] [--all] [--dry-run]` — relay many posts, one translation
   group at a time.
 * `wp content-relay verify <session>` — check a relay’s pages again.
 * `wp content-relay rollback <session> --to=<label>` — roll back a relay sent to
   another site; without `--to`, a relay received here.
 * `wp content-relay history [--direction=out|in] [--offline]`
 * `wp content-relay discover <page-id>` · `bundle <page-id>` · `plan <dir>` · `
   apply <dir>` — an offline bundle, written to `uploads/content-relay/bundles/`,
   for sites that cannot reach each other.

#### Hooks

 * `content_relay_post_types` — post types that can be relayed (default: pages).
 * `content_relay_meta_denylist` — meta keys never relayed.
 * `content_relay_resolvers` — reference resolvers used to discover what a page 
   needs.
 * `content_relay_query_dependencies` — content a page lists by query, checked after
   a relay.
 * `content_relay_page_cache_integrations`, `content_relay_purge_page_cache` — page-
   cache purging.
 * `content_relay_before_commit`, `content_relay_applied` — around the content commit
   on the receiving site.
 * `content_relay_request_denied` — a refused relay request.
 * `content_relay_http_args`, `content_relay_max_chunk_bytes` — transport tuning.

## Screenshots

[⌊A "Relay to…" action on every page you can relay.⌉⌊A "Relay to…" action on every
page you can relay.⌉[

A “Relay to…” action on every page you can relay.

[⌊The receiving site's plan, with a diff of each field and of the content it would
change. Nothing is written until you approve it.⌉⌊The receiving site's plan, with
a diff of each field and of the content it would change. Nothing is written until
you approve it.⌉[

The receiving site’s plan, with a diff of each field and of the content it would
change. Nothing is written until you approve it.

[⌊The result of a relay and the checks the sending site ran on the relayed page.⌉⌊
The result of a relay and the checks the sending site ran on the relayed page.⌉[

The result of a relay and the checks the sending site ran on the relayed page.

[⌊The relay history, where any relay can be rolled back.⌉⌊The relay history, where
any relay can be rolled back.⌉[

The relay history, where any relay can be rolled back.

[⌊The receiving site's settings: receiving switched on, and the key issued to the
sending site.⌉⌊The receiving site's settings: receiving switched on, and the key
issued to the sending site.⌉[

The receiving site’s settings: receiving switched on, and the key issued to the 
sending site.

## Installation

 1. Install and activate Content Relay on **both** sites.
 2. On the receiving site, open **Settings  Content Relay**, switch **Receiving** on
    and issue a key. Copy it: it is shown once.
 3. On the sending site, open **Settings  Content Relay**, switch **Sending** on and
    add a connection by pasting the key. A handshake checks it before it is stored.
 4. Deploy the same theme, at the same version, to both sites.
 5. On the sending site, hover a page in **Pages** and choose **Relay to…**. To relay
    posts or custom post types as well, tick them under **Settings  Content Relay**.

Sending and receiving can also be pinned in wp-config.php with `define( 'CONTENT_RELAY_SEND',
true );` and `define( 'CONTENT_RELAY_RECEIVE', true );` (or `false`). A defined 
constant overrides the switch and locks it.

## FAQ

### How do I push a page from staging to live?

Install the plugin on both sites, issue a key on the live site and paste it into
the staging site. Then hover the page in **Pages** on staging, choose **Relay to…**,
review the plan the live site answers with, and relay.

### Does it copy or overwrite the whole database?

No. Only the page or post you relay, its translations, its media and the terms it
needs are written. Everything else on the receiving site stays as it is.

### Can I pull content from live back to staging?

Yes. A relay works in either direction: switch receiving on for the staging site
and send from live.

### Can a site both send and receive?

Yes. Switch both on and connect each site to the other with a key issued by that
site.

### I copied the production database to a development site. Is that a problem?

A database copy carries the switches and the keys production issued, so the copy
arrives with receiving switched on and those keys active. Switch receiving off on
the copy, or pin `CONTENT_RELAY_RECEIVE` to `false` in its wp-config.php. Stored
connection keys cannot be decrypted on a site with different salts.

### What happens if a relay fails halfway?

The content is committed in one database transaction. If anything fails, the transaction
is rolled back and the media created for that relay are removed.

### The receiving site uses Redis or Memcached.

Content Relay clears the cache entries of the posts it writes. Tick **Flush the 
entire object cache** on the relay screen, or pass `--flush-cache` to `wp content-
relay push`, if other cached data depends on them.

### What does deleting the plugin remove?

Deactivating keeps everything. Deleting the plugin removes its settings, keys and
connections, the relay log, the relay identity stored on posts and terms, and `wp-
content/uploads/content-relay/`, which holds the rollback backups. Relayed pages,
media and terms stay.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“Content Relay – Push Pages and Posts from Staging to Live” is open source software.
The following people have contributed to this plugin.

Contributors

 *   [ Mahesh Bohara ](https://profiles.wordpress.org/maheshbohara/)

[Translate “Content Relay – Push Pages and Posts from Staging to Live” into your language.](https://translate.wordpress.org/projects/wp-plugins/content-relay)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/content-relay/), check
out the [SVN repository](https://plugins.svn.wordpress.org/content-relay/), or subscribe
to the [development log](https://plugins.trac.wordpress.org/log/content-relay/) 
by [RSS](https://plugins.trac.wordpress.org/log/content-relay/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 1.0.0

 * Renamed from Page Relay to Content Relay: the plugin relays posts and custom 
   post types as well as pages. Options, hooks, constants, the REST routes and the
   WP-CLI command are now named `content_relay` / `content-relay`; data stored under
   the old names is not migrated.
 * A Settings link on the plugin’s row of the Plugins screen.
 * Tested with WordPress 7.1.

#### 0.2.0

 * `wp content-relay push-many`: relay many posts at once (by ids, `--tag`, `--category`
   or `--all`, matched in every language), one translation group per session, with`--
   dry-run`, `--existing=update|skip`, `--media-fallback` and `--skip-missing-media`.
 * Settings: choose the relayable post types (pages by default).
 * References the sender cannot resolve — deleted posts, revisions, terms of unregistered
   taxonomies, skipped media — are dropped (mapped to 0) instead of blocking the
   relay.
 * Duplicates on the receiver (same file or same address) are resolved by the sender’s
   own id on sites cloned from one database; a referenced post with no language 
   is found by its address.
 * Fixed: a re-relay of a post with dropped references no longer reports it as changed.

#### 0.1.0

 * First release: keys and connections, the relay screen with plan and diffs, page-
   cache purging, checks after a relay, history with rollback on both sites, WP-
   CLI commands and offline bundles.

## Meta

 *  Version **1.0.0**
 *  Last updated **2 days ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.4 or higher **
 *  Tested up to **7.1.4**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [content sync](https://wordpress.org/plugins/tags/content-sync/)[deployment](https://wordpress.org/plugins/tags/deployment/)
   [migration](https://wordpress.org/plugins/tags/migration/)[staging](https://wordpress.org/plugins/tags/staging/)
   [wpml](https://wordpress.org/plugins/tags/wpml/)
 *  [Advanced View](https://wordpress.org/plugins/content-relay/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/content-relay/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/content-relay/reviews/)

## Contributors

 *   [ Mahesh Bohara ](https://profiles.wordpress.org/maheshbohara/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/content-relay/)