hirasso / wp-sync-deploy
Sync and deploy your WordPress website between environments 🔀
Fund package maintenance!
Requires
- php: >=8.2
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-27 16:56:12 UTC
README
Sync and deploy your WordPress website between environments 🔀
- sync your WordPress database from production or staging to your local dev environment
- deploy your local core, plugins, mu-plugins and theme to production or staging
- run common tasks through wp-cli on the remote server to ensure your deploy works as expected
- tested on OSX and Linux systems
Note
For less technical users I recommend to use a plugin instead. For database migrations, I'd recommend WP Migrate. For deployment, I'd recommend good old (S)FTP.
Prerequisites
- A writable SSH home directory on the remote server. wp-sync-deploy brings its own WP-CLI and uploads it to
~/.wp-sync-deploy/, outside of the web root - PHP >= 8.2 on your local machine
- A WordPress directory structure similar to this (adjustable through a
wp-sync-deploy.config.phpfile):
. ├── content # your WordPress content folder (equivalent to the standard wp-content) │ ├── plugins │ ├── themes │ ├── ... ├── core # your WordPress core folder (wp-admin, wp-includes, ...) ├── index.php # main WordPress entry file └── wp-config.php # your wp-config file
Tip
While it's easy to setup the custom directory structure yourself, I'd recommend to use a framework like Bedrock, WPStarter or wordplate. All of these provide amazing convenience features for modern WordPress development.
Installation
composer require --dev hirasso/wp-sync-deploy
All commands are then available through a single binary:
vendor/bin/wp-sync-deploy <config|sync|deploy|upload> [args]
Optionally, add script aliases to your project's composer.json:
"scripts": { "sync": "wp-sync-deploy sync", "deploy": "wp-sync-deploy deploy", "upload": "wp-sync-deploy upload" }, "config": { "process-timeout": 0 }
…so that you can run composer deploy production. The process-timeout prevents composer from aborting long-running syncs and deploys.
Warning
Composer silently drops options like --config or --paths that are passed to script aliases. Separate them with --:
composer deploy -- production --config=my-custom.config.php
Setup
Run this command:
vendor/bin/wp-sync-deploy config init
(vendor/bin/wp-sync-deploy setup still works as an alias.) It detects your local WordPress setup (URL, root, core, content and public dirs, git repo, composer) through WP-CLI, asks for your remote servers with the detected values as defaults, writes the config and validates it.
With -n (--no-interaction, also used automatically without a terminal), it writes the detected local values and placeholders for the remote server without asking.
You should now have these two files in your working directory:
wp-sync-deploy.config.php
This file holds all information about your environments (local, staging, production). See Configuration.
wp-sync-deploy.tasks.php
This file is used to run automated tasks after each deploy or sync. You can adjust this file as you wish or delete it if you don't want it to be executed.
Validate
After adjusting your config, check it:
vendor/bin/wp-sync-deploy config validate # all environments vendor/bin/wp-sync-deploy config validate --local # skip the remote checks
This prints the resolved config and checks your local paths, git repo and rsync. For each remote, it checks the SSH connection, the remote root, .allow-deployment (deploy strategy conservative) and the command line PHP version, and uploads WP-CLI. It also compares each configured URL with the home URL in WordPress.
Remote server preparation
wp-sync-deploy performs a few security checks before proceeding with a deploy:
- Do all directories marked for deployment exist locally? Do the WordPress core, plugins and themes directories exist remotely? (other directories like
mu-pluginsare created if missing) - Does a hidden file
.allow-deploymentexist in the remote environment's root dir (rootDir)? - Does the local command-line PHP version match the one on the remote environment?
- Does the local web-facing PHP version match the one on the remote environment?
So when you are starting, you will need to
- Perform the first deployment manually (or via the upload command)
- Add an empty file
.allow-deploymentto your remote root dir (rootDir) - Make sure that your local and remote server are set to use the same PHP version
Usage
Synchronise the database between environments
# sync the database from your production server vendor/bin/wp-sync-deploy sync production # sync the database from your staging server vendor/bin/wp-sync-deploy sync staging # push your local database to your staging server (asks you to type `push` to confirm) vendor/bin/wp-sync-deploy sync staging push # Backup the remote database and store it locally vendor/bin/wp-sync-deploy sync <production|staging> backup
Note
Pushing your local database is only possible to the staging server.
Deploy your local files to remote environments
# preview the deploy to your production server (dry run) vendor/bin/wp-sync-deploy deploy production --dry-run # deploy your files to your production server vendor/bin/wp-sync-deploy deploy production # deploy your files to your staging server vendor/bin/wp-sync-deploy deploy staging # deploy without the confirmation prompt vendor/bin/wp-sync-deploy deploy production --yes
Warning
Since 4.0.0, deploys are live by default. Use --dry-run for a preview.
Before deploying, wp-sync-deploy asks for confirmation if the remote WordPress version is newer than the local one (e.g. after a security update applied on the server). The same goes for plugins and themes that are newer at the remote or missing locally (e.g. after an update or install via wp-admin), as the deploy would downgrade or remove them.
If your root dir is in a git repo, deploys to production also ask for confirmation on a branch other than master or main and on uncommitted or unpushed changes. Configure this with git: new Git(dir: …, productionBranch: …) (e.g. if your repo is your theme), or disable it with git: false.
A --dry-run never asks for confirmation. Instead, it lists everything a live deploy will ask about at the end.
Composer dependencies
With composer: new Composer() in your config, wp-sync-deploy deploys your vendor directory without dev dependencies. Your local vendor directory stays untouched. Don't add vendor to additional in this case.
If your composer.json is not in the root dir (e.g. inside your theme), pass its location: new Composer(dir: 'content/themes/my-theme').
wp-sync-deploy asks for confirmation if the vendor directory in your root dir or git repo contains dev dependencies, and suggests the matching new Composer() config. The vendor directories that third-party plugins ship are not checked.
config validate warns if your composer.lock contains runtime libraries but your config has no composer:. Projects that only use composer for dev tools or for installing plugins and themes don't need it.
Simple Upload
To make sure you are uploading to the correct directory, the remote directory needs to
contain a file .allow-deployment
# Upload files to the remote root vendor/bin/wp-sync-deploy upload <environment> --paths # For example: vendor/bin/wp-sync-deploy upload staging --paths=".env wp-cli.yml config public vendor"
Run automated tasks after each deploy / sync ✨
wp-sync-deploy will automatically run tasks on the target server when you sync or deploy. Modify the wp-sync-deploy.tasks.php file created by the setup command, to customize which tasks should be executed.
Default tasks defined in the file are:
- Optionally delete all transients
- Optionally delete your static cache
- When deploying: Optionally update the rewrite rules
Configuration
wp-sync-deploy is configured through a wp-sync-deploy.config.php file returning a typed Hirasso\WPSyncDeploy\Config object. Your IDE will autocomplete and document all options. Have a look at the example file.
The remotes production and staging are both optional, but you need at least one of them.
The file is meant to be committed. For secrets like HTTP auth credentials, use environment variables exported in your shell:
httpAuth: getenv('STAGING_HTTP_AUTH') ?: null,
Important
Since 4.0.0, .env.wp-sync-deploy is not supported anymore. See Upgrading from 3.x.
Custom config file
By default, wp-sync-deploy looks for the closest wp-sync-deploy.config.php file. You can override this with the --config option:
vendor/bin/wp-sync-deploy sync production --config=my-custom.config.php
vendor/bin/wp-sync-deploy deploy production --config=my-custom.config.php
vendor/bin/wp-sync-deploy upload staging --paths="..." --config=my-custom.config.php
Note
--config must come after the required positional arguments.
Ignored files
wp-sync-deploy ignores common dev files during a deploy (node_modules, dotfiles, package.json, *.sql, ...). See Ignore::DEFAULTS in src/Config.php for the full list.
Add your own rules in your config:
$theme = 'content/themes/my-theme'; return new Config( // ... ignore: new Ignore( patterns: ["$theme/assets-src", "$theme/build.mjs", "$theme/*.md"], // added to the defaults gitattributes: true, // respect export-ignore (default) defaults: true, // use the built-in list (default) ), );
Patterns are relative to your root dir and unanchored. * doesn't cross /, so $theme/*.md only matches Markdown files in the theme root.
Files marked as export-ignore in .gitattributes files inside your themes and plugins are not deployed either. That's handy for themes and plugins you also distribute as an archive (zip, git archive, wp.org), where "not in the zip" and "not deployed" mean the same. Otherwise, prefer patterns, so all deploy rules live in one place.
/tests export-ignore *.md export-ignore
Ignored files are never deleted on the remote, with one exception: when you remove a folder locally, its remote copy is deleted together with any files matched by the defaults or export-ignore (e.g. a leftover .htaccess or node_modules). Files matched by your own patterns are never touched, so they keep such a folder alive. Don't add catch-alls like .* there. Folders that can't be deleted are listed at the end of each deploy.
Upgrading from 3.x
4.0.0 contains breaking changes. Most of them are migrated automatically:
vendor/bin/wp-sync-deploy config init
It finds your .env.wp-sync-deploy, takes over the values you set yourself (the local URL comes from WordPress) and only asks for the rest: values still as in the old example file and new options like composer: new Composer(). With -n, it doesn't ask at all. It writes wp-sync-deploy.config.php next to the old file (including custom .deployignore rules), removes the old files (git rm if tracked, otherwise renamed to .bak), uses the run() helper in your tasks file, lists what's left to do and runs config validate.
- Deploys are live by default. The
runargument has been removed. Use--dry-runfor a preview .env.wp-sync-deployhas been replaced bywp-sync-deploy.config.php.config initconverts it, see the table below for the mapping- PHP >= 8.2 is required on your local machine
vendoris deployed without dev dependencies if you addcomposer: new Composer()and removevendorfromadditional.deployignorehas been replaced byignore: new Ignore(). Its-prules (e.g.package.json,composer.json,*.config.js,README.md) never worked before and are now applied
.env.wp-sync-deploy |
wp-sync-deploy.config.php |
|---|---|
LOCAL_PROTOCOL + LOCAL_HOST |
local: new Local(url: …) |
LOCAL_HTTP_AUTH |
local: new Local(httpAuth: …) |
LOCAL_ROOT_DIR |
local: new Local(rootDir: …) |
PUBLIC_DIR |
paths: new Paths(public: …) |
WP_CONTENT_DIR |
paths: new Paths(wpContent: …) |
WP_CORE_DIR |
paths: new Paths(wpCore: …) |
ADDITIONAL_DIRS |
paths: new Paths(additional: […]) |
vendor in ADDITIONAL_DIRS |
composer: new Composer() |
GIT_DIR |
git: new Git(dir: …) |
PRODUCTION_BRANCH |
git: new Git(productionBranch: …) |
PROD_PROTOCOL + PROD_HOST |
production: new Remote(url: …) |
PROD_SSH, PROD_SSH_PORT |
ssh: …, sshPort: … |
PROD_ROOT_DIR |
rootDir: … |
PROD_PHP_BINARY |
phpBinary: … |
PROD_HTTP_AUTH |
httpAuth: … |
PROD_DEPLOY_STRATEGY |
deployStrategy: DeployStrategy::… |
STAG_* |
staging: new Remote(…) |
In your wp-sync-deploy.tasks.php, run commands with \WP_CLI::runcommand('…', ['launch' => false]) (see the run() helper in the example file). Otherwise, PHP 8.5 deprecation notices from WP-CLI show up for each command.
If you have a custom .deployignore, move your rules to ignore: new Ignore(patterns: [...]) (see Ignored files) and delete the file.
AI agents
This repo ships an agent skill that sets up or upgrades wp-sync-deploy in your project:
npx skills add hirasso/wp-sync-deploy
Optionally, let Claude Code suggest the upgrade when you open a project using an older major version, with a SessionStart hook in ~/.claude/settings.json:
"hooks": { "SessionStart": [ { "matcher": "startup", "hooks": [ { "type": "command", "command": "bash ~/.claude/skills/wp-sync-deploy/scripts/detect.sh 2>/dev/null || true", "timeout": 10 } ] } ] }
Adjust the path if the skill is installed elsewhere. Requires jq and curl.
Other notes
Set NO_COLOR=1 to disable colored output. Colors are also off when the output isn't a terminal.
Before each run, wp-sync-deploy checks if an important update is available and asks for confirmation before proceeding with an outdated version.