Don’t sleep on Acorn’s support for running Laravel migrations in WordPress, it’s a great tool to use
Also, using LLMs to help generate them makes creating these a fairly quick process. If you’re using a CLI like Claude Code, have it run wp
commands, scan plugin files, and run wp db
queries to help with making migrations. A couple recent migrations I’ve done:
Rename blocks
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// Update all post_content that contains oldnamespace/ block references
$blockMappings = [
'oldnamespace/block-name' => 'newnamespace/block-name',
'oldnamespace/block-name-2' => 'newnamespace/block-name-2',
];
foreach ($blockMappings as $oldBlock => $newBlock) {
// Convert block namespace: oldnamespace/block-name -> newnamespace/block-name
$oldBlockSlug = str_replace('/', '-', $oldBlock);
$newBlockSlug = str_replace('/', '-', $newBlock);
// Update HTML comment format
DB::table('posts')
->where('post_content', 'like', "%<!-- wp:$oldBlock %")
->update([
'post_content' => DB::raw("REPLACE(post_content, '<!-- wp:$oldBlock', '<!-- wp:$newBlock')"),
'post_modified' => now(),
'post_modified_gmt' => now(),
]);
// Update closing comments
DB::table('posts')
->where('post_content', 'like', "%/wp:$oldBlock -->%")
->update([
'post_content' => DB::raw("REPLACE(post_content, '/wp:$oldBlock -->', '/wp:$newBlock -->')"),
'post_modified' => now(),
'post_modified_gmt' => now(),
]);
}
// Update CSS classes: wp-block-oldnamespace- -> wp-block-newnamespace-
DB::table('posts')
->where('post_content', 'like', '%wp-block-oldnamespace-%')
->update([
'post_content' => DB::raw("REPLACE(post_content, 'wp-block-oldnamespace-', 'wp-block-newnamespace-')"),
'post_modified' => now(),
'post_modified_gmt' => now(),
]);
// Log the migration
\Log::info('Migrated Gutenberg blocks from oldnamespace/ to newnamespace/ namespace', [
'blocks_updated' => array_keys($blockMappings),
'migration_time' => now()
]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// Reverse the migration - change newnamespace/ back to oldnamespace/
$blockMappings = [
'newnamespace/block-name' => 'oldnamespace/block-name',
'newnamespace/block-name-2' => 'oldnamespace/block-name-2',
];
foreach ($blockMappings as $newBlock => $oldBlock) {
DB::table('posts')
->where('post_content', 'like', '%"blockName":"' . $newBlock . '"%')
->orWhere('post_content', 'like', "%<!-- wp:$newBlock %")
->update([
'post_content' => DB::raw("REPLACE(post_content, '\"blockName\":\"$newBlock\"', '\"blockName\":\"$oldBlock\"')"),
'post_modified' => now(),
'post_modified_gmt' => now(),
]);
// Also update HTML comment format
DB::table('posts')
->where('post_content', 'like', "%<!-- wp:$newBlock %")
->update([
'post_content' => DB::raw("REPLACE(post_content, '<!-- wp:$newBlock', '<!-- wp:$oldBlock')"),
'post_modified' => now(),
'post_modified_gmt' => now(),
]);
// Update closing comments
DB::table('posts')
->where('post_content', 'like', "%/wp:$newBlock -->%")
->update([
'post_content' => DB::raw("REPLACE(post_content, '/wp:$newBlock -->', '/wp:$oldBlock -->')"),
'post_modified' => now(),
'post_modified_gmt' => now(),
]);
}
\Log::info('Rolled back Gutenberg blocks from newnamespace/ to oldnamespace/ namespace');
}
};
Delete redirects from Redirection plugin that have had 0 hits
I absolutely loathe this plugin, but a site I work on has a lot of legacy redirects using it still. I’m going to end up creating a migration to move things to Safe Redirect Manager, but in the meantime…
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Get count of redirects with 0 hits before deletion for logging
$countBeforeDeletion = DB::table('wp_redirection_items')->where('last_count', 0)->count();
// Log the operation
if ($countBeforeDeletion > 0) {
\Log::info("Deleting {$countBeforeDeletion} redirects with 0 hits from wp_redirection_items table");
// Delete redirects with 0 hits
$deletedCount = DB::table('wp_redirection_items')->where('last_count', 0)->delete();
\Log::info("Successfully deleted {$deletedCount} redirects with 0 hits");
} else {
\Log::info("No redirects with 0 hits found to delete");
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// This migration cannot be reversed as we're deleting data
// The redirects would need to be restored from a backup
\Log::warning("Migration 'delete_redirects_with_no_hits' cannot be reversed - data has been permanently deleted");
}
};