How I Restructured My Arabic Learning Site’s URLs Without Losing SEO (Using WP‑CLI & Redirection)
A few weeks ago I faced a classic content‑site dilemma.
My website, Lucid Arabic, had around 80 of beginner vocabulary lessons with clean but flat URLs like:
https://lucidarabic.com/how-to-say-star-in-arabic
I knew I wanted to expand into intermediate and advanced content, grammar lessons, and exercises. The flat structure just wouldn’t scale. I needed a hierarchy that would make sense for learners and search engines alike:
https://lucidarabic.com/beginner/vocabulary/how-to-say-star-in-arabic
The goal was clear, but the path was full of potential SEO landmines. After a lot of planning (and a successful test on a local site), I pulled it off with zero broken links and zero ranking drops. Here’s exactly how I did it, including the WP‑CLI command that saved me hours and the critical step most people overlook.
The Challenge
- Old URL pattern:
/%postname%/(e.g.,/how-to-say-star-in-arabic/) - New URL pattern:
/beginner/vocabulary/%postname%/ - 80+ published posts that needed to move seamlessly
- No room for 404 errors – existing traffic and search rankings had to be preserved
I needed a method that would:
- Automatically assign the correct category to all existing posts
- Generate the new hierarchical URLs on the fly
- Create precise 301 redirects from every old URL to its new counterpart
- Update the XML sitemap so Google would see the new structure immediately
WordPress makes steps 1 and 2 easy if you’ve cleaned up your categories first. Step 3 is where WP‑CLI turned a tedious task into a 30‑second job.
Step 1: Create the New Category Hierarchy
First, I set up the container categories in Posts → Categories:
- beginner (slug:
beginner) - vocabulary (slug:
vocabulary) – with beginner as its parent
This gives the path beginner/vocabulary. I made sure no other posts were using these slugs to avoid conflicts.
Step 2: The Critical Clean‑up – Assign ONLY the Vocabulary Category
“If you change the permalink structure while posts still have other categories assigned, those posts will immediately take on URLs that include those old category slugs (e.g., /uncategorized/something/, or /some-other-category/something/). That would create a mess before you even get to the redirect step.”
This is the step many tutorials skip. WordPress uses the category with the lowest ID (usually the first one created) when building the /%category%/ permalink, unless you use a plugin to choose the primary category. To avoid any guesswork, I made sure every post I wanted to move was assigned only to the Vocabulary category and no other category could hijack the URL.
How I did it efficiently:
- Went to Posts → All Posts
- Selected all posts (using the checkbox at the top, then “All X posts” link if needed)
- Chose Edit from the Bulk actions dropdown and clicked Apply
- In the bulk edit panel, under Categories, I ticked only the “vocabulary” category and un‑ticked any other categories (like “Uncategorized”)
- Hit Update
That guaranteed every single post had exactly one category assignment. No leftovers, no surprise slugs.
For sites with thousands of posts, you could use a database query or a plugin, but the built‑in bulk edit handled my 80+ posts in seconds.
Step 3: Switch the Permalink Structure
With categories now clean, I could safely change the permalink setting.
- Went to Settings → Permalinks
- Selected Custom Structure and entered:
/%category%/%postname%/ - Clicked Save Changes
Instantly, all my post URLs became https://lucidarabic.com/beginner/vocabulary/slug/. The old short URLs started returning 404 errors – completely expected, and completely fixable in the next step.
Step 4: Generate 301 Redirects in Seconds with WP‑CLI
wp post list --post_type=post --post_status=publish --format=csv --fields=post_name | tail -n +2 | awk -F, '{print "/" $1 "/,/beginner/vocabulary/" $1 "/"}' > redirects.csv
It looks dense, but it’s just a chain of three simple commands connected by pipes (|). I’ll walk through each component.
0. Prerequisites
- WP‑CLI must be installed on your server (or local environment). Most managed WordPress hosts already have it. You can check by typing
wp --infoin your terminal. - You need SSH access to your server, or you can run it on a local copy of your site (which is safer, and exactly what I did first).
- Navigate to your WordPress root directory (where
wp-config.phplives) before running the command.
1. wp post list – Extracting the Raw Data
wp post list --post_type=post --post_status=publish --format=csv --fields=post_name
This is the WP‑CLI part. It queries the WordPress database for posts and outputs specific information.
| Flag | Meaning |
|---|---|
--post_type=post | Only include standard blog posts (exclude pages, attachments, etc.). |
--post_status=publish | Only include published items (no drafts, trashed, or private). |
--format=csv | Output the results as a plain CSV table. |
--fields=post_name | Only retrieve the post_name field – this is the URL slug. |
Example output of this sub‑command alone:
post_name
say-flower-in-arabic
how-to-say-star-in-arabic
arabic-word-for-tree
...
The first line is the column header post_name. We need to remove it, which leads to the next command.
2. tail -n +2 – Removing the Header
tail -n +2
tail normally outputs the last lines of a file or input stream. With the -n +2 option, it starts outputting from the second line onward. This effectively drops the first line (the header).
After this step, the stream becomes:
say-flower-in-arabic
how-to-say-star-in-arabic
arabic-word-for-tree
...
Now we have a clean list of slugs – one per line.
3. awk – Transforming Each Slug into a CSV Line
awk -F, '{print "/" $1 "/,/beginner/vocabulary/" $1 "/"}'
awk is a powerful text processor. Let’s unpack this instruction:
-F,sets the field separator to a comma. Although our input has no commas (each line is just a slug), specifying a separator is good practice. Here,$1represents the entire line because the line contains no comma to split it.'{print ... }'is the action performed on every line.- The print statement constructs the exact CSV line we need:
"/"adds a leading slash before the slug.$1inserts the slug itself."/,/beginner/vocabulary/"adds the comma (CSV delimiter) and the new path prefix.$1inserts the slug again, after the prefix."/"adds the trailing slash.
So, for the input say-flower-in-arabic, the output becomes:
/say-flower-in-arabic/,/beginner/vocabulary/say-flower-in-arabic/
And every line is transformed identically.
4. > redirects.csv – Saving to a File
The > operator redirects the final output (from awk) into a new file called redirects.csv in the current directory. If the file already exists, it’s overwritten. If you prefer to append, you’d use >>.
Putting It All Together – A Visual Flow
[WP Database]
|
v
wp post list … => post_name\nsay-flower-in-arabic\nhow-to-say-star-in-arabic\n…
|
v
tail -n +2 => say-flower-in-arabic\nhow-to-say-star-in-arabic\n…
|
v
awk … => /say-flower-in-arabic/,/beginner/vocabulary/say-flower-in-arabic/\n
/how-to-say-star-in-arabic/,/beginner/vocabulary/how-to-say-star-in-arabic/\n
…
|
v
> redirects.csv => (file saved on disk)
What the Final CSV Looks Like
Opening redirects.csv in a text editor, you’ll see lines exactly like:
/say-flower-in-arabic/,/beginner/vocabulary/say-flower-in-arabic/
/arabic-word-for-tree/,/beginner/vocabulary/arabic-word-for-tree/
/how-to-say-star-in-arabic/,/beginner/vocabulary/how-to-say-star-in-arabic/
/sun-in-arabic/,/beginner/vocabulary/sun-in-arabic/
…
Important characteristics:
- No header row (already stripped).
- Every line is a valid source/target pair.
- Both source and target paths are relative and end with a trailing slash – exactly what the Redirection plugin expects.
- Character encoding is preserved. Slugs that contain percent‑encoded Arabic characters (like
%d8%b4) remain untouched, ensuring the redirect matches the actual URL the browser requests.
Why This Command is So Powerful
- Zero manual editing: You don’t touch a single URL. The script reads your actual database slugs and builds every mapping automatically.
- Scalable: Whether you have 10 posts or 10,000, the command runs in the same amount of time (a few seconds).
- No copy‑paste errors: Manual CSV creation is prone to missing slashes, typos, or duplicate lines. This eliminates human error.
- Reusable: If you later add an “intermediate/vocabulary” level, you can run the same command with a slightly modified
awkpattern.
Alternatives If You Don’t Have WP‑CLI
If SSH access or WP‑CLI isn’t available, you can achieve the same result with:
- SQL export via phpMyAdmin:
SELECT CONCAT('/', post_name, '/') AS source,
CONCAT('/beginner/vocabulary/', post_name, '/') AS target
FROM wp_posts
WHERE post_type = 'post' AND post_status = 'publish' AND post_name != '';
Then export the results as a CSV without headers.
- Free export plugin (e.g., WP All Export):
Export only thepost_namefield twice, then use spreadsheet formulas to build the source and target columns.
But WP‑CLI is by far the fastest, and if your host supports it, I highly recommend using it.
Running It Safely
I can’t stress enough: run this command on a local/staging copy first. That’s what I did. I cloned my production site to my local machine using Local by Flywheel, executed the WP‑CLI command there, verified the CSV, and then repeated the process on the live server. This gave me absolute confidence that nothing would break.
After generating the file, I opened it with a plain‑text editor (not Excel, which can alter encodings), checked the first few and last few lines, and then imported it straight into the Redirection plugin.
The whole process – from running the command to seeing the redirect rules live – took less than five minutes. That single line of code turned what could have been a tedious, error‑prone chore into an automated, reliable solution. It’s now my go‑to technique for any bulk URL restructuring in WordPress.
Step 5: Import the CSV into the Redirection Plugin
Back in the WordPress admin:
- Installed and activated the free Redirection plugin
- Went to Tools → Redirection → Import/Export
- Uploaded the CSV, mapped Column 1 to Source URL and Column 2 to Target URL
- Clicked Import
All 80+ redirects were instantly created as 301 (Permanent) rules. I tested a few old URLs in a private browsing window – each one cleanly forwarded to the new hierarchical URL with no lag and no intermediate page.
Step 6: Update the Sitemap (Don’t Forget This!)
WordPress generated new URLs, but my XML sitemap (managed by an SEO plugin) still contained the old ones. I needed search engines to discover the new structure quickly.
I went to my SEO plugin’s settings (I use Rank Math, but Yoast has the same feature) and found the Sitemap Settings. I simply clicked Save Changes or triggered a manual sitemap rebuild. The plugin re‑scanned my posts and updated the sitemap with the new /beginner/vocabulary/ URLs.
I also submitted the updated sitemap to Google Search Console for good measure.
Bonus: My Safety Net – Local Testing & Backup
Before touching the live site, I cloned my entire production site to a local environment (using Local by Flywheel). There I ran through every step – category cleanup, permalink change, WP‑CLI redirect generation, and the Redirection import. I could verify that:
- The new URLs resolved correctly
- The old URLs redirected without a hitch
- No weird 404s appeared
- My theme and plugins played nicely with the hierarchical permalinks
Once I was confident, I backed up my production site (both files and database). With the backup securely stored, I repeated the steps on the live server. The whole live migration took about 10 minutes, and because the redirects were already mapped in the CSV, there was zero downtime.
Key Takeaways
- Clean up category assignments before changing permalinks. If you don’t, you’ll end up with unpredictable URLs.
- WP‑CLI is your best friend for bulk redirect generation. A single command can save hours of manual CSV editing.
- The Redirection plugin makes redirect management painless – and its CSV import works flawlessly.
- Always test on a local or staging site first, and have a full backup before making structural changes to a live site.
- Update your sitemap immediately so search engines index the new URLs and pass ranking signals through the redirects.
The result? My Arabic learning site now has a clean, scalable URL structure ready for intermediate and advanced content. Old links still work perfectly, and search engine rankings held steady.
If you’re facing a similar URL restructuring, don’t fear the change – just plan carefully, let automation do the heavy lifting, and always protect yourself with a backup. And if you have access to the command line, give that WP‑CLI one‑liner a try – you’ll be amazed at how fast it works.
Have you restructured your WordPress URLs? I’d love to hear about your experience – and any other clever WP‑CLI tricks you’ve used – in the comments below.

