Dexie Logo
Documentation

Road Map: Dexie 5.0

Some of the features presented here were moved from the road map for dexie@4 in January 2024 when dexie@4 went into release candidate.
The goal for Dexie 5.0 will be a better experience for web developers to declare and query their data. What we'll be focusing on will be to query richness, paging and performance using some RAM-sparse caching. The goal is an intuitive and easy-to-use db API that performs well in large apps without sacrificing device resource usage.
We don't have a any dedicated time schedule of when dexie 5 will be in alpha, beta or feature complete. This road map may also be updated and modified along the way.

Type Safe Declaration

Schema definition and typings can be declared in a single expression. Instead of having to declare .version().stores() after instanciating db, the db instanciation and the schema declaration can be done in a single expression.

Dexie's classical schema style:

// 1. Declare db
export const db = new Dexie('friendsDB') as Dexie & {
  friends: Table<Friend, number>
}

// 2. Specify version(s) and schema(s)
db.version(1).stores({
  friends: `
    ++id,
    name,
    age
  `
});

Dexie@5 type+schma declaration in single expression:

export const db = new Dexie('friendsDB').stores({
  friends: Table<Friend>`
    ++id   
    name    # Comments allowed!
    age     # (comma is optional!)
  `
});

export interface Friend {
  id: number
  name: string
  age: number
  picture?: Blob
}

Representing Classes instead of Interfaces

In dexie@4 and earlier, we've always had the Table.mapToClass() method to connect a table to its model class.
In dexie@5 this will be done simply by declaring the schema with Table(MyClass) instead of Table<MyInterface>:
export const db = new Dexie('friendsDB').stores({
  friends: Table(Friend)` # Table(Class) instead of Table<Type>
    ++id
    name
    age
  `
});

export class Friend {
  id = 0;
  name = "";
  age = -1;
  picture?: Blob

  birthday() {
    return db.friends.update(this.id, { age: add(1) });
  }
}

Breaking Changes?

Ever since Dexie version 1 came out, we've been very strict with backward compability and almost never introduced any breaking changes.
To continue this approach, dexie schema declaration will stay backward compatible in dexie@5, so the old declaration style will continue to work. It will be an opt-in possibility to take advantage of the benefits with the new declaration style in dexie 5:
  • One single declaration for both schema and typings
  • No version number needed
  • The class is automatically mapped, just like mapToClass() did work in earlier versions.

Sub-classing Dexie

export class AppDB extends Dexie {
  friends = Table(Friend)`
    ++id
    name
    age
  `
}

const db = new AppDB('appDB');
The sub-classed version above is equivalent to:
const db = new Dexie('appDB').stores({
  friends: Table(Friend)`
    ++id
    name
    age
  `
});
Subclassing Dexie isn't required anymore for typings but it is still useful the declared class extends the Entity helper because it will have the properties db and table so that methods can perform operations on the database:
class Friend extends Model<AppDB> {
  id!: string;
  name!: string;
  age!: number;

  // methods can access this.db because we're subclassing Entity<AppDB>
  async birthDay() {
    return this.db.friends.update(this.id, { age: add(1) });
  }
}
Notice that versions aren't needed for schema changes anymore. Here we diverge from native IndexedDB that require this. As already introduced in dexie@4, we work around it letting the declared version and the native version diverge. And when they do, we store the virtual version in a meta table on the database. This table will only be created on-demand, if a schema upgrade on same given version was needed. Basically, we continue working like before, unless the db has the $meta table - in which case the info there will be respected instead of the native one.
Also, any methods in the type will be omitted from the insert type so that if you have a class with methods that backs the model of your table, you will continously be able to add items using plain objects (with methods omitted).

Migrations

We've changed the view of migrations and version handling. Before the version was directly related to changes in the schema such as added tables or indexes. This was natural and corresponds to how IndexedDB works natively.
The only situations where you need a new version number in dexie@5 will be in one of the following situations:
  • You want to rename a table or property
  • You've refactored your model and need to move data around to comply with the new model

New Migration Methods for Rename

Three new methods exists that can be used in migrations instead of update(). These are declarative and revertable, which is much better canary use cases where you might have to downgrade the database without deleting it.
This new bidirectional framework is also compatible with Dexie Cloud since it allows for multiple clients sharing the same data of in different versions and still be able to sync it.
  • renameTable()
  • renameProperty()
  • refactor()

Example: You want to rename a table or property or both:

You want to rename table "friends" to "contacts". You also want to rename a property on that model from "name" to "displayName":
const db = new Dexie('dbName').version(2).stores({
  contacts: Table<Contact>`
    ++id
    displayName
    age
  `
}).renameTable({friends: 'contacts'})
  .renameProperty({contacts: {name: 'displayName'}}); // renaming prop "name" to "displayName"

object-wise upgrade()

We add a new object-wise upgrade. In contrast to the generic upgrade() callback, object-wise upgrades can incrementally upgrade individual objects which makes it perfect for distributed synced databases where some clients may still be on the old version.
If you are on Dexie Cloud, only object-wise upgrades are permitted.
const db = new Dexie('dbName').version(3).stores({
  contacts: Table<Contact>`
    ++id
    [lastName+firstName]
    age
    `
}).upgrade({
  contacts: (contactV2: ContactV2) => {
    // Split displayName into firstName and lastName:
    const [firstName, ...lastNames] = contactV2.displayName?.split(' ') ?? [];
    const contact: Contact = {
      ...contactV2,
      firstName,
      lastName: lastNames?.join(' ')
    };
    return contact;
  }
});


// Keep the refactoring history of earlier versions in separate expression declared later:
db.version(2)
  .renameTable({friends: 'contacts'})
  .renameProperty({contacts: {name: 'displayName'}});

Richer Queries

Dexie will support combinations of criterias within the same collection and support a subset of mongo-style queries. Dexie has before only supported queries that are fully utilizing at least one index. Where-clauses only allow fields that can be resolved using a plain or compound index. And orderBy() requires an index for ordering, making it impossible to combine with a critera, unless the criteria uses the same index. Currently, combining index-based and 'manual' filtering is possible using filter(), but it puts the burden onto the developer to determine which parts of the query that should utilize index and which parts should not. Dexie 5.0 will move away from this limitation and allow any combination of criterias within the same query. Resolving which parts to utilize index will be decided within the engine.
orderBy() will be available on Collection regardless of whether the current query already 'occupies' an index or not. It will support multiple fields including non-indexed ones, and allow to specify collation.
await db.friends
  .where({
    name: 'foo',
    age: { between: [18, 65] },
    'address.city': { startsWith: 'S' }
  })
  .orderBy(['age', 'name'])
  .offset(50)
  .limit(25)
  .toArray();

Improved Paging

The cache will assist in improving paging. The caller will keep using offset()/limit() to do its paging. The difference will be that the engine can optimize an offset()-based query in case it starts close to an earlier query with the same criteria and order, so the caller will not need to use a new paging API

Encryption

We will provide a new encryption addon, similar to the 3rd part dexie-encrypted and dexie-easy-encrypt addons. These addons will continue to work with dexie@5 but due to the lack of maintainance of we believe there's a need to have a maintained addon for such an important feature.
The syntax for initializing encryption is not yet decided on, but might not correspond to those of the current 3rd part addons.

Support SQLite as backing DB

We also aim to make it possible to use Dexie and Dexie Cloud in react-native, nativescript, Node, Bun, Deno or in the browser with SQLite's webassembly build. Running Dexie on Node is actually already possible using IndexedDBShim but the idea is to support it natively to improve performance and stability.

Breaking Changes

No default export

We will stop exporting Dexie as a default export as it is easier to prohibit dual package hazard when not exporting both named and default exports. Named exports have the upside of enabling tree shaking so we prefer using that only.
Already since Dexie 3.0, it has been possible to import { Dexie } as a named export. To prepare for Dexie 5.0, it can be wise to change your imports from import Dexie from 'dexie' to import { Dexie } from 'dexie' already today.

More to come

Dexie has been pretty much backward compatible between major versions so far and the plan is to continue being backward compatible as much as possible. But there might be additional breaking changes to come and they will be listed here. This is a living document. Subscribe to our github discussions or to the blog if you want to get notified on updates.

© 2014-2025 Awarica AB

Made with love for great people.

Read Terms & Conditions and Privacy Policy.