Library / SDK
dexie/Dexie.js avatar
dexie/Dexie.js

Dexie.js turns IndexedDB into a promise API with live queries

A Minimalistic Wrapper for IndexedDB

14,596 stars712 forksTypeScriptApache-2.0

At a glance

What is it?
The Apache-2.0 library for browser storage wraps IndexedDB in promises, adds reactive hooks for React, and writes bulk data far faster than the raw API. Used by more than 100,000 sites and apps.
Who is it for?
Dexie sits in a narrow and useful place. It does not replace IndexedDB with a different storage engine, and it does not bring a sync backend or a query language you have to learn from scratch.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 31 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A schema string replaces the version upgrade callback

The difference shows up in the first four lines you write. Raw IndexedDB asks you to open a connection, and if the store set does not match what is already on disk it fires an `onupgradeneeded` handler where you create object stores and indexes by hand. Dexie replaces that ceremony with one string:

js
const db = new Dexie('FriendDatabase');
db.version(1).stores({
  friends: '++id, age'
});

`'++id, age'` is a schema declaration, not SQL. `++id` is an auto-incrementing primary key and `age` is an index, so `age` queries do not have to scan. Declaring version 2 means writing `db.version(2).stores({ friends: '++id, age, name' })` and Dexie computes the migration, which is the part people tend to get wrong by hand.

From there the stores become properties on the database object. `db.friends` is a table with methods on it, and every one of them returns a promise:

js
await db.friends.add({ name: 'Alice', age: 21 });

const youngFriends = await db.friends
  .where('age')
  .below(30)
  .toArray();

The `where('age')` call resolves to the indexed `age` field, `below(30)` narrows the range, and `toArray()` collects the matches. The README calls this `Yes, it's that simple`, which is fair for a hello world and generous for everything past it.

Three ways to load the same six lines

The README documents the entry points separately, which is useful because getting the URL wrong is the most common first-day problem. For a plain page, the ES module build straight from the CDN:

html
<script type="module">
  import { Dexie } from 'https://unpkg.com/dexie/dist/modern/dexie.mjs';
</script>

For a page that cannot use modules, the legacy UMD bundle attaches a global:

html
<script src="https://unpkg.com/dexie/dist/dexie.js"></script>

For a bundled project, `package.json` names the package `dexie` and maps the entry point per environment. `main` is `dist/dexie.js`, `module` and `jsnext:main` are `dist/dexie.mjs`, typings live at `dist/dexie.d.ts`, and the `exports` field has separate `production` and `development` branches. The production branch resolves `require` to `dist/dexie.min.js` and goes through `import-wrapper-prod.mjs` for ESM, while development keeps the readable bundle. That switch is the reason a bug can reproduce locally and vanish in a production build, and it is exactly what the v4.4.6 release notes describe fixing.

Live queries replace manual refetching in React

A React component that reads from IndexedDB has a problem the `useState` version never did: nothing tells it when to re-read. Dexie ships a companion package, `dexie-react-hooks`, whose `useLiveQuery` observes the tables a query touches and re-runs it when they change:

tsx
const youngFriends = useLiveQuery(() =>
  db.friends
    .where('age')
    .below(30)
    .toArray()
);

The subscription is derived from the query itself. There is no effect array to keep in sync and no invalidation call to remember after a write.

Typing the tables is where the TypeScript example earns its keep. Rather than declaring the shape of the database separately, the example casts the instance to a `Dexie` with typed tables:

tsx
import { Dexie, type EntityTable } from 'dexie';
import { useLiveQuery } from 'dexie-react-hooks';

export const db = new Dexie('FriendDatabase') as Dexie & {
  friends: EntityTable<Friend, 'id'>;
};

`EntityTable<Friend, 'id'>` means `db.friends.add` wants a `Friend` and `db.friends.get` takes an `id`. Without that cast every property lookup on the database is an error, because Dexie is one object holding stores you declare at runtime.

Tutorials for Svelte, Vue, Angular and vanilla JS are linked from the README for the frameworks that have their own adapter.

Bulk writes skip the per-record event handler

The performance argument for the wrapper is narrow and specific, and the README is honest about where it comes from. IndexedDB exposes a lesser-known feature that lets a transaction store many records without waiting on an `onsuccess` event for each one. Dexie's bulk methods use it:

js
bulkAdd(items: Array): Promise;
bulkDelete(keys: Array): Promise;
bulkPut(items: Array): Promise;

If you are importing thousands of records, calling `put` in a loop and awaiting each one is the slow path, and it is the path most people write first because it is the obvious one. The bulk variants are the difference between a progress bar and a frozen tab.

The rest of the surface is large enough that the README prints it as a reference list rather than prose, mixing methods from `WhereClause`, `Table` and `Collection`. Range and prefix matching cover `above`, `below`, `between`, `anyOf`, `noneOf`, `startsWith` and `startsWithIgnoreCase`, there are both `equals` and `equalsIgnoreCase`, and result shaping includes `limit`, `offset`, `reverse`, `sortBy`, `distinct`, `filter`, `and`, `or`, `until` and `each`. Two methods accept both a callback and an object of changes: `modify`, and `update` alongside it. Knowing that list exists in full is often more useful than memorising it, since the API reference covers the details.

The companion packages define the real scope

The repository is a workspace, and `pnpm-workspace.yaml` sits next to `addons/`. Release notes list the packages that ship together and pin them against each other, which is how you can tell what is core and what is not.

`dexie-cloud-addon` is the largest of them: sync across devices, authentication, access control, file and blob storage, and offline-first behaviour, with the README's summary being that you keep writing frontend code and it handles the backend. Alongside it sit `dexie-react-hooks` for reactive components, `y-dexie` as a Yjs binding for collaborative editing, and `dexie-export-import` for moving data in and out.

The v4.4.6 release is the clearest illustration of the versioning policy. Dexie itself moved to 4.4.6, `dexie-cloud-addon` to 4.4.15 and `dexie-export-import` to 4.4.1, while `dexie-react-hooks` and `y-dexie` stayed at 4.4.0 with the notes explicitly saying no change. So the numbers are aligned, not identical, and a lockfile pin matters when the cloud addon is in the picture.

None of that is in the core package. Core Dexie is a local storage library with Apache-2.0 licensing, and everything about cross-device state lives in a separate dependency.

What three patch releases say about the code

The June to September 2026 releases are worth reading together, because each one is a bug with a shape you can reason about.

v4.4.4 fixed `useLiveQuery` failing to detect updates. In the default cache mode, mutating a queried object in place and then calling `put()` would write through to the cache, so the before and after states matched and no subscriber fired. The fix deeply clones results on read, isolating query results from consumer mutations. That is the classic observable-state problem, and the fact that it shipped in a patch means the default mode was the broken one.

v4.4.5 fixed a crash when an index or primary key was named `constructor`. Tables with that name failed `db.open()` with `TypeError: indexList.push is not a function`, because the internal index lookup was inheriting from `Object.prototype`. The fix builds that lookup from a prototype-free object, which also covers `toString` and `valueOf`. Naming a field `constructor` is unremarkable in application data and catastrophic in an internal map.

v4.4.6 fixed `updating`-hook subscribers missing record modifications in the minified production bundle, along with schema synchronisation in the cloud addon and filtered chunked exports in the export/import package. A bug that only appears in the minified build is a packaging bug, and it is precisely the kind that the `production` branch in `exports` sets up.

The adoption claim and where it does not apply

The README makes a specific adoption claim: more than 100,000 websites, apps and other projects use Dexie, across all browsers, Electron for desktop, Capacitor for iOS and Android, and plain PWAs. It also states that Dexie works around bugs in IndexedDB implementations, which is the part that matters most when you read the repository rather than the marketing.

What it is not is a replacement storage engine. Data goes into the browser's IndexedDB, which means the same quotas, the same clearing behaviour under storage pressure, and the same fact that the data lives on one device. Anyone who needs a server-side relational database is looking at the wrong library, and the comparison to SQLite is a comparison of different things.

The other boundary is the ecosystem. A wrapper that exposes IndexedDB faithfully will also expose IndexedDB's shape, including the requirement that queries go through an index or a primary key, and the fact that a schema change is a version bump in a string you wrote. Dexie's own advice is to read the docs after the hello world, and the API reference is where the depth is.

Editorial conclusion

Dexie sits in a narrow and useful place. It does not replace IndexedDB with a different storage engine, and it does not bring a sync backend or a query language you have to learn from scratch. What it replaces is the ceremony around IndexedDB: callback pairs that leak out of every method, version upgrades written as transaction callbacks, and no way to re-render a component when the data underneath it changes. The cost is a dependency, a schema string that only Dexie understands, and a layer between you and the browser API when something goes wrong. The recent releases suggest an actively fixed codebase rather than a stable one on autopilot, since v4.4.4, v4.4.5 and v4.4.6 all shipped targeted fixes for caching, prototype collisions and minified-bundle hook behaviour. For local-first browser storage that needs a real query surface and reactive components, that trade reads as a good one. The package is Apache-2.0, the npm package is `dexie`, and the project was last pushed on 2026-09-10.

Frequently asked questions

What is Dexie.js?

Dexie.js is a minimalistic Apache-2.0 wrapper around IndexedDB, the browser's built-in database. It turns the native callback API into promises, declares schemas as short strings instead of version upgrade callbacks, and adds companions for reactive React components, Yjs collaboration, data import and export, and optional cloud sync. The repository describes it as used by more than 100,000 sites and apps.

How do you use Dexie.js in a project?

Create a database instance with a name, declare the stores and their indexes with `db.version(1).stores({...})`, then treat each store as a property with promise-returning methods. Load it from a CDN as an ES module, from the UMD bundle as a global, or install the npm package, whose exports map picks a minified production bundle or a readable development one. In React, wrap reads in `useLiveQuery` from dexie-react-hooks so the component updates when the data changes.

Is Dexie.js free and open source?

Yes. Dexie is published on npm under the package name `dexie` and carries the Apache-2.0 license, with the license text and a NOTICE file in the repository root. The core library is free to use. Dexie Cloud, the commercial service that adds real-time sync, authentication and access control on top, is a separate product, as are the paid tiers of its plan.

Dexie.js or raw IndexedDB, which should you use?

Raw IndexedDB has no dependency and no abstraction to learn, so it wins if your data model is small and fixed or you need precise control over transactions and object stores. Dexie wins on everything that comes from the wrapper: promises instead of request and event pairs, computed schema migrations, a large indexed query surface, bulk writes that skip per-record events, and `useLiveQuery` for reactive components. The trade is a dependency and a layer between you and the browser API when something breaks.

Official sources

  1. dexie/Dexie.js on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dexie-dexie-js.svg)](https://hysenlabs.com/projects/dexie-dexie-js)