Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs-src/docs/cleanup.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {Faq, FaqItem} from '@site/src/components/faq';
To make the [replication](./replication.md) work, and for other reasons, RxDB has to keep deleted documents in storage so that it can replicate their deletion state.
This ensures that when a client is [offline](./offline-first.md), the deletion state is still known and can be replicated with the backend when the client goes online again.

Keeping too many deleted documents in the storage, can slow down queries or fill up too much disc space.
Keeping too many deleted documents in the storage, can slow down queries or fill up too much disc space. The Storage panel of the [devtool](./devtool.md) counts how many of them a collection currently holds and can run a cleanup on demand.
With the cleanup plugin, RxDB will run cleanup cycles that clean up deleted documents when it can be done safely.


Expand Down
2 changes: 1 addition & 1 deletion docs-src/docs/dev-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {Steps} from '@site/src/components/steps';

The dev-mode plugin adds many checks and validations to RxDB.
This ensures that you use the RxDB API properly and so the dev-mode plugin should always be used when
using RxDB in development mode.
using RxDB in development mode. To look at the data itself while developing, open the [database viewer](./devtool.md).

- Adds readable error messages.
- Ensures that `readonly` JavaScript objects are not accidentally mutated.
Expand Down
138 changes: 138 additions & 0 deletions docs-src/docs/devtool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
title: Devtool - Database Viewer and Editor for RxDB
slug: devtool.html
description: Inspect and edit a running RxDB database in the browser with a data grid, Mango query bar, schema analysis, query explain, replication and change feeds.
image: /headers/devtool.jpg
---

# Devtool

With the `devtool` plugin you can open a **database viewer** for a running [RxDatabase](./rx-database.md) inside your app. It reads the data of the live database, so you see the same documents your code sees, including the ones written a millisecond ago.

Key features:

- **Data grid and JSON view** with a [Mango query](./rx-query.md) bar, sorting and paging at 100 rows per page.
- **Document drawer** that stages your edits and previews the exact `upsert()` call before anything is written.
- **Live activity map** that draws the database as app, collections and remote, with per collection write rates over the last 60 seconds.
- **Schema panel** that samples the stored documents and reports what they actually contain next to what the [schema](./rx-schema.md) declares.
- **Query lab** that explains which index a query used, how many documents it examined and what it discarded.
- **Replication and Changes panels** that show what crossed the wire and the diff of every write.
- **Storage panel** with document counts, tombstone counts and a button to run the [cleanup](./cleanup.md).

The whole UI ships inside the plugin. There are no external stylesheets, no font files and no network requests.

## Installation

```ts
import { mountRxDBDevtool } from 'rxdb/plugins/devtool';
```

## Usage

Mount the devtool on a database and it renders as a full screen overlay:

```ts
import { createRxDatabase } from 'rxdb';
import { getRxStorageLocalstorage } from 'rxdb/plugins/storage-localstorage';
import { mountRxDBDevtool } from 'rxdb/plugins/devtool';

const db = await createRxDatabase({
name: 'heroesdb',
storage: getRxStorageLocalstorage()
});
await db.addCollections({
heroes: { schema: heroSchema }
});

const devtool = mountRxDBDevtool(db);
```

To render it into your own element instead, pass a `target`:

```ts
const devtool = mountRxDBDevtool(db, {
target: document.querySelector('#rxdb-panel')
});
```

Call `devtool.destroy()` to close it again. Mounting twice for the same database returns the devtool that is already open.

You can also add the plugin and use the `mountDevtool()` method on the database:

```ts
import { addRxPlugin } from 'rxdb';
import { RxDBDevtoolPlugin } from 'rxdb/plugins/devtool';
addRxPlugin(RxDBDevtoolPlugin);

const devtool = db.mountDevtool();
```

## Options

```ts
mountRxDBDevtool(db, {
// where the devtool is mounted, changes only the chrome of the top bar
surface: 'tab', // 'tab' | 'embedded' | 'tanstack' | 'dump'
// element to render into, defaults to a full screen overlay
target: myElement,
// rows per page in every grid and result list
pageSize: 100,
// set when reading a static export instead of a live database
dump: { fileName: 'heroesdb-2026-08-05.json', exportedAt: Date.now() },
// state of a remote connection, for example over WebRTC
connection: { state: 'local' }
});
```

When `dump` is set, or when `connection` reports a read-only remote, every writing action is disabled and says so in its tooltip. Counts, Schema, Query lab and Storage keep working.

## Editing documents

Rows open in the drawer, the checkbox selects without opening it. Editing a field in the drawer, or double clicking a cell in the grid, stages the change instead of writing it. The **WILL RUN** block shows the exact call with the changed lines highlighted, and only `Apply changes` runs it.

Deleting more than one document at once states the blast radius first: how many of how many documents match, that the deletes replicate to connected peers, and that tombstones remain until cleanup runs. The delete button stays disabled until you type the collection name.

## What the Live map shows

The Live map draws names, counts and rates, never document contents, so the screen stays safe to share. Every colour is paired with a glyph: `+` insert, `~` update, `-` delete, `?` query, `◆` live query result, `↑` `↓` push and pull. Above roughly 200 events per second a lane becomes a moving band and the exact rate is printed beside it, so the picture stays readable with motion disabled.

Reads and live query emits are derived from the query cache rather than from a dedicated event stream, so their counters update once per second.

## Limitations

- The devtool needs a DOM. Calling `mountRxDBDevtool()` in Node.js throws the error code `DVT1`.
- Below 640 pixels the rail and the tool panels do not fit. The devtool switches to three stacked screens that are read-only.
- Leadership is only known when the [leader election](./leader-election.md) plugin is added. RxDB does not publish a roster of the other open instances, so the Instances panel reports this instance only.
- The Changes and Replication feeds keep their most recent entries in memory. Nothing the devtool records is written back into the database.
- Tombstone counts and the cleanup button need the [cleanup](./cleanup.md) plugin.

## FAQ

<details>
<summary>Does the devtool slow down my app?</summary>

It subscribes to the change stream of the database and polls the query cache once per second. Both are cheap. Ship it behind a flag anyway so it is not bundled into production builds.

</details>

<details>
<summary>Can I inspect a database that runs on another device?</summary>

Yes. Pass a `connection` describing the remote peer and the devtool shows the connection stages while it pairs, a banner with the transport and the write mode once it is connected, and a diagnosis if it fails. When peer to peer traffic is blocked, export the data with [exportJSON()](./rx-database.md#exportjson) on the device and open the file with the `dump` option instead.

</details>

<details>
<summary>Why does the Live map use different colours than the Replication panel?</summary>

The map uses one violet for push and pull so that replication reads as a single flow, while the Replication and Changes panels colour each direction separately. The glyphs `↑` and `↓` are the same in both places.

</details>

## Follow Up

- Start with the [Quickstart](./quickstart.md).
- Read about [RxQuery](./rx-query.md) to write the selectors the query bar takes.
- Read about [cleanup](./cleanup.md) to understand what the Storage panel purges.
- Read about [dev-mode](./dev-mode.md) for the other checks that run while developing.
- Check the [RxDB GitHub repo](/code/) and leave a star ⭐
2 changes: 1 addition & 1 deletion docs-src/docs/rx-database.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ myDb.$.subscribe(changeEvent => console.dir(changeEvent));
```

### exportJSON()
Use this function to create a JSON export from every piece of data in every collection of this database. You can pass `true` as a parameter to decrypt the encrypted data fields of your document.
Use this function to create a JSON export from every piece of data in every collection of this database. You can pass `true` as a parameter to decrypt the encrypted data fields of your document. Such an export can be opened read-only in the [devtool](./devtool.md) when you cannot reach the device the database runs on.


Before `exportJSON()` and `importJSON()` can be used, you have to add the `json-dump` plugin.
Expand Down
2 changes: 1 addition & 1 deletion docs-src/docs/rx-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {Faq, FaqItem} from '@site/src/components/faq';

# RxQuery

To find documents inside of an [RxCollection](./rx-collection.md), RxDB uses the RxQuery interface that handles all query operations: it serves as the main interface for fetching documents, relies on a MongoDB-like [Mango Query Syntax](https://github.com/cloudant/mango), and provides three types of queries: [find()](#find), [findOne()](#findOne) and [count()](#count). By caching and de-duplicating results, RxQuery ensures efficient in-memory handling, and when queries are observed or re-run, the [EventReduce algorithm](https://github.com/pubkey/event-reduce) speeds up updates for a fast real-time experience and queries that run more than once.
To find documents inside of an [RxCollection](./rx-collection.md), RxDB uses the RxQuery interface that handles all query operations: it serves as the main interface for fetching documents, relies on a MongoDB-like [Mango Query Syntax](https://github.com/cloudant/mango), and provides three types of queries: [find()](#find), [findOne()](#findOne) and [count()](#count). By caching and de-duplicating results, RxQuery ensures efficient in-memory handling, and when queries are observed or re-run, the [EventReduce algorithm](https://github.com/pubkey/event-reduce) speeds up updates for a fast real-time experience and queries that run more than once. To try a selector out against real data, and to see which index it used, run it in the [devtool](./devtool.md).

## find()
To create a basic `RxQuery`, call `.find()` on a collection and insert selectors. The result-set of normal queries is an array with documents.
Expand Down
5 changes: 5 additions & 0 deletions docs-src/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,11 @@ const sidebars = {
iconAfter: 'premium'
}
},
{
type: 'doc',
id: 'devtool',
label: 'Devtool'
},
{
type: 'doc',
id: 'webmcp',
Expand Down
5 changes: 5 additions & 0 deletions examples/angular/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ <h3>
<mat-card-subtitle>Add Hero</mat-card-subtitle>
<hero-insert></hero-insert>
</mat-card>
<br />
<mat-card>
<mat-card-subtitle>Database</mat-card-subtitle>
<db-viewer></db-viewer>
</mat-card>
</div>
3 changes: 2 additions & 1 deletion examples/angular/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {
import { MatCard, MatCardSubtitle } from '@angular/material/card';
import { HeroesListComponent } from './components/heroes-list/heroes-list.component';
import { HeroInsertComponent } from './components/hero-insert/hero-insert.component';
import { DbViewerComponent } from './components/db-viewer/db-viewer.component';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less'],
imports: [MatCard, MatCardSubtitle, HeroesListComponent, HeroInsertComponent]
imports: [MatCard, MatCardSubtitle, HeroesListComponent, HeroInsertComponent, DbViewerComponent]
})
export class AppComponent {
title = 'angular';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<button
mat-raised-button
color="primary"
class="db-viewer-button"
aria-label="open the database viewer"
(click)="open()"
>
<mat-icon>storage</mat-icon>
Open database viewer
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
ChangeDetectionStrategy,
Component,
OnDestroy
} from '@angular/core';
import { MatButton } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';

import { mountRxDBDevtool } from 'rxdb/plugins/devtool';
import type { DevtoolHandle } from 'rxdb/plugins/devtool';

import { DatabaseService } from '../../services/database.service';

/**
* Opens the RxDB database viewer on top of the app.
* @link https://rxdb.info/devtool.html
*/
@Component({
selector: 'db-viewer',
templateUrl: './db-viewer.component.html',
providers: [DatabaseService],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MatButton, MatIcon]
})
export class DbViewerComponent implements OnDestroy {

public isOpen = false;

private devtool?: DevtoolHandle;
private overlay?: HTMLElement;

constructor(
private dbService: DatabaseService
) { }

/**
* The overlay is built outside of the angular template so that it sits
* above the app regardless of the stacking contexts around this component.
*/
open() {
if (this.isOpen) {
return;
}
const overlay = document.createElement('div');
Object.assign(overlay.style, {
position: 'fixed',
inset: '0',
zIndex: '9000',
display: 'flex',
flexDirection: 'column',
background: '#0D0F18'
});

const bar = document.createElement('div');
Object.assign(bar.style, {
display: 'flex',
justifyContent: 'flex-end',
padding: '6px 10px',
background: '#27022D',
borderBottom: '1px solid rgba(255,255,255,0.10)'
});
const closeButton = document.createElement('button');
closeButton.textContent = 'Close viewer';
Object.assign(closeButton.style, {
border: '1px solid rgba(255,255,255,0.25)',
background: 'transparent',
color: '#FFFFFF',
font: '11px system-ui, sans-serif',
padding: '4px 12px',
cursor: 'pointer'
});
closeButton.addEventListener('click', () => this.close());
bar.appendChild(closeButton);

const target = document.createElement('div');
Object.assign(target.style, { flex: '1', minHeight: '0' });

overlay.appendChild(bar);
overlay.appendChild(target);
document.body.appendChild(overlay);

this.overlay = overlay;
this.devtool = mountRxDBDevtool(this.dbService.db as any, { target });
this.isOpen = true;
}

close() {
this.devtool?.destroy();
this.devtool = undefined;
this.overlay?.remove();
this.overlay = undefined;
this.isOpen = false;
}

ngOnDestroy() {
this.close();
}
}
1 change: 1 addition & 0 deletions orga/changelog/devtool-database-viewer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added the `devtool` plugin, a database viewer and editor for a running [RxDatabase](https://rxdb.info/rx-database.html). It renders a data grid and JSON view with a Mango query bar, a document drawer that previews the exact upsert before it runs, and panels for the live activity map, schema analysis, query explain, replication, changes and storage. Mount it with `mountRxDBDevtool(database)` or `database.mountDevtool()`. The [angular example](https://github.com/pubkey/rxdb/tree/master/examples/angular) opens it from a button. Added testcases for the metrics window, the selector parsing, the document diff and the grid column selection.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@
"import": "./dist/esm/plugins/crdt/index.js",
"default": "./dist/esm/plugins/crdt/index.js"
},
"./plugins/devtool": {
"types": "./dist/types/plugins/devtool/index.d.ts",
"require": "./dist/cjs/plugins/devtool/index.js",
"import": "./dist/esm/plugins/devtool/index.js",
"default": "./dist/esm/plugins/devtool/index.js"
},
"./plugins/dev-mode": {
"types": "./dist/types/plugins/dev-mode/index.d.ts",
"require": "./dist/cjs/plugins/dev-mode/index.js",
Expand Down
8 changes: 8 additions & 0 deletions src/plugins/dev-mode/error-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,14 @@ export const ERROR_MESSAGES = {
docs: ''
},

// plugins/devtool
DVT1: {
message: 'The devtool can only be mounted where a DOM is available',
cause: 'mountRxDBDevtool() was called in an environment without a document, for example in Node.js.',
fix: 'Mount the devtool from browser code, or connect to the database from a browser instead.',
docs: ''
},

// plugins/webmcp
WMCP1: {
message: 'WebMCP Agent attempted to delete a document that does not exist',
Expand Down
Loading
Loading