MySQL to PostgreSQL Converter
Paste MySQL DDL or a dump, get PostgreSQL DDL plus a manual-fix checklist. Free and private.
At a glance
MySQL to PostgreSQL Converter is a free, client-side tool that translates MySQL DDL and dump statements into PostgreSQL DDL. It maps types (TINYINT(1) to BOOLEAN, UNSIGNED widening, DATETIME to TIMESTAMP), rewrites AUTO_INCREMENT, indexes, and INSERT patterns, and flags what needs manual work. Nothing is uploaded and no account is required.
100% Client-Side Migration Blueprint
Your schema is converted strictly in local browser memory. Zero uploads, zero servers, zero sign-up.
Fixed weights: table 3, column 0.5, index 1, insert 0.2; gaps: routine 8, fulltext 8, spatial 8, partition 8, prefix index 5, set 3, replace into 3, update/delete limit 2, on-duplicate unknown target 2, on update timestamp 2, virtual generated 2. Under 15 points is S, 15–40 is M, above 40 is L. A planning aid, not a quote.
Run the converted script on a staging database first. FULLTEXT search, spatial columns, partitioning, and stored routines are flagged in the checklist and must be rewritten by hand.
How do I convert MySQL to PostgreSQL?
Paste MySQL DDL or a mysqldump excerpt into the converter above and press Convert to PostgreSQL. The tool rewrites CREATE TABLE statements, translates types and INSERT patterns, strips MySQL-only boilerplate, and returns a PostgreSQL starter script plus a checklist of everything it deliberately did not guess. Nothing is uploaded: the conversion runs entirely in your browser, and the result is ready to review on a staging database.
What MySQL types have no direct PostgreSQL equivalent?
PostgreSQL has no UNSIGNED integers, no MEDIUMINT, and no inline ENUM or SET in CREATE TABLE. A mysqldump is the MySQL text export of schema and data that most migrations start from. This table is the exact type rule set the converter applies; every substitution is also reported as a note in the output.
| MySQL type | PostgreSQL result | Status |
|---|---|---|
| INT UNSIGNED | BIGINT (widened) | Automatic with note |
| BIGINT UNSIGNED | NUMERIC(20,0) | Automatic with note |
| TINYINT(1) | BOOLEAN | Automatic |
| TINYINT(n) / SMALLINT | SMALLINT (UNSIGNED widened to INTEGER) | Automatic with note |
| MEDIUMINT | INTEGER | Automatic with note |
| DECIMAL(p,s) / NUMERIC | NUMERIC(p,s) | Automatic |
| ENUM('a','b') | TEXT + CHECK (col IN (...)) | Automatic with note |
| SET('a','b') | TEXT placeholder (TEXT[], CHECK, or join table) | Manual gap |
| DATETIME | TIMESTAMP | Automatic |
| TIMESTAMP / TIME | TIMESTAMP / TIME | Review (timezone, durations) |
| TINYTEXT / MEDIUMTEXT / LONGTEXT | TEXT | Automatic with note |
| BLOB / BINARY / VARBINARY | BYTEA | Automatic with note |
| JSON | JSONB | Automatic with note |
| YEAR | SMALLINT | Automatic with note |
| AUTO_INCREMENT | GENERATED BY DEFAULT AS IDENTITY | Automatic |
| GEOMETRY / POINT / SPATIAL | PostGIS (rebuild by hand) | Manual gap |
Which MySQL statements convert automatically in a dump?
The converter works statement by statement, so pasting a full mysqldump excerpt is fine. These are the exact rewrite rules applied; anything else is carried over as a comment for manual review.
| MySQL statement or pattern | PostgreSQL result | Status |
|---|---|---|
| `backtick` identifiers | "double-quoted" identifiers | Automatic |
| # comment | -- comment | Automatic |
| LIMIT offset,count | LIMIT count OFFSET offset | Automatic |
| INSERT IGNORE INTO | INSERT ... ON CONFLICT DO NOTHING | Automatic |
| ON DUPLICATE KEY UPDATE | ON CONFLICT (pk) DO UPDATE SET col = EXCLUDED.col | Automatic when the primary key is in the pasted input |
| UNIQUE KEY name (col) | CONSTRAINT "name" UNIQUE ("col") | Automatic |
| KEY name (col) | CREATE INDEX "name" ON table ("col") | Automatic |
| /*!40101 ... */ conditionals | Removed (mysqldump boilerplate) | Removed with note |
| SET NAMES / SQL_MODE / time_zone | Removed | Removed with note |
| LOCK TABLES / UNLOCK TABLES | Removed | Removed with note |
| USE db / CREATE DATABASE | Removed (PostgreSQL connects per database) | Removed with note |
| ENGINE / CHARSET / COLLATE / ROW_FORMAT options | Removed from the CREATE TABLE tail | Removed with note |
| REPLACE INTO | Commented out for manual rewrite | Manual gap |
| UPDATE / DELETE ... LIMIT | Commented out for manual rewrite | Manual gap |
| Stored procedures / functions / triggers / events | Commented out; rewrite in PL/pgSQL | Manual gap |
What must be fixed by hand after the conversion?
These constructs have no safe automatic mapping. The converter flags each one it finds in your input with its exact location, and the same items are collected in the migration effort estimate.
- FULLTEXT indexes → rebuild with tsvector + GIN or pg_trgm
- SPATIAL / geometry columns → PostGIS extension and indexes
- PARTITION BY → rewrite as PostgreSQL declarative partitioning
- Stored routines, triggers, events → rewrite in PL/pgSQL
- col(n) prefix indexes → substring expression index
- SET columns → TEXT[], TEXT + CHECK, or a join table
- REPLACE INTO → ON CONFLICT DO UPDATE or DELETE + INSERT
- ON DUPLICATE KEY UPDATE without a known PK → pick the conflict target
- UPDATE/DELETE ... LIMIT → ctid or key subquery
- ON UPDATE CURRENT_TIMESTAMP → trigger or application logic
- VIRTUAL generated columns → mapped to STORED, verify behavior
- Double-quoted strings in MySQL mode → verify literal vs identifier
How is the migration effort estimate calculated?
The tool counts the converted items and the flagged manual gaps, then applies the fixed published weights below. The result is a t-shirt size with an itemized breakdown: S under 15 points, M 15–40 points, L above 40 points. It is a planning aid for scoping the migration, not a quote.
Base: table 3 · column 0.5 · index 1 · insert 0.2
Manual gaps: routine 8 · fulltext 8 · spatial 8 · partition 8 · prefix index 5
Manual gaps: set column 3 · replace into 3
Manual gaps: update/delete limit 2 · on-duplicate unknown target 2 · on update timestamp 2 · virtual generated 2
MySQL vs PostgreSQL function equivalents
Function calls inside SELECT, WHERE, and other DML are not rewritten automatically (that would require evaluating your queries); use this table when you review converted data statements. DDL statements are rewritten for you; function bodies are not.
| MySQL | PostgreSQL equivalent |
|---|---|
| IFNULL(a, b) | COALESCE(a, b) |
| NOW() / CURDATE() | now() / CURRENT_DATE |
| DATE_FORMAT(d, '%Y-%m-%d') | TO_CHAR(d, 'YYYY-MM-DD') |
| STR_TO_DATE(s, fmt) | TO_DATE / TO_TIMESTAMP(s, fmt) |
| GROUP_CONCAT(x SEPARATOR ',') | STRING_AGG(x, ',') |
| CONCAT(a, b) | CONCAT(a, b) or a || b |
| LOCATE(s, str) | STRPOS(str, s) |
| UNIX_TIMESTAMP() | EXTRACT(EPOCH FROM now()) |
| FROM_UNIXTIME(ts) | TO_TIMESTAMP(ts) |
| RAND() | random() |
Why Use This MySQL to PostgreSQL Converter?
How to Use MySQL to PostgreSQL Converter
Paste MySQL DDL or a dump
Paste CREATE TABLE statements or a mysqldump excerpt (schema or schema+data) into the input box, or load the sample.
Review the PostgreSQL script and checklist
The tool converts statements, lists every type mapping applied, and flags constructs that need manual work with a reason.
Plan with the effort estimate
Use the S/M/L estimate and gap list to scope the remaining manual work before running the script on a staging database.
Worked example: users table with auto-increment and a named unique key
The engine splits the statement in browser memory, widens INT UNSIGNED to BIGINT, rewrites AUTO_INCREMENT as an identity column, maps TINYINT(1) to BOOLEAN and DATETIME to TIMESTAMP, keeps the primary key, turns the named unique key into a UNIQUE constraint, and strips the ENGINE/charset table options with conversion notes; this table has no manual gaps. The input/output pair above matches the engine output.
CREATE TABLE `users` ( `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `email` varchar(255) NOT NULL, `active` tinyint(1) NOT NULL DEFAULT '1', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uq_email` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE "users" (
"id" BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
"email" VARCHAR(255) NOT NULL,
"active" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("id"),
CONSTRAINT "uq_email" UNIQUE ("email")
);
Technical Specifications & Engine Details
| Specification | Details |
|---|---|
| Supported Formats | MYSQL-DDL, MYSQL-DUMP, SQL-TEXT → POSTGRESQL-DDL, POSTGRESQL-SQL, CHECKLIST, ESTIMATE |
| Execution Mode | 100% Client-Side In-Browser (client) |
| Browser Requirements | Modern browser with JavaScript and HTML5 APIs enabled. |
| Performance Constraints | Text-in/text-out converter; no database connection and no execution. Stored procedures, triggers, events, and function bodies are flagged as manual, not rewritten. Complex constructs (partitioning, spatial data) are documented gaps. Output is a reviewed starter migration script, verify before running. |
100% Client-Side Privacy Guarantee
Pasted MySQL schemas and dumps are converted 100% locally in your browser memory. No schema content, data, or credentials embedded in the input are ever sent to any server.
Frequently Asked Questions
Can I convert a MySQL dump to PostgreSQL automatically?
What MySQL types have no direct PostgreSQL equivalent?
Does the converter send my schema to a server?
How is the migration effort estimate calculated?
Methodology
Conversion rules map MySQL types and table options to PostgreSQL equivalents. Review the generated DDL against your schema before running it.
Last updated .