Operand

can sell console.
← Chronicle

Dump your WordPress.

Phase one in ending a long and arduous relationship.

So, you're managing WordPress?
That's a popular thing to be doing!
How long have you had this running?

Oh, how rude of me to ask; probably much longer than you'd like to admit.

Here is how you can quickly back up a WordPress domain - focusing on the content of the pages, rather than the themes & style.

Throughout this guide we'll be making use of the WordPress REST API. Our code samples are prepared using Nushell.


Discover the API.

WordPress relies on a discovery mechanism to communicate each site's public API to one another.

In this example, we use wordpress.org - the same code applies, in theory, to any wordpress domain.

def "wp base" [domain: string] {
  http get --full $domain |
  get headers.response |
  where name == link |
  get value.0 |
  parse '<{link}>; rel="https://api.w.org/"' |
  get link.0
}

let base = wp base https://wordpress.org | tee { print }

Solid! We have a base URL.

Using the reference, we see that /wp/v2/posts is the path we need to use.

Depending on how your WordPress has been managed, other nice places to look are:

  • /wp/v2/pages
  • /wp/v2/tags
  • /wp/v2/categories
  • /wp/v2/comments
  • /wp/v2/media
  • /wp/v2/users

Again, see the reference for a more thorough appraisal.

To help us progress here, let's make a couple quick helper functions:

def "wp dump" [base: string, path: string, --form (-f): string = yml] {
  let node = [. ($base | url parse | get host) $"($path).($form)" ] |
    path join | path expand
  mkdir ($node | path dirname)
  try { http get ([ $base wp v2 $path ] | path join) | save -f $node }
}

Ready? Here goes.

let base = wp base 'https://wordpress.org'

timeit { [
 posts pages comments media users
 categories tags taxonomies types statuses
 settings themes search plugins
 block-types blocks block-renderer block-directory/search
] | each {|label| wp dump $base $label } }

# for a colleague's domain:
# 21sec 895ms 964µs 817ns

Of course, if you prefer a non-yml dump, pass in one of these extensions under -f:

➜ scope commands
    | where name starts-with "to "
    | insert extension { get name | str replace -r "^to " "" | $"*.($in)" }
    | select extension name
    | rename extension command

╭────┬────────────┬─────────────╮
│  # │ extension  │   command   │
├────┼────────────┼─────────────┤
│  0 │ *.csv      │ to csv      │
│  1 │ *.html     │ to html     │
│  2 │ *.json     │ to json     │
│  3 │ *.md       │ to md       │
│  4 │ *.msgpack  │ to msgpack  │
│  5 │ *.msgpackz │ to msgpackz │
│  6 │ *.nuon     │ to nuon     │
│  7 │ *.text     │ to text     │
│  8 │ *.toml     │ to toml     │
│  9 │ *.tsv      │ to tsv      │
│ 10 │ *.xml      │ to xml      │
│ 11 │ *.yaml     │ to yaml     │
│ 12 │ *.yml      │ to yml      │
╰────┴────────────┴─────────────╯

For completeness, here is a multi-format dump:

let base = wp base 'https://wordpress.org'

let labels = [
 posts pages comments media users
 categories tags taxonomies types statuses
 settings themes search plugins
 block-types blocks block-renderer block-directory/search
]

let forms = [ csv json yml ]

timeit { $labels | each {|l| $forms | each {|f|
  wp dump -f $f $base $l
} } }

Yeah! Such a nice backup already.

➜ tree wordpress.org
wordpress.org
├── block-directory
├── blocks.csv
├── blocks.json
├── blocks.yaml
├── blocks.yml
├── categories.json
├── categories.yaml
├── categories.yml
├── comments.json
├── comments.yaml
├── comments.yml
├── media.json
├── media.yaml
├── media.yml
├── pages.json
├── pages.yaml
├── pages.yml
├── posts.json
├── posts.yaml
├── posts.yml
├── search.json
├── search.yaml
├── search.yml
├── statuses.json
├── statuses.yaml
├── statuses.yml
├── tags.json
├── tags.yaml
├── tags.yml
├── taxonomies.json
├── taxonomies.yaml
├── taxonomies.yml
├── types.json
├── types.yaml
├── types.yml
├── users.json
├── users.yaml
└── users.yml

If you have a local copy of zip, yay!

zip -r wordpress.zip wordpress.org/*

Since I'm on NixOS I'm going to use a small prefix here:

nix-shell -p zip --run 'zip -r wordpress.zip wordpress.org/*'

Now, I'm going through this process to help a colleague, so this is a good place to email off the ZIP file, to see if she has any issues with how the records came back.

I'm eager to press ahead and look at a few newer CMS options for her! A couple ones I already have my eye on:

Plus, the Awesome SelfHosted page has 40 more options for a CMS upgrade - of course, including WordPress.

← Chronicle