Renaming 1000+ Pages Worth of 'Levels' Without Losing My Mind
Source: Dev.to
The Challenge
A colleague emailed me the morning we pushed changes to the site:
Hi Ice, now that we pushed the changes sa site, we need to scour the site for mentions of Level 1, Level 2, Level 1/2 and change them accordingly to
Level 1 → Intro to Neurofascial Training
Level 1/2 → Intermediate Instability Training
Level 2 → Advanced Neurofascial Training
At first glance it seemed like a simple find‑and‑replace job: open the WordPress admin, use the search feature, fix each page, and be done before lunch.
But the sitemap revealed 1,074 URLs. Even if only a fraction contain the word “Level”, manually clicking through every post, searching for three different strings, and deciding which replacement applies would take at least a full day—and there’s a real risk of missing something.
Mapping the Renames
| Old text | New text |
|---|---|
| Level 1 | Intro to Neurofascial Training |
| Level 1/2 | Intermediate Instability Training |
| Level 2 | Advanced Neurofascial Training |
Important ordering detail: “Level 1/2” must be matched before “Level 1”. A naïve replace would turn “Level 1/2” into “Intro to Neurofascial Training/2”.
Why Plugins Aren’t Enough
Plugins like Better Search Replace operate directly on the database. A single wrong checkbox could overwrite every “Level 1” across post content, post meta, widget text, theme options, and even places that shouldn’t be changed (e.g., analytics or audit logs).
- No preview of where each match lives.
- Overlapping strings mean the order of operations matters, and most plugins don’t let you sequence replacements atomically.
What I really needed was a map—a list of every occurrence with enough surrounding context to judge whether it’s safe to change, plus a direct link to the WordPress editor for that specific page. I would still perform the replacement by hand, but informed by real data.
Building a Custom Scanner
I wrote a Python script that:
- Walks the site’s sitemap.
- Visits each URL.
- Searches the rendered HTML for the three patterns using a single regex with word boundaries.
import re
LEVEL_PATTERN = re.compile(
r"\bLevel\s*1\s*/\s*2\b|\bLevel\s*1\b|\bLevel\s*2\b",
re.IGNORECASE,
)
Word boundaries (\b) prevent false positives such as “Level 10” or “Level 12‑week program”. The alternation order mirrors the mapping problem—“Level 1/2” is tried first so it isn’t shadowed by the simpler “Level 1” pattern.
What the Script Captures
For each match the script records:
- Page URL
- WordPress post ID (extracted from the
class, e.g.,page-id-123orpostid-123) - Direct admin edit link:
/wp-admin/post.php?post=123&action=edit - ≈80 characters of context on either side of the match
- HTML tag wrapping the match (
h2,li,p, etc.) to help locate it in the block editor
All data are dumped to a CSV, sorted, duplicate matches within a page collapsed, and system paths like /wp-admin and /wp-json filtered out.
Benefits of the Approach
Instead of blindly clicking through 1,074 pages, I now have a focused list of every page that mentions “Level” in any form, each with a one‑click link to the editor and enough context to decide which replacement applies.
The context column is the real magic—e.g., a snippet like:
…our flagship Level 1 class is held every Tuesday…
clearly indicates that the class name should become “Intro to Neurofascial Training”, not an incidental use of the word “level”.
Rare edge cases (historical text, testimonials, etc.) are easy to spot and skip, something a blind database replace would mishandle.
The half‑hour spent writing this scanner paid for itself immediately: saved time, confidence that nothing was missed, and an audit trail that protects against accidental corruption.
References
- New XML Sitemaps Functionality in WordPress 5.5 — Make WordPress Core
- Sitemaps XML Protocol — sitemaps.org
body_class()Function Reference — WordPress Developer Resourcesget_body_class()Function Reference — WordPress Developer Resourcesre— Regular expression operations — Python 3 docs- Better Search Replace — WordPress Plugin Directory
- Requests: HTTP for Humans
- Beautiful Soup Documentation