---
title: "SQLite to SQLite migration guide"
description: "Convert a SQLite database or dump into a SQLite script with the same tables, keys, indexes and data: how to dump, convert, load, and every rule the converter applies."
canonical: https://intosql.com/docs/migrations/sqlite-to-sqlite
last_updated: 2026-10-15
---

# SQLite to SQLite migration guide

IntoSQL reads a SQLite database file, or the SQL that SQLite's `.dump` writes, and turns it into one SQLite script with the same tables, columns, keys, indexes, foreign keys, defaults, counters and rows. SQLite keeps any value in any column, so the declared type is a hint: each column gets the SQLite type all of its values fit, and a column holding mixed values keeps their text. Every value SQLite can't hold as it is, and every object it doesn't carry, is listed in the report. The script loads on SQLite 3.31 and later.

**Convert in the browser:** [SQLite to SQLite](/sqlite-to-sqlite) opens the converter set up for this pair.

## The file or a dump

Drop the database file itself (`.db`, `.sqlite`, `.sqlite3`): it is read page by page, in UTF-8 or UTF-16, and text with NUL characters and every digit of a double arrive as stored. Close the app that writes to it first, so no change is left behind in its `-wal` file.

A dump converts too, as `.sql` or `.sql.gz`:

```shell
sqlite3 shop.db .dump > shop.sql
```

- DB Browser for SQLite writes the same SQL with File, Export, Database to SQL file, and so does Python's `iterdump()`.
- `.dump --nosys` converts too. A `--data-only` dump has no `CREATE TABLE`, so its columns are named `column_1`, `column_2` and so on, and the types come from the values.
- Every test file and dump comes from sqlite3 3.31 to 3.54, with `STRICT` and `WITHOUT ROWID` tables, and is compared with its source cell by cell.

## Convert

In the browser, open [SQLite to SQLite](/sqlite-to-sqlite) or the converter on the home page, drop the file and choose SQLite. It reads the file from your disk in pieces, twice, and writes the script as it goes, so the file is never uploaded and its size isn't limited by memory. The [desktop app](/docs/desktop) does the same with the file on disk.

With the [command line](/docs/cli):

```shell
intosql inspect shop.db
intosql convert shop.db --to sqlite --output shop.sqlite.sql --report shop.report.json
```

`inspect` lists the tables, their rows and what won't be converted, without writing anything. `convert` writes the script and, with `--report`, the report as JSON.

Through the [API](/docs/api), a database file or a dump converts like any other file: open a conversion with its name and `"output": {"dialect": "sqlite"}`.

## Load into SQLite

```shell
sqlite3 -bail shop.sqlite < shop.sqlite.sql
```

The script creates each table with its keys, copies the rows and adds indexes and counters, all in one transaction with syncing off, so it runs at disk speed and a load that stops leaves no partial tables. Declared types, `COLLATE NOCASE` and `RTRIM` stay as they are, and `sqlite_sequence` continues where the source left it. `STRICT` is left out, so the script loads on every version from 3.31. The script ends with `PRAGMA foreign_key_check`, which lists any row that points to a missing row instead of failing the load.

### Or load it straight in

`intosql load` and the [desktop app](/docs/desktop) load the dump into SQLite without a script, with the same tables, keys and indexes the script creates:

```shell
intosql load shop.db --into shop.db
```

It stops before writing anything when one of the dump's tables already exists. It loads in one transaction, and a file the load created is removed if it fails. Loading into SQLite is free. See [Loading](/docs/loading#database-dumps).

## What isn't converted

Views and triggers are written in SQLite's own language. They are listed in the report by kind and name, never dropped silently, so you can recreate them in SQLite. Virtual tables, such as FTS5 and R*Tree, are listed too, and their shadow tables are left out.


## Data types

[SQLite data types between versions](/docs/types/sqlite-to-sqlite) lists each type that changes in the script: what it becomes, why, and the report line.

## Every other rule

Generated from the engine, where each rule is decided.

| In the dump | In the script | Why | Report line |
| --- | --- | --- | --- |
| COLLATE NOCASE | COLLATE NOCASE | The same collation. | None |
| generated columns | generated columns, STORED or VIRTUAL as in the source | SQLite computes them the same way. | None |
| generated columns with functions IntoSQL doesn't write, and the \|\| operator | a plain column | A formula that can't be written leaves a plain column and a report line; the dump has no values for generated columns. | `generated_not_translated` |
| defaults with functions IntoSQL doesn't write (strftime, randomblob) | no default | A default that can't be written is left out and reported; CURRENT_TIMESTAMP and datetime('now') are written. | `default_not_translated` |
| CHECK constraints | translated | The same checks, tested as each row loads. | None |
| CHECK constraints with functions IntoSQL doesn't write | not created | A check that can't be written is left out and reported. | `check_skipped` |
| partial and expression indexes | not created | IntoSQL writes indexes on whole columns; create the others again after loading. | `index_skipped` |
| foreign keys (unenforced unless PRAGMA foreign_keys=ON) | the same foreign keys, listed by PRAGMA foreign_key_check | SQLite checks foreign keys only when told to, so rows without a parent are common: they load, and the key is left unchecked with a warning instead of stopping the load. | None |
| foreign keys to tables the dump doesn't create | not created | A key can't name a table that isn't there. | `foreign_key_skipped` |
| primary keys holding NULL, and NOT NULL columns with values that become NULL | NOT NULL dropped | SQLite lets a primary key other than the rowid hold NULL; the column can't be NOT NULL in the target, or the load would stop. | `not_null_relaxed` |
| views, triggers and virtual tables (FTS, R*Tree) | listed in the report | Their code is written in SQLite's own language, and virtual tables need SQLite's modules; they are listed for you to recreate. Their internal tables aren't copied. | `not_converted_objects` |
| dumps without CREATE TABLE (--data-only) | types read from the values | A data-only dump has only rows, so the types come from the values and the columns are numbered. | `no_schema` |
| expressions in INSERT values other than the text functions dumps write | NULL | unistr(), replace(), char() and \|\| are evaluated as the shell writes them; other expressions can't be without a database. | `expression_value` |
| text decoded twice (Ã±) | copied as it is | The dump holds exactly these characters, so they are copied; the report points them out. | `mojibake_suspected` |
