# WordPress tools
URL: https://synquic.tech/docs/guides/wordpress.md

Manage a WordPress site from its service: versions, plugin, theme and core updates, maintenance and debug mode, search-replace for a new URL, an admin password reset and PHP limits, all through WP-CLI.

import { Step, Steps } from 'fumadocs-ui/components/steps';

A service that runs the official **WordPress** image gets a **WordPress** tab. It shows what the site is
running and lets you do the everyday WordPress chores without opening a terminal: update WordPress, its
plugins and themes, switch maintenance or debug mode on, move the site to a new address, give an
administrator a new password, and raise PHP's upload and memory limits.

<Callout title="What you need first">
- A service running the official `wordpress` image (any version: Apache, FPM or Alpine), deployed with
  Docker to one of your own servers. The [WordPress app](/docs/guides/one-click-apps) from the catalog is
  set up this way.
- The WordPress install finished: open the site once and create the admin account.
- To change anything: the **Owner** or **Admin** role in the organization. Everyone who can see the service
  can read the tab.
</Callout>

## Where it is

Open the project, then **Apps & Services**, click the WordPress service and open its **WordPress** tab.
The tab only appears for services whose image is `wordpress:<tag>`; the `wordpress:cli` image and other
WordPress images (for example Bitnami's) don't get it.

## How Synq talks to WordPress

Everything runs through [WP-CLI](https://wp-cli.org), the official command-line tool for WordPress. The site
container is never changed for this:

- Synq starts a **one-shot container** from the official `wordpress:cli` image next to your site, with the
  same PHP version when the site's tag names one (`wordpress:cli-php8.4` for `wordpress:7.1.2-php8.4-apache`).
- That container shares the site's **files** (its volumes), its **network** (so the database host resolves
  the same way) and its **environment** (the `WORDPRESS_DB_*` settings), and runs as the user that owns the
  WordPress files, so updated files stay writable by the web server.
- It runs one command and is removed. Reads take a few seconds; updates can take a few minutes.
- If the server can't pull the `wordpress:cli` image and your image ships its own `wp`, Synq uses that
  inside the site container instead.

Commands are built as argument lists, never as shell text, and every value you type is checked against a
strict pattern first: plugin and theme names, URLs and user names. The new admin password is passed to
WP-CLI in an environment variable, never on a command line.

## What the tab shows

- **WordPress** and **PHP** versions, the **site URL** (and the home URL when it differs), and how WP-CLI runs.
- A **core update** when WordPress offers one.
- **Plugins** and **Themes**: active or inactive, version, and whether an update is available.
- **Maintenance mode** and **Debug mode** switches.
- **PHP settings**, with the values the site reads right now.

Press **Refresh** to read everything again.

## Update WordPress, plugins and themes

<Steps>

<Step>

### Choose what to update

Press **Update** next to the WordPress version, next to a plugin or theme, or **Update all** above the
plugin or theme list.

</Step>

<Step>

### Back up first

The confirmation offers **Back up first** when the project has a backup policy for the WordPress service,
its database service (the one `WORDPRESS_DB_HOST` names) or one of their volumes. It is on by default: Synq
runs those backups, shows their progress, and starts the update only when all of them succeeded. If a
backup fails, nothing is changed; you can cancel or choose **Continue without a backup**.

Without a policy the dialog says so. Set one up on the service's **Backup** tab first if you want a copy
to go back to; see [Backups & restore](/docs/guides/backups-restore).

</Step>

<Step>

### Update

WordPress shows its maintenance page while an update runs. A core update also runs the database upgrade
(`wp core update-db`). When it finishes, the result shows each item's old and new version.

</Step>

</Steps>

WordPress removes maintenance mode when an update finishes. If maintenance mode was on before, Synq
switches it back on afterwards.

## Plugins and themes

- **Activate** or **Deactivate** a plugin, and **Delete** an inactive one (its files are removed; its
  settings in the database stay).
- **Activate** a theme to switch the site to it.

A plugin that breaks WordPress can still be listed and deactivated here: Synq lists plugins again without
loading any of them when the normal list fails.

## Maintenance mode

Switch **Maintenance mode** on and visitors see WordPress's "Briefly unavailable for scheduled
maintenance" page until you switch it off. Unlike the file `wp maintenance-mode activate` writes, Synq's
does not expire after ten minutes.

## Debug mode

Switching **Debug mode** on sets `WP_DEBUG` in `wp-config.php`, logs PHP errors to `/tmp/wp-debug.log`
inside the site container, and keeps them off the page (`WP_DEBUG_DISPLAY` false). The log is outside the
web root, so visitors can't download it; read it from the service's **Terminal** tab:

```bash
tail -f /tmp/wp-debug.log
```

The log is cleared when the container is recreated. Switching debug mode off sets `WP_DEBUG` and
`WP_DEBUG_LOG` back to false.

<Callout type="warn" title="WORDPRESS_DEBUG">
The official image reads `WP_DEBUG` from the `WORDPRESS_DEBUG` variable. Once you use the switch,
`wp-config.php` holds the value itself and `WORDPRESS_DEBUG` no longer changes it.
</Callout>

## Flush the cache and permalinks

**Flush now** empties the object cache (`wp cache flush`) and rebuilds the permalink rules
(`wp rewrite flush`). Use it after changing permalinks or when a caching plugin shows stale pages.

## Move the site to a new URL

WordPress stores its address in the database, so a new domain or a switch to HTTPS needs the old address
replaced everywhere it is stored. **Search and replace URL** does that with `wp search-replace`:

<Steps>

<Step>

### Enter the old and new URL

Both must be full `http://` or `https://` addresses (no query string). The old one starts as the current
site URL. A trailing slash is ignored.

</Step>

<Step>

### Count the matches

**Count matches** runs a dry run and shows how many values would change. Nothing is written.

</Step>

<Step>

### Replace

**Replace N matches** asks for confirmation (with **Back up first**, as for updates). Synq runs the dry
run again and only replaces when it still finds the number you confirmed; if the site changed in between,
it stops and shows the new count.

</Step>

</Steps>

The replacement covers every table with the site's prefix, handles serialized data, and never touches the
`guid` column (GUIDs identify posts in feeds and must not change). The cache is flushed afterwards.

## Reset an administrator's password

Pick an administrator under **Administrator password** and press **Reset password**. Synq generates a
strong password, sets it with WP-CLI and shows it **once**, in a hidden field with a copy button. It is not
stored or logged anywhere. The user is signed out everywhere and must use the new password.

## PHP settings

| Setting | PHP directive | Range |
| --- | --- | --- |
| Max upload size | `upload_max_filesize` | 1 to 4096 MB |
| Max post size | `post_max_size` | 1 to 4096 MB, at least the upload size |
| Memory limit | `memory_limit` | 64 to 4096 MB |
| Max execution time | `max_execution_time` | 10 to 3600 seconds |
| Max input vars | `max_input_vars` | 1000 to 100000 |

**Save and restart** writes the values to `/usr/local/etc/php/conf.d/zz-synq-php.ini`, a
[file mount](/docs/guides/file-mounts) on the service (it is listed on the **Volumes** tab), and redeploys the
service so the new container reads it. The site is unavailable for a few seconds while its container is
recreated. The file sorts after the image's own PHP files, so its values win.

<Callout title="Uploads through the proxy">
The proxy in front of the site accepts 50 MB per request unless you raise it in the domain's proxy
settings. Raise both for large uploads.
</Callout>

## Audit log

Every change made from the tab is recorded in the [audit log](/docs/guides/audit-log) as **WordPress
changed**, with the operation and its targets (plugin names, versions, URLs, the user whose password was
reset). Passwords and WP-CLI output are never recorded.

## Limits

- Only the official `wordpress` image. Multisite networks work for updates and plugin management; plugin
  activation is per site (network activation isn't offered).
- The WP-CLI container needs to pull `wordpress:cli` from Docker Hub once per server.
- Debug mode and PHP settings are for one service; they don't follow a clone of the site.
- WP-CLI needs a Docker service on one of your servers; on a cloud workspace the tab says it can't run.

## API

The tab uses these service endpoints (see [Services](/docs/api/services)):

| Endpoint | What it does |
| --- | --- |
| `GET /api/projects/:id/services/:serviceId/wordpress` | Status: versions, URLs, plugins, themes, updates, maintenance, debug, PHP settings, backup policies. |
| `POST /api/projects/:id/services/:serviceId/wordpress/actions` | One action: `core.update`, `plugins.update`, `themes.update`, `plugin.activate`, `plugin.deactivate`, `plugin.delete`, `theme.activate`, `cache.flush`, `maintenance.set`, `debug.set`. |
| `POST /api/projects/:id/services/:serviceId/wordpress/search-replace` | Dry run (default) or replace with `dryRun: false` and the confirmed `expectedCount`. |
| `POST /api/projects/:id/services/:serviceId/wordpress/reset-password` | New generated password for an administrator, returned once. |
| `PUT /api/projects/:id/services/:serviceId/wordpress/php` | Save the PHP settings file; redeploy the service to apply it. |
