Skip to content

Instantly share code, notes, and snippets.

@mdutt247
Last active March 5, 2026 18:35
Show Gist options
  • Select an option

  • Save mdutt247/709c10a2a0aca3b00c26656a0a7b3677 to your computer and use it in GitHub Desktop.

Select an option

Save mdutt247/709c10a2a0aca3b00c26656a0a7b3677 to your computer and use it in GitHub Desktop.
Migrate WordPress to Laravel

WordPress → Laravel Migration (Full Seeder + Relationships + SEO + Media)


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 PHP WordPress

Requirements

  • Laravel 10+
  • PHP 8+
  • MySQL
  • WordPress database access

1. WordPress Database Connection

Add a second DB connection.

config/database.php

'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'),
]

.env

WP_DB_HOST=127.0.0.1
WP_DB_DATABASE=wordpress
WP_DB_USERNAME=root
WP_DB_PASSWORD=

2. Laravel Migrations

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_table

Categories

Schema::create('categories', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('slug')->unique();
    $table->timestamps();
});

Tags

Schema::create('tags', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('slug')->unique();
    $table->timestamps();
});

Posts

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();
});

Pivot Tables

category_post

Schema::create('category_post', function (Blueprint $table) {
    $table->foreignId('post_id');
    $table->foreignId('category_id');
});

post_tag

Schema::create('post_tag', function (Blueprint $table) {
    $table->foreignId('post_id');
    $table->foreignId('tag_id');
});

Media

Schema::create('media', function (Blueprint $table) {
    $table->id();
    $table->string('url');
    $table->string('title')->nullable();
    $table->timestamps();
});

Comments

Schema::create('comments', function (Blueprint $table) {
    $table->id();
    $table->foreignId('post_id');
    $table->string('author');
    $table->string('email');
    $table->text('content');
    $table->timestamps();
});

Redirects

Schema::create('redirects', function (Blueprint $table) {
    $table->id();
    $table->string('old_url');
    $table->string('new_url');
});

3. Create Seeder

php artisan make:seeder WordpressMigrationSeeder

4. Full Migration Seeder

use 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);
    }
}

5. Run Migration

php artisan migrate
php artisan db:seed --class=WordpressMigrationSeeder

6. Model Relationships

Post.php

public function categories()
{
    return $this->belongsToMany(Category::class);
}

public function tags()
{
    return $this->belongsToMany(Tag::class);
}

public function comments()
{
    return $this->hasMany(Comment::class);
}

7. Additional Migration Tips

Disable Model Events

Model::unsetEventDispatcher();

Copy Uploads

Move files:

wp-content/uploads

to

storage/app/public/uploads

Use Queue for Huge Sites

php artisan queue:work

Migration Architecture

WordPress data is mapped into Laravel tables using the following structure.

WordPress → Laravel Table Mapping

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment