Author: Madhavendra Dutt
GitHub: https://github.com/mdutt247
Created: 05th March 2026
License: MIT
Stack: Laravel, MySQL, WordPress
Website: MDITech
Production-ready migration script to move WordPress content into Laravel including:
- Users
- Posts
- Categories
- Tags
- Post relationships
- Media attachments
- Comments
- SEO meta (Yoast / RankMath)
- URL redirects
- Chunked + bulk inserts for large sites
Designed for 10k–500k posts migrations.
- Laravel 10+
- PHP 8+
- MySQL
- WordPress database access
Add a second DB connection.
'mysql_wordpress' => [
'driver' => 'mysql',
'host' => env('WP_DB_HOST', '127.0.0.1'),
'port' => env('WP_DB_PORT', '3306'),
'database' => env('WP_DB_DATABASE'),
'username' => env('WP_DB_USERNAME'),
'password' => env('WP_DB_PASSWORD'),
]WP_DB_HOST=127.0.0.1
WP_DB_DATABASE=wordpress
WP_DB_USERNAME=root
WP_DB_PASSWORD=Generate tables:
php artisan make:migration create_categories_table
php artisan make:migration create_tags_table
php artisan make:migration create_posts_table
php artisan make:migration create_post_tag_table
php artisan make:migration create_category_post_table
php artisan make:migration create_media_table
php artisan make:migration create_comments_table
php artisan make:migration create_redirects_tableSchema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
});Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
});Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->string('slug')->unique();
$table->foreignId('user_id')->nullable();
$table->string('seo_title')->nullable();
$table->text('seo_description')->nullable();
$table->string('featured_image')->nullable();
$table->timestamps();
});Schema::create('category_post', function (Blueprint $table) {
$table->foreignId('post_id');
$table->foreignId('category_id');
});Schema::create('post_tag', function (Blueprint $table) {
$table->foreignId('post_id');
$table->foreignId('tag_id');
});Schema::create('media', function (Blueprint $table) {
$table->id();
$table->string('url');
$table->string('title')->nullable();
$table->timestamps();
});Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id');
$table->string('author');
$table->string('email');
$table->text('content');
$table->timestamps();
});Schema::create('redirects', function (Blueprint $table) {
$table->id();
$table->string('old_url');
$table->string('new_url');
});php artisan make:seeder WordpressMigrationSeederuse Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class WordpressMigrationSeeder extends Seeder
{
public function run()
{
// Disable model events for performance
// Model::unsetEventDispatcher();
DB::statement('SET FOREIGN_KEY_CHECKS=0');
$this->users();
$this->categories();
$this->tags();
$this->posts();
$this->relationships();
$this->media();
$this->comments();
$this->redirects();
DB::statement('SET FOREIGN_KEY_CHECKS=1');
}
private function users()
{
DB::connection('mysql_wordpress')
->table('wp_users')
->orderBy('id')
->chunk(200, function ($users) {
$data=[];
foreach ($users as $user) {
$data[]=[
'id'=>$user->ID,
'name'=>$user->display_name,
'email'=>$user->user_email,
'password'=>Hash::make('temporary_password'),
'created_at'=>$user->user_registered
];
}
DB::table('users')->insertOrIgnore($data);
});
}
private function categories()
{
$rows = DB::connection('mysql_wordpress')
->table('wp_terms as t')
->join('wp_term_taxonomy as tt','t.term_id','=','tt.term_id')
->where('taxonomy','category')
->select('t.term_id as id','t.name','t.slug')
->get();
DB::table('categories')->insertOrIgnore(
$rows->map(fn($r)=>[
'id'=>$r->id,
'name'=>$r->name,
'slug'=>$r->slug,
'created_at'=>now(),
'updated_at'=>now()
])->toArray()
);
}
private function tags()
{
$rows = DB::connection('mysql_wordpress')
->table('wp_terms as t')
->join('wp_term_taxonomy as tt','t.term_id','=','tt.term_id')
->where('taxonomy','post_tag')
->select('t.term_id as id','t.name','t.slug')
->get();
DB::table('tags')->insertOrIgnore(
$rows->map(fn($r)=>[
'id'=>$r->id,
'name'=>$r->name,
'slug'=>$r->slug,
'created_at'=>now(),
'updated_at'=>now()
])->toArray()
);
}
private function posts()
{
DB::connection('mysql_wordpress')
->table('wp_posts')
->where('post_type','post')
->where('post_status','publish')
->orderBy('ID')
->chunk(500,function($posts){
$data=[];
foreach($posts as $post){
$seo = DB::connection('mysql_wordpress')
->table('wp_postmeta')
->where('post_id',$post->ID)
->pluck('meta_value','meta_key');
$data[]=[
'id'=>$post->ID,
'title'=>$post->post_title,
'content'=>$post->post_content,
'slug'=>$post->post_name,
'user_id'=>$post->post_author,
'seo_title'=>$seo['_yoast_wpseo_title'] ?? null,
'seo_description'=>$seo['_yoast_wpseo_metadesc'] ?? null,
'created_at'=>$post->post_date,
'updated_at'=>$post->post_modified
];
}
DB::table('posts')->insertOrIgnore($data);
});
}
private function relationships()
{
DB::connection('mysql_wordpress')
->table('wp_term_relationships as tr')
->join('wp_term_taxonomy as tt','tr.term_taxonomy_id','=','tt.term_taxonomy_id')
->orderBy('tr.object_id')
->chunk(1000,function($rows){
$cat=[];
$tag=[];
foreach($rows as $r){
if($r->taxonomy=='category'){
$cat[]=[
'post_id'=>$r->object_id,
'category_id'=>$r->term_id
];
}
if($r->taxonomy=='post_tag'){
$tag[]=[
'post_id'=>$r->object_id,
'tag_id'=>$r->term_id
];
}
}
if($cat) DB::table('category_post')->insertOrIgnore($cat);
if($tag) DB::table('post_tag')->insertOrIgnore($tag);
});
}
private function media()
{
$media = DB::connection('mysql_wordpress')
->table('wp_posts')
->where('post_type','attachment')
->get();
$data=[];
foreach($media as $m){
$data[]=[
'id'=>$m->ID,
'url'=>$m->guid,
'title'=>$m->post_title,
'created_at'=>$m->post_date
];
}
DB::table('media')->insertOrIgnore($data);
}
private function comments()
{
DB::connection('mysql_wordpress')
->table('wp_comments')
->orderBy('comment_id')
->chunk(500,function($rows){
$data=[];
foreach($rows as $c){
$data[]=[
'post_id'=>$c->comment_post_ID,
'author'=>$c->comment_author,
'email'=>$c->comment_author_email,
'content'=>$c->comment_content,
'created_at'=>$c->comment_date
];
}
DB::table('comments')->insertOrIgnore($data);
});
}
private function redirects()
{
$posts = DB::connection('mysql_wordpress')
->table('wp_posts')
->where('post_type','post')
->get();
$data=[];
foreach($posts as $post){
$data[]=[
'old_url'=>'/'.$post->post_name,
'new_url'=>'/blog/'.$post->post_name
];
}
DB::table('redirects')->insertOrIgnore($data);
}
}php artisan migrate
php artisan db:seed --class=WordpressMigrationSeederpublic function categories()
{
return $this->belongsToMany(Category::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}Model::unsetEventDispatcher();Move files:
wp-content/uploadsto
storage/app/public/uploadsphp artisan queue:workWordPress data is mapped into Laravel tables using the following structure.
| WordPress Table | Laravel Table / Structure |
|---|---|
wp_users |
users |
wp_posts |
posts |
wp_terms |
categories, tags |
wp_term_relationships |
category_post, post_tag (pivot tables) |
wp_comments |
comments |
wp_posts (attachments) |
media |