Docs menu
DocsAPI reference

SQLite

Private, persistent SQLite databases — one per name you open, no prompts.

For structured data that outgrows key-value storage — conversations, terminal history, rows you want to query — declare "sqlite" and get your own private, persistent SQLite database files (one per name you open). No approval prompt; the files live under the app's private data directory and survive restarts.

The flat bridge surface (prefer this)

// app.json: "capabilities": ["sqlite"]

const name = await window.chatoss.db.open('myapp');   // → 'myapp' (sanitized) or null
await window.chatoss.db.exec('myapp', 'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT)');
await window.chatoss.db.exec('myapp', 'INSERT INTO notes (text) VALUES (?)', ['hello']); // params optional
const rows = await window.chatoss.db.query('myapp', 'SELECT * FROM notes WHERE text = ?', ['hello']);
// → [{ id: 1, text: 'hello' }] — rows as objects keyed by column name
await window.chatoss.db.close('myapp'); // drops the cached handle; the file persists

The FLAT shape above — exec(name, sql, params?) / query(name, sql, params?) / close(name), with the database name as the first argument — is the real bridge surface and the one to prefer.

The handle form

open(name) also returns a handle built client-side on top of those same flat calls, if you find it tidier:

const db = await window.chatoss.db.open('myapp');   // → handle, or null on failure
await db.exec('CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT)');
const rows = await db.query('SELECT * FROM notes WHERE text = ?', ['hello']);
await db.close();

Rules

  • open(name) creates/opens a private DB file (name is sanitized to [a-zA-Z0-9_-]), one file per name.
  • exec(name, sql, params?) runs DDL/DML (CREATE/INSERT/UPDATE/DELETE) and returns the affected-row count. query(name, sql, params?) runs a SELECT and returns rows as objects keyed by column name.
  • 🔴 params really are bound. Use ? placeholders and pass values positionally — never string-concatenate SQL. (In an older build params was silently dropped, so INSERT … VALUES (?) inserted nothing; that is fixed, and a build old enough to drop them is old enough to fail loudly elsewhere.)
  • close(name) drops the cached connection; the file persists.
  • Each app can only touch its OWN databases — the app id comes from the manifest host-side, never from your arguments, so there is no cross-app access and nothing to spoof.
  • A previewed / in-development app gets a real database too. An app with no installed record (running under preview.launch, or from the Create app's live preview) is namespaced to its own container rather than refused, so you can build and test the whole schema before publishing.