Skip to content

Instantly share code, notes, and snippets.

@bojanrajkovic
Created May 26, 2026 00:46
Show Gist options
  • Select an option

  • Save bojanrajkovic/bf3cef8dbc6f360d03030eecfc9d79ea to your computer and use it in GitHub Desktop.

Select an option

Save bojanrajkovic/bf3cef8dbc6f360d03030eecfc9d79ea to your computer and use it in GitHub Desktop.
Copy Chrome localStorage entries from one origin to another (rewrites LevelDB directly)
#!/usr/bin/env node
// Copies Chrome localStorage entries from one origin to another.
// Chrome must be closed before running.
//
// Usage:
// node migrate.js <old-origin> <new-origin> [chrome-profile-path]
//
// Examples:
// node migrate.js https://old.example.com https://new.example.com
// node migrate.js https://old.example.com https://new.example.com "/path/to/Chrome/Default"
'use strict';
const { Level } = require('level');
const fs = require('fs');
const path = require('path');
const os = require('os');
function getDefaultChromePaths() {
const home = os.homedir();
switch (process.platform) {
case 'darwin':
return [
path.join(home, 'Library/Application Support/Google/Chrome/Default'),
path.join(home, 'Library/Application Support/Chromium/Default'),
path.join(home, 'Library/Application Support/Google/Chrome Beta/Default'),
];
case 'win32':
return [
path.join(process.env.LOCALAPPDATA || '', 'Google/Chrome/User Data/Default'),
path.join(process.env.LOCALAPPDATA || '', 'Chromium/User Data/Default'),
path.join(process.env.LOCALAPPDATA || '', 'Google/Chrome Beta/User Data/Default'),
];
default: // Linux and other POSIX
return [
path.join(home, '.config/google-chrome/Default'),
path.join(home, '.config/chromium/Default'),
path.join(home, '.config/google-chrome-beta/Default'),
path.join(home, 'snap/chromium/common/chromium/Default'),
];
}
}
function findDbPath(profilePath) {
const candidates = profilePath
? [profilePath]
: getDefaultChromePaths();
for (const p of candidates) {
const dbPath = path.join(p, 'Local Storage/leveldb');
if (fs.existsSync(dbPath)) return dbPath;
}
return null;
}
async function migrate(oldOrigin, newOrigin, dbPath) {
console.log(`DB: ${dbPath}`);
console.log(`From: ${oldOrigin}`);
console.log(`To: ${newOrigin}\n`);
// Warn if LOCK file suggests Chrome might be open
const lockFile = path.join(dbPath, 'LOCK');
if (fs.existsSync(lockFile) && fs.statSync(lockFile).size > 0) {
console.warn('WARNING: LOCK file is non-empty — Chrome may still be running. Proceed at your own risk.\n');
}
const db = new Level(dbPath, { keyEncoding: 'buffer', valueEncoding: 'buffer' });
await db.open();
const oldDataPrefix = Buffer.from(`_${oldOrigin}\x00\x01`);
const oldMetaKey = Buffer.from(`META:${oldOrigin}`);
const oldAccessKey = Buffer.from(`METAACCESS:${oldOrigin}`);
const toWrite = [];
const found = [];
for await (const [key, value] of db.iterator()) {
const keyBin = key.toString('binary');
if (key.equals(oldMetaKey)) {
toWrite.push({ type: 'put', key: Buffer.from(`META:${newOrigin}`), value });
found.push(`META:${newOrigin}`);
} else if (key.equals(oldAccessKey)) {
toWrite.push({ type: 'put', key: Buffer.from(`METAACCESS:${newOrigin}`), value });
found.push(`METAACCESS:${newOrigin}`);
} else if (keyBin.startsWith(oldDataPrefix.toString('binary'))) {
const suffix = keyBin.slice(oldDataPrefix.toString('binary').length);
toWrite.push({ type: 'put', key: Buffer.from(`_${newOrigin}\x00\x01${suffix}`), value });
found.push(` ${suffix}`);
}
}
if (toWrite.length === 0) {
console.log(`No localStorage entries found for ${oldOrigin}`);
await db.close();
return;
}
console.log(`Found ${toWrite.length} entries:`);
for (const k of found) console.log(` ${k}`);
await db.batch(toWrite);
console.log(`\nWrote ${toWrite.length} entries to ${newOrigin}`);
await db.close();
}
// ── main ──────────────────────────────────────────────────────────────────────
const [,, oldOrigin, newOrigin, profileArg] = process.argv;
if (!oldOrigin || !newOrigin) {
console.error('Usage: node migrate.js <old-origin> <new-origin> [chrome-profile-path]');
console.error('Example: node migrate.js https://old.example.com https://new.example.com');
process.exit(1);
}
const dbPath = findDbPath(profileArg);
if (!dbPath) {
console.error('Could not find Chrome localStorage directory. Pass it explicitly as the third argument.');
process.exit(1);
}
migrate(oldOrigin, newOrigin, dbPath).catch(err => {
console.error(err.message || err);
process.exit(1);
});
{
"name": "chrome-localstorage-origin-migrate",
"version": "1.0.0",
"description": "Copy Chrome localStorage entries from one origin to another by rewriting the underlying LevelDB",
"main": "migrate.js",
"bin": {
"chrome-ls-migrate": "migrate.js"
},
"scripts": {
"start": "node migrate.js"
},
"keywords": ["chrome", "localstorage", "leveldb", "migrate"],
"license": "MIT",
"type": "commonjs",
"dependencies": {
"level": "^10.0.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment