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

# Migrate MySQL to SQLite in your browser

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

[MySQL to SQLite migration guide](https://intosql.com/docs/migrations/mysql-to-sqlite)

[MySQL to SQLite data types](https://intosql.com/docs/types/mysql-to-sqlite)

## What changes from MySQL to SQLite

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

| In MySQL | In SQLite | Report line |
| --- | --- | --- |
| TINYINT(1), BOOL | INTEGER with the values as they are |  |
| UNSIGNED integers | INTEGER with a CHECK that it isn't negative; BIGINT UNSIGNED holding values above 2^63-1 is TEXT | `unsigned_widened` |
| ZEROFILL and display widths | dropped | `zerofill_dropped` |
| FLOAT(M,D) | REAL, without the scale | `float_scale_dropped` |
| DECIMAL(p,s) | DECIMAL(p,s) up to 15 digits; TEXT beyond | `decimal_as_text` |
| BIT(1), BIT(n) | INTEGER with the bits' value; BIT(64) as TEXT digits |  |
| 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 text as MySQL writes it, durations included |  |
| YEAR | INTEGER with a CHECK of its range |  |
| CHAR(n), VARCHAR(n) | VARCHAR(n), not enforced |  |
| DATETIME | DATETIME as ISO 8601 text |  |
| TIMESTAMP | TIMESTAMP as ISO 8601 text in UTC; the 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 holding values outside its list | no CHECK | `enum_check_skipped` |
| SET | TEXT as MySQL writes it ('a,b') |  |
| JSON | TEXT |  |
| NUL bytes in text | kept |  |
| 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 | INTEGER PRIMARY KEY AUTOINCREMENT, with the counter set past the copied ids |  |
| AUTO_INCREMENT on part of a key or outside one | INTEGER without a counter | `identity_without_counter` |
| ON UPDATE CURRENT_TIMESTAMP | an AFTER UPDATE trigger | `on_update_trigger` |
| generated columns | GENERATED ALWAYS AS, VIRTUAL or STORED as in the source |  |
| generated columns with functions SQLite lacks | a plain column | `generated_not_translated` |
| INVISIBLE columns | regular columns | `invisible_column` |
| CHECK constraints | translated, inline in CREATE TABLE |  |
| CHECK constraints with functions SQLite lacks | not created | `check_skipped` |
| CHECK ... NOT ENFORCED | not created | `check_not_enforced` |
| defaults with functions SQLite lacks | no default | `default_not_translated` |
| keys and indexes on a prefix, c(20) | an index on substr(c, 1, 20) | `prefix_unique` |
| FULLTEXT indexes | not created | `fulltext_not_created` |
| SPATIAL indexes | not created | `spatial_index_not_created` |
| functional indexes SQLite can't write | not created | `index_skipped` |
| foreign keys | in CREATE TABLE, loaded unchecked, then listed by PRAGMA foreign_key_check |  |
| foreign keys to columns without a unique key | not created | `foreign_key_skipped` |
| table, index and column names | as written; renamed when they differ only in case or an index name repeats | `identifier_renamed` |
| GEOMETRY, POINT, POLYGON... | WKB in a BLOB | `geometry_as_blob` |
| INET4, INET6, UUID (MariaDB) | TEXT |  |
| BINARY, VARBINARY, BLOB | BLOB |  |
| 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` |
| dumps of several databases | one file, each table named after its database first | `databases_merged` |
| 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 a BLOB | `geometry_as_blob` |
| _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) | not created | `sequence_not_converted` |
| VECTOR(n) (MariaDB 11.7+) | TEXT with the floats as a JSON array | `type_replaced` |

## Load the script into SQLite

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
sqlite3 -bail mydb.sqlite < mysql.sqlite.sql
```

Foreign keys are declared in each table and the rows load with foreign key checks off. The script then lists any row that points to a row that doesn't exist, instead of failing half way.

For a large dump, load into a new file on a local disk. The script loads everything in one transaction with syncing off, so it runs at disk speed, and a load that stops half way leaves no partial tables.

The command line loads the file straight into a SQLite database, without writing a script. It's free.

## Dump MySQL 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 utf8 dump loses before the converter sees it.
- --hex-blob writes binary columns 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 each database's own language. They are listed in the report by name, never dropped silently, so you can rewrite them for SQLite.

## Questions about MySQL to SQLite

### 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.

### Does it read MariaDB dumps?

Yes. Dumps from mariadb-dump and from MariaDB 10.3 to 12 convert the same way, including MariaDB's INET6, INET4 and UUID columns.

### What happens to 0000-00-00 dates?

They become NULL: SQLite would store the text, but nothing reads 0000-00-00 as a date. The report says how many there were in each column.

### Are AUTO_INCREMENT values kept?

Yes. An AUTO_INCREMENT column that is the table's whole primary key becomes INTEGER PRIMARY KEY AUTOINCREMENT, every row keeps its id, and the next id continues after the highest one. Any other AUTO_INCREMENT column keeps its values without a counter, and the report says so.

### Does TINYINT(1) become boolean?

No. SQLite has no boolean type, so TINYINT(1) is an INTEGER and every value stays as it is.

### 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. ON UPDATE CURRENT_TIMESTAMP is the exception: it becomes a SQLite trigger.

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

SQLite 3.31 and later. Every test dump loads on 3.31 and 3.54, stopping at the first error if anything goes wrong.

## From MySQL

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

## Into SQLite

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

## Guide and every pair

- [SQLite to MySQL](https://intosql.com/sqlite-to-mysql)
- [MySQL to SQLite migration guide](https://intosql.com/docs/migrations/mysql-to-sqlite)
- [MySQL to SQLite data types](https://intosql.com/docs/types/mysql-to-sqlite)
- [Every database pair](https://intosql.com/database-migration)
