---
title: "Migrate MariaDB to PostgreSQL in your browser"
description: "Migrate a MariaDB database to PostgreSQL: convert its dump into a script with the same tables, keys, sequences and data, and a report of every change."
canonical: https://intosql.com/mariadb-to-postgresql
last_updated: 2026-10-15
---

# Migrate MariaDB to PostgreSQL in your browser

Drop a dump from mariadb-dump, mysqldump, phpMyAdmin or Adminer, as .sql or .sql.gz, and download a script for PostgreSQL with the same tables, keys, indexes and data. Every value PostgreSQL can't hold as it is, and every object it doesn't carry, is listed in the report.

[MariaDB to PostgreSQL migration guide](https://intosql.com/docs/migrations/mariadb-to-postgresql)

[MariaDB to PostgreSQL data types](https://intosql.com/docs/types/mariadb-to-postgresql)

## What changes from MariaDB to PostgreSQL

Each row is a rule the converter applies, with the report line it writes when the rule changes something in your data.

| In MariaDB | In PostgreSQL | Report line |
| --- | --- | --- |
| TINYINT(1), BOOL | boolean when every value is 0 or 1, else smallint | `bool_kept_numeric` |
| UNSIGNED integers | the next wider integer with a CHECK that it isn't negative; BIGINT UNSIGNED is bigint when every value fits, else numeric(20,0) | `unsigned_widened` |
| ZEROFILL and display widths | dropped | `zerofill_dropped` |
| FLOAT(M,D) | real or double precision, without the scale | `float_scale_dropped` |
| BIT(1), BIT(n) | boolean, bit(n) |  |
| zero, partial or impossible dates (0000-00-00, 2024-02-30) | NULL | `invalid_date_null` |
| NOT NULL columns with values that become NULL | NOT NULL dropped | `not_null_relaxed` |
| DEFAULT '0000-00-00' | no default | `zero_date_default` |
| TIME | time when every value is within a day, else interval | `time_as_interval` |
| YEAR | smallint with a CHECK of its range |  |
| CHAR(n) | varchar(n) |  |
| DATETIME | timestamp |  |
| TIMESTAMP | timestamptz in the time zone the dump states, UTC when it states none | `timestamp_zone_assumed` |
| ENUM | text with a CHECK of its values |  |
| ENUM holding '' (MySQL's error value) | the empty value allowed by the check | `enum_invalid_value` |
| ENUM or SET holding values outside its list | no CHECK | `enum_check_skipped` |
| SET | text[] with a CHECK that every element is in the list |  |
| JSON | jsonb; json when a value holds \u0000 | `json_not_jsonb` |
| NUL bytes in text | removed | `nul_removed` |
| text that isn't valid in its character set | the bad bytes as the replacement character | `invalid_utf8_replaced` |
| text decoded twice (Ã±) | copied as it is | `mojibake_suspected` |
| AUTO_INCREMENT | GENERATED BY DEFAULT AS IDENTITY, with the counter set past the copied ids |  |
| MyISAM AUTO_INCREMENT on the second column of a key | one counter for the table | `myisam_group_autoincrement` |
| ON UPDATE CURRENT_TIMESTAMP | a BEFORE UPDATE trigger | `on_update_trigger` |
| generated columns | STORED | `virtual_as_stored` |
| generated columns with functions PostgreSQL lacks | a plain column | `generated_not_translated` |
| INVISIBLE columns | regular columns | `invisible_column` |
| CHECK constraints | translated, inline in CREATE TABLE |  |
| CHECK constraints with functions PostgreSQL lacks | not created | `check_skipped` |
| CHECK ... NOT ENFORCED | not created | `check_not_enforced` |
| defaults with functions PostgreSQL lacks | no default | `default_not_translated` |
| keys and indexes on a prefix, c(20) | an index on left(c, 20) | `prefix_unique` |
| FULLTEXT indexes | GIN on to_tsvector('simple', ...) | `fulltext_as_gin` |
| indexes on values over 2,000 bytes | hash | `index_on_wide_values` |
| SPATIAL indexes | not created without PostGIS | `spatial_index_skipped` |
| functional indexes PostgreSQL can't write | not created | `index_skipped` |
| foreign keys | after the data, NOT VALID, then validated with a warning on orphans |  |
| foreign keys to columns without a unique key | not created | `foreign_key_skipped` |
| table and column names | lowercase when that stays unique | `identifier_renamed` |
| names over 63 bytes | shortened with a hash | `name_shortened` |
| GEOMETRY, POINT, POLYGON... | WKB in bytea | `geometry_as_wkb` |
| INET4, INET6, UUID (MariaDB) | inet, uuid |  |
| BINARY, VARBINARY, BLOB | bytea |  |
| PARTITION BY | one table | `partitions_dropped` |
| WITH SYSTEM VERSIONING (MariaDB) | a table with the current rows | `system_versioning_dropped` |
| MyISAM, MEMORY and other engines | a regular table | `engine_dropped` |
| views, triggers, procedures, functions and events | listed in the report | `not_converted_objects` |
| dumps without CREATE TABLE | types read from the values | `no_schema` |
| geometries in a dump without CREATE TABLE | WKB in bytea | `geometry_inferred` |
| _ci collations | exact comparison | `case_insensitive_collation` |
| SET NAMES utf8 from utf8mb4 tables | converted, with a warning | `dump_lost_four_byte_chars` |
| dumps without SET NAMES whose text isn't UTF-8 | read as Latin-1 (Windows-1252) | `charset_guessed` |
| sjis, cp932, gbk, big5 dumps | read as UTF-8, with a warning | `unsupported_charset` |
| UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows | evaluated |  |
| other expressions in rows | NULL | `expression_value` |
| DEFAULT in VALUES | the column's default value | `default_keyword_filled` |
| DEFAULT in VALUES without a literal default | NULL | `default_keyword_null` |
| REPLACE and INSERT IGNORE | plain rows | `replace_as_insert` |
| types IntoSQL doesn't know | text | `unknown_type` |
| values that don't fit their column | NULL | `invalid_value_null` |
| unreadable geometries | NULL | `bad_geometry` |
| CREATE SEQUENCE (MariaDB) | CREATE SEQUENCE with its current value |  |
| VECTOR(n) (MariaDB 11.7+) | jsonb with the floats as an array | `type_replaced` |

## Load the script into PostgreSQL

The script creates the tables, copies the rows, then adds keys, indexes and foreign keys, so it loads fast and stops at the first error.

```shell
psql -v ON_ERROR_STOP=1 -d mydb -f mariadb.postgresql.sql
```

Foreign keys are added as NOT VALID and then validated. If a row points to a row that doesn't exist, the script tells you which constraint, instead of failing half way.

For a large dump, create the database first and run the script with psql from the same machine as the server.

IntoSQL Pro loads the file straight into PostgreSQL from the desktop app or the command line, without writing a script.

## Dump MariaDB for the best result

The converter reads what the dump holds. These options keep more of your data in it:

- --default-character-set=utf8mb4 keeps emoji and other 4-byte characters, which a utf8mb3 dump loses before the converter sees it.
- --hex-blob writes binary columns and VECTOR values as hexadecimal, which survives copying between systems.
- --routines --triggers --events add those objects to the dump, so the report can list them.

## What isn't converted

Views, triggers, stored procedures, functions and events are written in MariaDB's own language. They are listed in the report by name, never dropped silently, so you can rewrite them for PostgreSQL.

## Questions about MariaDB to PostgreSQL

### Is my dump uploaded?

No. The converter runs in your browser and reads the dump from your disk in pieces, so it never leaves your computer. The desktop app and the command line convert on your computer too.

### How large can the dump be?

The converter never holds the whole dump in memory: it reads the file twice, by ranges, and writes the script as it goes. Multi-gigabyte dumps convert in the browser, in the desktop app and with the command line.

### Which MariaDB versions can the dump come from?

Dumps from mariadb-dump and mysqldump on MariaDB 10.3 to 12, with one INSERT per table or one per row. Every test dump comes from MariaDB 10.11, 11.4 and 11.8 and is compared with its source cell by cell.

### Are UUID, INET6, JSON and VECTOR columns kept?

UUID becomes uuid, INET4 and INET6 inet, JSON jsonb, and VECTOR a jsonb array of its floats. Zero dates become NULL and the report counts them.

### Are AUTO_INCREMENT values and sequences kept?

Yes. Each AUTO_INCREMENT column becomes an identity column that continues after the highest id. Sequences become PostgreSQL sequences, and defaults that draw from one keep drawing from it.

### What about stored procedures and triggers?

They are listed in the report by name and not converted, because each database writes them in its own language.

### Which PostgreSQL versions does the script load on?

PostgreSQL 13 to 18. The same script loads on each of them, stopping at the first error if anything goes wrong.

## From MariaDB

- [MariaDB to MySQL](https://intosql.com/mariadb-to-mysql)
- [MariaDB to MariaDB](https://intosql.com/mariadb-to-mariadb)
- [MariaDB to SQLite](https://intosql.com/mariadb-to-sqlite)
- [MariaDB to SQL Server](https://intosql.com/mariadb-to-sql-server)
- [MariaDB to Oracle](https://intosql.com/mariadb-to-oracle)

## Into PostgreSQL

- [MySQL to PostgreSQL](https://intosql.com/mysql-to-postgresql)
- [PostgreSQL to PostgreSQL](https://intosql.com/postgresql-to-postgresql)
- [SQL Server to PostgreSQL](https://intosql.com/sql-server-to-postgresql)
- [SQLite to PostgreSQL](https://intosql.com/sqlite-to-postgresql)
- [Oracle to PostgreSQL](https://intosql.com/oracle-to-postgresql)

## Guide and every pair

- [PostgreSQL to MariaDB](https://intosql.com/postgresql-to-mariadb)
- [MariaDB to PostgreSQL migration guide](https://intosql.com/docs/migrations/mariadb-to-postgresql)
- [MariaDB to PostgreSQL data types](https://intosql.com/docs/types/mariadb-to-postgresql)
- [Every database pair](https://intosql.com/database-migration)
