Skip to content
Back to skills

Laravel Mysql To Postgres

ASecurity

Migrate a Laravel application's database from MySQL/MariaDB to PostgreSQL safely. Use when moving a Laravel app to Postgres, when Eloquent/queries behave differently after switching drivers, or when planning a MySQL->Postgres cutover. Covers the migration strategy (pgloader vs query-builder), the Laravel-specific behavior differences that silently break (case sensitivity, booleans, the id-sequence trap, raw SQL, GROUP BY strictness), and a verification checklist.

  • 7 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 6, 2026
ai-agentsgophpbashsqldatabase

Security analysis

A100/100

Scanned September 6, 2026

npx -y skills add majdghithan/agent-skills --skill laravel-mysql-to-postgres --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Laravel Mysql To Postgres?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Laravel Mysql To Postgres
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majdghithan-laravel-mysql-to-postgres/badge)](https://www.skillsdirectory.com/skills/majdghithan-laravel-mysql-to-postgres)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: laravel-mysql-to-postgres
description: Migrate a Laravel application's database from MySQL/MariaDB to PostgreSQL safely. Use when moving a Laravel app to Postgres, when Eloquent/queries behave differently after switching drivers, or when planning a MySQL->Postgres cutover. Covers the migration strategy (pgloader vs query-builder), the Laravel-specific behavior differences that silently break (case sensitivity, booleans, the id-sequence trap, raw SQL, GROUP BY strictness), and a verification checklist.
---

# Laravel: MySQL -> PostgreSQL migration

The data moves fine. What breaks is the assumptions your app baked in over the years, because **MySQL is forgiving and Postgres is strict**. Every place you leaned on MySQL being lenient is a bug waiting on the other side. Treat a migration as an *audit of your app's lazy assumptions*, not a data transfer.

## Step 0 - The one test that surfaces half the problems

Before touching real data, point `php artisan migrate` at a **fresh empty Postgres database** and watch what throws. If every migration runs clean, your schema is genuinely database-agnostic. If it throws, you just found your MySQL-only assumptions for free.

```bash
# .env pointed at an empty pgsql db
php artisan migrate:fresh   # does the schema even build on Postgres?
```

Do not delete MySQL the day you switch. Keep it running for days/weeks until you're sure - cutovers get reverted.

## Step 1 - Move the data (pick one)

- **`pgloader`** - a single tool built for exactly this. Reads MySQL, writes Postgres, converting types as it goes (`TINYINT(1)` -> `boolean`, `AUTO_INCREMENT` -> sequence). One command, most of the job. **Usually resets sequences for you** (see the trap below).
- **Laravel's query builder** - read from the old connection, write to the new one in PHP, transforming rows. More control, more code; good when the schema has corners pgloader trips on. **Does NOT reset sequences** - you must do it manually.

## The behavior differences that silently break Laravel

### 1. Search stops matching (case sensitivity) - the big one for any app with search
MySQL's default collation is case-INsensitive; Postgres is case-SENSITIVE.
```php
// Worked on MySQL ('iphone' matched 'iPhone'), silently returns nothing on Postgres:
Product::where('name', 'LIKE', "%{$q}%")->get();
// Fix - case-insensitive LIKE:
Product::where('name', 'ILIKE', "%{$q}%")->get();
// Heavy search: the citext extension, or a functional index on LOWER(name).
```
Audit every product search, "find by email", and user-typed filter.

### 2. 0 and 1 are not true/false
MySQL has no real boolean (`TINYINT(1)`); Postgres has a real `boolean`. Laravel casts smooth over most of it, but raw comparisons bite:
```php
DB::table('shops')->whereRaw('is_active = 1')->get();   // PG: "operator does not exist: boolean = integer"
Shop::where('is_active', true)->get();                  // let Eloquent + the 'boolean' cast handle it
```

### 3. The id-sequence trap (the nastiest - passes tests, explodes in prod)
When you bulk-import existing rows, the table's id **sequence does not advance**. Your data has ids up to 40,000; the sequence still thinks the next id is 1. The first insert after go-live collides on a duplicate key - on checkout, in production.
```sql
-- After a data import, bump every table's sequence past its max id:
SELECT setval(pg_get_serial_sequence('orders','id'), (SELECT MAX(id) FROM orders));
-- repeat for every imported table. pgloader usually handles this; a query-builder migration does NOT.
```

### 4. Your raw SQL speaks MySQL
Anywhere you used `DB::raw`, `whereRaw`, `selectRaw`, or a MySQL function, Postgres may not understand it:
- `FIND_IN_SET()`, `GROUP_CONCAT()` -> Postgres arrays / `string_agg()`
- `DATE_FORMAT()` -> `to_char()`
- `RAND()` -> `RANDOM()`
- Backtick quoting `` `col` `` -> double quotes `"col"`

Grep the whole app for `DB::raw|whereRaw|selectRaw` before cutover; each hit is a manual review.

### 5. Postgres won't let sloppy queries slide
- **GROUP BY is strict:** every non-aggregated column in `SELECT` must appear in `GROUP BY`. MySQL guessed; Postgres refuses. Reporting/dashboard queries hide this.
- **No silent truncation:** a string longer than the column throws instead of being trimmed. A bad value in a `uuid` column throws instead of being stored. Postgres is protecting your data, but old code that relied on the trim now errors.

## Verification checklist (before deleting MySQL)
- [ ] `migrate:fresh` runs clean on empty Postgres
- [ ] Every `DB::raw`/`whereRaw`/`selectRaw` reviewed and ported
- [ ] All search/filter code uses `ILIKE` (or `LOWER()`/citext) where case-insensitivity is expected
- [ ] Every imported table's sequence reset (`setval(pg_get_serial_sequence(...))`); create one record per critical table and confirm no duplicate-key error
- [ ] Boolean columns cast to `'boolean'`; no raw `= 1`/`= 0`
- [ ] Reporting/GROUP BY queries run without error
- [ ] Run the full test suite against Postgres; smoke-test the real app end to end
- [ ] Keep MySQL live and revertible until confident

## Why bother
On the other side: `JSONB` with real indexing (great for attribute-heavy catalogs), stricter integrity that catches bugs MySQL swallowed, and query planning that holds up as data grows. Postgres is the strict senior reviewer your codebase never had - it will find things, and that's the point.

## Sources
[Laravel Cloud: Migrate MySQL to PostgreSQL](https://cloud.laravel.com/docs/knowledge-base/migrating-mysql-to-postgresql) · [Flare: migrating with Laravel's query builder](https://flareapp.io/blog/migrating-from-mysql-to-postgres-using-laravels-query-builder) · [pgloader](https://pgloader.io)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.