Laravel on Postgres after MySQL: paste your error
Paste the error (the whole SQLSTATE line is fine). Matching entries open below. Everything runs in your browser.
duplicate key value violates unique constraint "users_pkey" SQLSTATE 23505
You copied rows from MySQL with their ids, but the Postgres sequence behind each id column still starts at 1. The first new insert reuses id 1, 2, 3... If your ids have gaps, it fails some inserts and not others, so it looks random.
Tested on Postgres 17.11: after the setval, the next insert gets MAX(id)+1. Running it again is safe. If the key column is named differently, change 'id'.
Full tested fix (scripts for every table, Laravel command, checks): Error Fix Pack ($19), item 01.
column "users.name" must appear in the GROUP BY clause or be used in an aggregate function SQLSTATE 42803
MySQL (with ONLY_FULL_GROUP_BY off) lets you select columns that are not grouped and returns a value from an arbitrary row. Postgres refuses. Grouping by the table's primary key is allowed, so groupBy('users.id') with users.* works.
Worth knowing: the old MySQL query may have been returning the wrong row the whole time. In our test, select * ... group by user_id on non-strict MariaDB 11.8 returned the oldest row, not the latest.
Full tested fix (scripts for every table, Laravel command, checks): Error Fix Pack ($19), item 03.
value "3000000000" is out of range for type integer SQLSTATE 22003
Postgres has no unsigned integers. Laravel's unsignedInteger() and increments() become a plain integer on pgsql, so the ceiling drops from 4,294,967,295 to 2,147,483,647. Values above that loaded fine on MySQL and fail on Postgres.
Tested with Laravel 13 on MariaDB 11.8 + Postgres 17: inserting 3000000000 works on MariaDB and fails on Postgres with SQLSTATE[22003]; after ALTER ... TYPE bigint it works.
Full tested fix (scripts for every table, Laravel command, checks): Error Fix Pack ($19), item 06.
operator does not exist: boolean = integer SQLSTATE 42883
tinyint(1) columns usually become boolean on Postgres. MySQL compares them to 1/0 freely; Postgres does not when the 1 is written into the SQL text (whereRaw, DB::select with a literal, raw joins).
Tested with pdo_pgsql: a bound 1 is accepted for a boolean column; only a literal 1 in the SQL fails.
Full tested fix (scripts for every table, Laravel command, checks): Error Fix Pack ($19), item 04.
operator does not exist: character varying = integer SQLSTATE 42883
A string column (phone, code, zip, external_id) is compared with a bare number in raw SQL. MySQL casts silently; Postgres won't guess.
Tested with pdo_pgsql: a bound value works; only the literal number fails.
date/time field value out of range: "0000-00-00 00:00:00" SQLSTATE 22008
MySQL stores zero dates ('0000-00-00') when strict mode is off. Postgres has no such date, so the copy or the insert fails.
Tested on MariaDB 11.8. Also remove DEFAULT '0000-00-00' from the schema, or new rows bring the problem back.
Full tested fix (scripts for every table, Laravel command, checks): Error Fix Pack ($19), item 05.
column "a@x.com" does not exist SQLSTATE 42703
If the 'column' in the error is actually a value (an email, a status word), your raw SQL uses double quotes for a string. MySQL treats "text" as a string; Postgres treats it as a column name.
If the name really is a column, check its case: Postgres folds unquoted names to lower case, so a quoted "userId" and userid are different.
syntax error at or near "`" SQLSTATE 42601
Backticks are MySQL-only identifier quotes. Laravel's query builder quotes for you, so these come from raw SQL: DB::statement, whereRaw, selectRaw, old migrations.
Tested on Postgres 17. Backticks next to a comma or keyword can also show up as: syntax error at or near "," or near "from". Search the codebase for ` in .php files.
function ifnull(text, unknown) does not exist SQLSTATE 42883
A MySQL-only function in raw SQL. Common ones and their Postgres versions are below.
COALESCE, string_agg and to_char tested on Postgres 17.11.
invalid byte sequence for encoding "UTF8": 0x00 SQLSTATE 22021
Some MySQL text values contain NUL bytes (often from binary data or bad imports). Postgres text can't store them, so the load fails.
Silent variant: through PHP's pdo_pgsql (PHP 8.4, Postgres 17), a bound value 'a\0b' was stored as 'a' with no error. Everything after the NUL is lost. Check before you copy. Tested on MariaDB 11.8 and Postgres 17.
current transaction is aborted, commands ignored until end of transaction block SQLSTATE 25P02
This is a symptom. An earlier statement in the same transaction failed; MySQL would carry on, but Postgres refuses every statement until rollback. Code that catches an exception and keeps using the same transaction hits this.
Tested on Postgres 17: after a failing statement, the next one in the block returns 25P02.
(no error) search or login finds nothing: WHERE email LIKE ... returns 0 rows
Not an error, which makes it worse. MySQL's _ci collations make = and LIKE case-insensitive. Postgres is case-sensitive, so Name@Example.com no longer matches name@example.com.
Tested on Postgres 17.11. Also check unique indexes: two rows differing only in case were blocked on MySQL and are allowed on Postgres.
Not moved yet? Check before you switch: the free schema risk check (paste SHOW CREATE TABLE) and the free read-only data checker (finds the rows that will fail). Planning the whole move: Migration Kit ($49, $34.30 with LAUNCH30 until Nov 5).
Each message above was reproduced on Postgres 17.11 (and the MySQL side on MariaDB 11.8) before it was written down. Error text is from Postgres 17; older versions word a few of them slightly differently.