View on GitHub

merge-firefox-history

Script designed to merge the browsing history from two Firefox profile databases.

merge-firefox-history

Merge the browsing history of two Firefox profiles by combining their places.sqlite databases. The target profile keeps its own history and gains the history from the other profile; the source database is never modified. Current Firefox releases and older profiles are supported.

Requirements

Usage

./merge-firefox-history.sh <db1> <db2> [backup_location] [options]

<db1> is the target database and receives the history from <db2>. <db2> is never modified.

Close Firefox before using a database as the target. A timestamped backup of <db1> is created before anything is changed.

Example

./merge-firefox-history.sh \
  ~/.mozilla/firefox/profile1.default-release/places.sqlite \
  ~/.mozilla/firefox/profile2.default-release/places.sqlite

This adds the history from profile2 to profile1 and writes a timestamped backup of profile1/places.sqlite to the current directory.

Options

Option Description
-b, --backup-dir DIR Directory for the backup of <db1> (default: current directory).
-o, --output FILE Write the result to FILE instead of modifying <db1>. <db1> is only read.
-n, --dry-run Do the merge on a temporary copy and discard it.
-s, --skip-vacuum Do not VACUUM the database (faster on large profiles).
-y, --yes Do not ask for confirmation.
-f, --force Overwrite an existing --output file.
--keep-temp Keep the temporary working directory (for debugging).
-h, --help Show help.

The third positional argument is a backwards-compatible alias for --backup-dir.

Merge into a new file

To leave both originals untouched, write the result to a separate file:

./merge-firefox-history.sh \
  ~/.mozilla/firefox/profile1.default-release/places.sqlite \
  ~/.mozilla/firefox/profile2.default-release/places.sqlite \
  --output ~/merged-places.sqlite

Then replace the profile’s places.sqlite with the merged file while Firefox is closed, keeping a copy of the original.

Preview the result

Use --dry-run to perform the merge on a temporary copy and report how many places and visits would be added.

Where is places.sqlite?

In Firefox, open about:support and use Profile Directory → Open Directory. The database is the file named places.sqlite in that directory.

Firefox stores profiles in:

Safety

Both inputs are snapshotted with SQLite’s online backup API before use, so the snapshot includes committed WAL contents. The source is opened read-only and is never modified. There is no unsafe file-copy fallback: if a snapshot cannot be taken, the run fails.

Before anything is changed, the target database is written to a timestamped backup. If the merge, a VACUUM, or the final verification fails, the backup is restored automatically. The generated merge statements share one transaction; the optional URL-hash update and VACUUM run afterwards and are covered by the same restore-on-failure path.

Filling url_hash is best-effort. If python3 is unavailable, or hashing fails, the merge still succeeds with a warning and Firefox calculates the missing hashes during idle maintenance. Use --skip-vacuum when compaction is not wanted or cannot be completed.

A database that another process, usually Firefox, has open is never modified in place. --output and --dry-run work from a consistent snapshot instead. The script also refuses --output paths that refer to either input database, even through a hard link. An output file is written only after the merged database has passed PRAGMA quick_check and the foreign-key check; a failed run leaves an existing output file untouched.

PRAGMA foreign_key_check is compared with the target’s pre-merge baseline. Only violations introduced by the merge are fatal; pre-existing ones are reported but do not block the merge. Foreign-key checks cannot validate Places relationships that a particular schema does not declare as constraints.

Keep in mind that only one profile can use a given places.sqlite at a time, so close Firefox before swapping files.

Output

Progress is written to stderr and appended to merge.log in the current directory. Set the MERGE_LOG environment variable to use another file.

[2026-10-20 23:04:03] merge-firefox-history: target='.../profile1/places.sqlite' source='.../profile2/places.sqlite'
[2026-10-20 23:04:03] Backup created: ./places.sqlite.backup_20261020_230403
[2026-10-20 23:04:07] Merge completed successfully.
[2026-10-20 23:04:07] Places added: 101628 (now 203148)
[2026-10-20 23:04:07] Visits added: 215507 (now 442654; skipped as duplicate or unmapped: 0)

Troubleshooting

What is not merged

Favicons live in favicons.sqlite, a separate database, and bookmarks, keywords, annotations, passwords, tabs, add-ons and preferences are all outside the scope of a history merge. Sync metadata is also left alone, because merging it between two profiles could cause duplicated uploads or misleading statistics.

History is matched by URL, so metadata that exists in both profiles (for example a title changed in one profile) is not reconciled. Pages imported from profiles older than the moz_origins schema may have no origin assigned; Firefox still displays them, only origin-based frecency is affected.

This is not a Sync replacement. See How it works for the merged tables, deduplication rules, and schema compatibility details.

License

This script is released under the ISC License.