Skip to content

Instantly share code, notes, and snippets.

@loilo
Last active December 6, 2025 03:09
Show Gist options
  • Select an option

  • Save loilo/ed43739361ec718129a15ae5d531095b to your computer and use it in GitHub Desktop.

Select an option

Save loilo/ed43739361ec718129a15ae5d531095b to your computer and use it in GitHub Desktop.

Back up and Restore an IndexedDB Database

This gist provides functions to import and export data from an IndexedDB database as JSON. It's based on Justin Emery's indexeddb-export-import package, but applies some adjustments that reflect better on the current browser landscape (i.e. better developer ergonomics but no support for Internet Explorer).

Usage

For each of the provided functionalities, you need a connected IDBDatabase instance.

Export Data

import { idb } from 'some-database'

import { exportToJson } from 'idb-backup-and-restore.js'

exportToJson(idb)
  .then(result => {
    console.log('Exported JSON string:', result)
  })
  .catch(error => {
    console.error('Something went wrong during export:', error)
  })

Import Data

import { idb } from 'some-database'
import { serializedData } from 'some-serialized-data'

import { importFromJson } from 'idb-backup-and-restore.js'

importFromJson(idb, serializedData)
  .then(() => {
    console.log('Successfully imported data')
  })
  .catch(error => {
    console.error('Something went wrong during import:', error)
  })

Clear Database

Depending on your use case, it can be reasonable to clear a database before importing serialized data:

import { idb } from 'some-database'
import { serializedData } from 'some-serialized-data'

import { importFromJson, clearDatabase } from 'idb-backup-and-restore.js'

clearDatabase(idb)
  .then(() => importFromJson(idb, serializedData))
  .then(() => {
    console.log('Successfully cleared database and imported data')
  })
  .catch(error => {
    console.error('Could not clear & import database:', error)
  })
/**
* Export all data from an IndexedDB database
*
* @param {IDBDatabase} idbDatabase The database to export from
* @return {Promise<string>}
*/
export function exportToJson(idbDatabase) {
return new Promise((resolve, reject) => {
const exportObject = {}
if (idbDatabase.objectStoreNames.length === 0) {
resolve(JSON.stringify(exportObject))
} else {
const transaction = idbDatabase.transaction(
idbDatabase.objectStoreNames,
'readonly'
)
transaction.addEventListener('error', reject)
for (const storeName of idbDatabase.objectStoreNames) {
const allObjects = []
transaction
.objectStore(storeName)
.openCursor()
.addEventListener('success', event => {
const cursor = event.target.result
if (cursor) {
// Cursor holds value, put it into store data
allObjects.push(cursor.value)
cursor.continue()
} else {
// No more values, store is done
exportObject[storeName] = allObjects
// Last store was handled
if (
idbDatabase.objectStoreNames.length ===
Object.keys(exportObject).length
) {
resolve(JSON.stringify(exportObject))
}
}
})
}
}
})
}
/**
* Import data from JSON into an IndexedDB database.
* This does not delete any existing data from the database, so keys may clash.
*
* @param {IDBDatabase} idbDatabase Database to import into
* @param {string} json Data to import, one key per object store
* @return {Promise<void>}
*/
export function importFromJson(idbDatabase, json) {
return new Promise((resolve, reject) => {
const transaction = idbDatabase.transaction(
idbDatabase.objectStoreNames,
'readwrite'
)
transaction.addEventListener('error', reject)
var importObject = JSON.parse(json)
for (const storeName of idbDatabase.objectStoreNames) {
let count = 0
for (const toAdd of importObject[storeName]) {
const request = transaction.objectStore(storeName).add(toAdd)
request.addEventListener('success', () => {
count++
if (count === importObject[storeName].length) {
// Added all objects for this store
delete importObject[storeName]
if (Object.keys(importObject).length === 0) {
// Added all object stores
resolve()
}
}
})
}
}
})
}
/**
* Clear a database
*
* @param {IDBDatabase} idbDatabase The database to delete all data from
* @return {Promise<void>}
*/
export function clearDatabase(idbDatabase) {
return new Promise((resolve, reject) => {
const transaction = idbDatabase.transaction(
idbDatabase.objectStoreNames,
'readwrite'
)
transaction.addEventListener('error', reject)
let count = 0
for (const storeName of idbDatabase.objectStoreNames) {
transaction
.objectStore(storeName)
.clear()
.addEventListener('success', () => {
count++
if (count === idbDatabase.objectStoreNames.length) {
// Cleared all object stores
resolve()
}
})
}
})
}
@knutesears
Copy link

knutesears commented Dec 6, 2025

Here's a quick hack to do what I needed to do, which is save/load a whole IDB database from the console. (I'm rolling out a new version of a web app, with changes to IDB structures, and I want to save copies so I can re-run upgrade scenarios if I don't get it right the first time.) Of course, one could just add these functions to idb-backup-and-restore.js for a cleaner HTML world, but I thought this was illustrative of how to do it without fiddling with a module...

`<script type="module">

    import { exportToJson, importFromJson, clearDatabase } from '/idb-backup-and-restore.js'
                
    const dbName = 'DB'; // specify your DB name here

    function exportIDB() {

        indexedDB.open(dbName).onsuccess = async function (event) {
            const db = event.target.result;
            console.log("Exporting DB:", db.name);

            const jsonData = await exportToJson(db);

            // trigger download automatically
            const blob = new Blob([JSON.stringify(jsonData, null, 2)], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = Object.assign(document.createElement('a'), { href: url, download: 'indexeddb_backup.json' });
            a.click();
            URL.revokeObjectURL(url);

            console.log("Download triggered.");
        };
    }

    function importIDB() {

        const input = document.createElement('input');
        input.type = 'file';
        input.accept = 'application/json';
        document.body.appendChild(input); // append to body so it works consistently

        input.onchange = async (event) => {
            const file = event.target.files[0];
            if (!file) return;

            const reader = new FileReader();
            reader.onload = async (e) => {
                try {
                    const backupData = JSON.parse(e.target.result);

                    // open the DB and prepare for import
                    indexedDB.open(dbName).onsuccess = async function (event) {
                        const db = event.target.result;
                        console.log(`Clearing and importing data into DB: ${db.name}`);

                        try {
                            // use global clear function first
                            await clearDatabase(db);
                            console.log("Database cleared successfully.");

                            // import the new data
                            await importFromJson(db, backupData);
                            console.log("Import successful! Reload the page to see changes.");

                        } catch (importError) {
                            console.error("Import/Clear failed:", importError);
                            alert("Import failed. Check console for details.");
                        }
                    };

                } catch (parseError) {
                    console.error("Failed to parse JSON file:", parseError);
                    alert("Could not read file or parse JSON data.");
                }
            };
            reader.readAsText(file);
        };

        // trigger the hidden input click to open the file dialog
        input.click();
        // clean up the input element afterward if you want...
        // document.body.removeChild(input); 
    }

    // make these globally available, i.e. for dev console use...
    window.exportIDB = exportIDB 
    window.importIDB = importIDB 
</script> `

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment