flutterware
All guides
The running app

Database watch — your app's sqlite, on every surface

Hand the devbar a DatabaseAdapter and the running app's database becomes a panel: browse the schema, run a query, pin a live query, follow the writes — from the cockpit's App tab, from fw run run, from an agent over MCP, on any device the run cockpit reaches. Nothing here needs a rebuild to inspect, and it works on a physical phone, where the database file itself is sealed inside the app sandbox (design doc: docs/superpowers/specs/2026-08-12-sqlite-watch-design.md).

flutterware imports no sqlite. The adapter is four function types and a name; you wire it to whatever database library you use and own those five lines.

The recipe: sqlite_async (and PowerSync)#

PowerSyncDatabase implements sqlite_async's interface, so the same lines cover both. A live version is fixtures/probe_app/lib/devbar_example.dart.

import 'package:flutterware/devbar.dart';

Devbar(
  plugins: [
    DatabasePlugin.init(
      database: DatabaseAdapter(
        query: (sql, args) => db.getAll(sql, args),
        updates: db.updates.map((u) => u.tables),
        watch: (sql) =>
            db.watch(sql, throttle: const Duration(milliseconds: 250)),
      ),
    ),
  ],
  child: ...,
)

PowerSync: say so, and see the sync too#

One more line tells the panel the database is PowerSync's:

DatabaseAdapter(
  query: (sql, args) => db.getAll(sql, args),
  updates: db.updates.map((u) => u.tables),
  sync: DatabaseSync.powersync,
)

The panel then reads PowerSync's own tables, through the same read-only query, and adds:

It is said rather than guessed: an app on plain sqlite gets nothing it has no use for, and flutterware still imports no sync library.

Writes are opt-in, by existence#

There is no flag. Provide execute and an Execute SQL action exists, marked danger on every surface; leave it out and no surface — agents included — can see a write door at all:

DatabaseAdapter(
  query: (sql, args) => db.getAll(sql, args),
  updates: db.updates.map((u) => u.tables),
  execute: (sql, args) => db.execute(sql, args),  // the whole opt-in
)

What you get on the wire#

From an agent, one call each:

fw run run panelInvoke --panel=db:main --action=query \
  --args='{"sql": "SELECT * FROM orders WHERE status = ?", "args": "[\"open\"]"}'

Edit this page on GitHub