# Recipes: WordPress

By the end of this page you have an event site on WordPress with real
content, a menu, a footer, a site icon, images and a co-organizer, all from
the terminal or through your agent. The commands were tested on the
production multisite with a subsite administrator account, same as yours.

How it works, in one line: `rapadura wp <site> -- <wp-cli command>`.
Everything after `--` goes to `wp-cli`, already pinned to your site and your
user. What is allowed is in [WordPress sites](wordpress.md#what-is-allowed).

## 1. Create the site

```sh
rapadura wp create php-sobral-2026 --blueprint event --title "PHP Sobral 2026" --email you@example.com
```

```text
PHP Sobral 2026 is live at https://php-sobral-2026.rapadura.net (WordPress, blueprint event).
site: https://php-sobral-2026.rapadura.net
  wp-admin: https://php-sobral-2026.rapadura.net/wp-admin/
  REST: https://php-sobral-2026.rapadura.net/wp-json/wp/v2/
  WordPress login: yourlogin
  application password (shown once): xxxx xxxx xxxx xxxx xxxx xxxx
  note: Shown once. It signs your agent in to the WordPress REST API as you (HTTP basic auth). Application passwords belong to your WordPress account, not to one site: it works on every site of the network where you have a role, and has no permissions anywhere else. Keep it like a token; revoke it in wp-admin › Users › Profile if it leaks.
  next: Edit content with wp-cli: rapadura wp php-sobral-2026.rapadura.net -- post list --post_type=page
  next: Or use the REST API at https://php-sobral-2026.rapadura.net/wp-json/wp/v2/ with HTTP basic auth: yourlogin and the application password.
  next: To log into https://php-sobral-2026.rapadura.net/wp-admin/ in a browser, set a password with: rapadura wp password php-sobral-2026.rapadura.net
```

Keep the **application password**: it is shown once and it is what gives
access to the REST API (step 8). Lost it? `rapadura wp credentials
php-sobral-2026` mints another. `--email` is only needed the first time.

Blueprints: `blank` (empty), `event` (home, schedule, speakers, venue,
registration), `meetup` (next meetup, past editions, give a talk) and
`course` (about, syllabus, enroll). `rapadura wp blueprints` lists them. The
blueprint pages are in Portuguese, with placeholder text.

```prompt
Create a WordPress site on rapadura called php-sobral-2026 with the event blueprint and the title "PHP Sobral 2026". Keep the application password in this session only, never in a file, and tell me the address and the wp-admin link.
```

## 2. See what came with it

```sh
rapadura wp php-sobral-2026 -- post list --post_type=page --fields=ID,post_title,post_name
```

```text
ID	post_title	post_name
6	Local	local
7	Inscrições	inscricoes
5	Palestrantes	palestrantes
4	Programação	programacao
3	Início	inicio
```

The blueprint pages come with placeholder text. The blueprint has also set
up the top menu (step 5) and the footer (step 6), and deleted the "Sample
Page" and "Hello world!" post WordPress puts on every new site.

The `blank` blueprint does none of this: the site stays as WordPress made
it. To delete the two samples on a `blank` site:

```sh
rapadura wp php-sobral-2026 -- post delete 2 1 --force
```

## 3. Replace a page's content with your own file

Write the content on your computer. With block markup
(`<!-- wp:... -->`) the editor opens each part as an editable block; plain
HTML works too, as a "Classic" block.

```html
<!-- programacao.html -->
<!-- wp:heading -->
<h2 class="wp-block-heading">Saturday, November 14</h2>
<!-- /wp:heading -->

<!-- wp:table -->
<figure class="wp-block-table"><table><tbody>
<tr><td>09:00</td><td>Check-in</td></tr>
<tr><td>09:30</td><td>Opening</td></tr>
<tr><td>10:00</td><td>PHP 8.5 in practice</td></tr>
</tbody></table></figure>
<!-- /wp:table -->
```

```sh
ID=$(rapadura wp php-sobral-2026 -- post list --post_type=page --name=programacao --field=ID)
rapadura wp php-sobral-2026 -- post update "$ID" --post_content="$(cat programacao.html)"
```

```text
Success: Updated post 4.
```

The content travels **inside the command**, hence the `$(cat ...)`. Passing
the file name (`post update 4 programacao.html`) does not work: `wp-cli` runs
on the server and would look for the file there, and rapadura refuses it.
For very large pages (above roughly 100 KB), use the REST API (step 9).

## 4. Create a new page

```sh
rapadura wp php-sobral-2026 -- post create --post_type=page --post_status=publish \
  --post_title="Sponsors" --post_name=sponsors \
  --post_content="<!-- wp:paragraph --><p>Want to support us? Write to contato@phpcomrapadura.org.</p><!-- /wp:paragraph -->" \
  --porcelain
```

```text
11
```

`--porcelain` prints only the new page's ID, handy for scripts and agents.

## 5. Top menu

The network's theme is **Twenty Twenty-Five**, a block theme. Menus made
with `wp menu` **do not show up** in it: the header uses a navigation block,
which reads the site's most recent navigation post.

The blueprint has already created that post, "Menu principal", with a link
to every page except the home page, labeled with the page titles in
blueprint order. To change items, order or labels, update the post with the
whole list, one `navigation-link` per item:

```sh
MENU=$(rapadura wp php-sobral-2026 -- post list --post_type=wp_navigation --field=ID)
rapadura wp php-sobral-2026 -- post update "$MENU" \
  --post_content='<!-- wp:navigation-link {"label":"Schedule","url":"/programacao/"} /--><!-- wp:navigation-link {"label":"Speakers","url":"/palestrantes/"} /--><!-- wp:navigation-link {"label":"Venue","url":"/local/"} /--><!-- wp:navigation-link {"label":"Register","url":"/inscricoes/"} /-->'
```

```text
Success: Updated post 8.
```

A `blank` site has no such post yet. Create it with the same content:

```sh
rapadura wp php-sobral-2026 -- post create --post_type=wp_navigation --post_status=publish \
  --post_title="Main menu" --post_content='<!-- wp:navigation-link {"label":"Schedule","url":"/programacao/"} /-->' --porcelain
```

## 6. Footer

The theme's default footer has sample links (Blog, About, FAQs, Shop...).
The blueprint has already replaced it with one line carrying the site title,
in Portuguese like the rest of the blueprint: "PHP Sobral 2026 · uma
realização da comunidade PHP Com Rapadura". It is your own version of the
theme's "footer" part, a `wp_template_part` post. To change the text,
update that post:

```sh
ID=$(rapadura wp php-sobral-2026 -- post list --post_type=wp_template_part --name=footer --field=ID)
rapadura wp php-sobral-2026 -- post update "$ID" \
  --post_content='<!-- wp:group {"layout":{"type":"constrained"}} --><div class="wp-block-group"><!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center">PHP Sobral 2026 · organized by the <a href="https://phpcomrapadura.org">PHP Com Rapadura</a> community</p><!-- /wp:paragraph --></div><!-- /wp:group -->'
rapadura wp php-sobral-2026 -- cache flush
```

```text
Success: Updated post 9.
Success: The cache was flushed.
```

On a `blank` site the footer is still the theme's. Create your own version
and tie it to the theme and to the footer area; without the two
`post term set` lines WordPress ignores the post:

```sh
ID=$(rapadura wp php-sobral-2026 -- post create --post_type=wp_template_part --post_status=publish \
  --post_name=footer --post_title=Footer --porcelain \
  --post_content='<!-- wp:group {"layout":{"type":"constrained"}} --><div class="wp-block-group"><!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center">PHP Sobral 2026 · organized by the <a href="https://phpcomrapadura.org">PHP Com Rapadura</a> community</p><!-- /wp:paragraph --></div><!-- /wp:group -->')
rapadura wp php-sobral-2026 -- post term set "$ID" wp_theme twentytwentyfive
rapadura wp php-sobral-2026 -- post term set "$ID" wp_template_part_area footer
rapadura wp php-sobral-2026 -- cache flush
```

To go back to the theme's original footer, on any site, delete the post
(`post delete "$ID" --force`).

## 7. Title, tagline and icon

```sh
rapadura wp php-sobral-2026 -- option update blogname "PHP Sobral 2026"
rapadura wp php-sobral-2026 -- option update blogdescription "The PHP meetup of the Ceará backlands"
ICON=$(rapadura wp php-sobral-2026 -- media import https://www.php.net/images/logos/new-php-logo.png --title="Logo" --porcelain)
rapadura wp php-sobral-2026 -- option update site_icon "$ICON"
```

`media import` takes **http(s) addresses**: the server downloads the image.
For a file on your computer, use the REST API (next step).

## 8. Images from your computer (REST API)

With the login and the application password from step 1:

```sh
export WP_SITE=https://php-sobral-2026.rapadura.net
export WP_AUTH="yourlogin:xxxx xxxx xxxx xxxx xxxx xxxx"

curl -s -u "$WP_AUTH" \
  -H "Content-Disposition: attachment; filename=palco.png" \
  -H "Content-Type: image/png" \
  --data-binary @palco.png \
  "$WP_SITE/wp-json/wp/v2/media" | jq '{id, source_url}'
```

```text
{
  "id": 15,
  "source_url": "https://php-sobral-2026.rapadura.net/wp-content/uploads/sites/7/2026/10/palco.png"
}
```

To put the image on a page, use `source_url` in an image block:

```html
<!-- wp:image --><figure class="wp-block-image"><img src="https://php-sobral-2026.rapadura.net/wp-content/uploads/sites/7/2026/10/palco.png" alt="The PHP Sobral stage"/></figure><!-- /wp:image -->
```

Upload space per site is 100 MB.

## 9. Pages through the REST API

The same application password creates and edits pages. Good for agents that
prefer HTTP, and for large content.

```sh
# list
curl -s -u "$WP_AUTH" "$WP_SITE/wp-json/wp/v2/pages?per_page=50&_fields=id,slug,title"

# create
curl -s -u "$WP_AUTH" -H "Content-Type: application/json" \
  -d '{"title":"Code of conduct","slug":"conduct","status":"publish","content":"<!-- wp:paragraph --><p>Respect above all.</p><!-- /wp:paragraph -->"}' \
  "$WP_SITE/wp-json/wp/v2/pages" | jq '{id, link}'
```

```text
{
  "id": 16,
  "link": "https://php-sobral-2026.rapadura.net/conduct/"
}
```

```sh
# update page 16
curl -s -u "$WP_AUTH" -H "Content-Type: application/json" \
  -d '{"status":"draft"}' "$WP_SITE/wp-json/wp/v2/pages/16" | jq '{id, status}'
```

The password belongs to your WordPress account, not to this one site: it
works on every site of the network where you have a role (your own sites)
and has no permissions anywhere else. The full reference is the
[WordPress REST API](https://developer.wordpress.org/rest-api/reference/).

## 10. Bring in a co-organizer

```sh
rapadura wp php-sobral-2026 -- user create maria maria@example.com --role=editor
```

```text
Success: Created user 6.
Password: t7Qx2LmV9rBz1sPw4KcE
```

The password is shown once; send it to the person over a safe channel. The
login takes lowercase letters and digits only. Roles: `editor` (all
content), `author` (their own posts), `administrator` (the whole site,
appearance included). To change it later: `user set-role maria author`.

## 11. Draft, unpublish, delete

```sh
rapadura wp php-sobral-2026 -- post update 16 --post_status=draft   # offline, kept
rapadura wp php-sobral-2026 -- post delete 16                       # to the trash
rapadura wp php-sobral-2026 -- post delete 16 --force               # gone for good
```

## 12. Back up the content

```sh
rapadura wp php-sobral-2026 -- export --stdout > php-sobral-2026.xml
```

Always `--stdout`: writing the file on the server (`--dir`) is refused.

The file is a WXR with pages, posts, menus and references to the images. To
restore it on another site: activate the importer
(`plugin activate wordpress-importer`) and use **Tools › Import** in
wp-admin.

## 13. Replace a string across the site

Always with `--dry-run` first:

```sh
rapadura wp php-sobral-2026 -- search-replace "2025" "2026" --dry-run
rapadura wp php-sobral-2026 -- search-replace "2025" "2026"
```

rapadura always limits `search-replace` to your site's tables
(`wp_7_posts`, `wp_7_options`...). The output lists each table and how many
replacements it got.

## 14. For agents: JSON output

With `--json` before the `--`, the answer separates output, errors and exit
status:

```sh
rapadura --json wp php-sobral-2026 -- option get blogname
```

```text
{
  "host": "php-sobral-2026.rapadura.net",
  "args": [
    "option",
    "get",
    "blogname"
  ],
  "stdout": "PHP Sobral 2026\n",
  "stderr": "",
  "exit_code": 0,
  "duration": "1.2s"
}
```

Over MCP, the `wp_cli` tool takes the same arguments as a list:
`["option", "get", "blogname"]`.

## What you cannot do, and what to do instead

| I want to | what happens | do this |
|---|---|---|
| install a plugin or theme | refused | ask the administrator; today only Twenty Twenty-Five is enabled |
| `media import ./photo.jpg` | refused: the path would be read on the server | REST API (step 8) or a URL |
| `post create page.html` | refused, same reason | `--post_content="$(cat page.html)"` |
| `wp menu create` | works, but the theme does not show it | a `wp_navigation` post (step 5) |
| change my password or credentials | `user update` is refused | `rapadura wp password` / `rapadura wp credentials` |
| touch another site or the network | refused | only your own site is reachable |

## Ready-made prompts

```prompt
On the php-sobral-2026 WordPress site on rapadura, replace the placeholder text of every blueprint page with real content about PHP Sobral 2026 (November 14, Sobral, Ceará). Use WordPress block markup and show me the final list of pages.
```

```prompt
On php-sobral-2026, relabel the top menu in English (Schedule, Speakers, Venue, Register), add a link to the Sponsors page, and change the footer to say "organized by the PHP Com Rapadura community". Check by opening the home page.
```

```prompt
Upload the photos in ./photos to the php-sobral-2026 media library through the REST API, using the application password I will paste, and create a "Gallery" page with all of them.
```

```prompt
Back up the content of php-sobral-2026 to php-sobral-2026.xml and tell me how many pages and images it has.
```
