Core Concepts
CapsulDB
Every Capsul-generated app has access to a real persistent database through window.CapsulDB — injected automatically before any of your app's scripts run.
Why CapsulDB?
Most AI-generated apps default to localStorage for data — which has a 5 MB limit, stores only strings, and has no querying capability. CapsulDB gives generated apps a real SQL database:
Real SQL
Full SQLite syntax: JOINs, aggregates, indexes, transactions
Persistent
Data survives page refreshes, browser restarts, and redeployment
Per-app isolation
Each app has its own database file — apps cannot read each other's data
Offline-capable
Downloaded apps use AlaSQL + IndexedDB — no server required
API Reference
CapsulDB.execute(sql, params?)
INSERT, UPDATE, DELETE, CREATE TABLE. Returns { changes: number }.
// Create table
await CapsulDB.execute(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
done INTEGER DEFAULT 0,
due TEXT
)
`);
// Insert
await CapsulDB.execute(
'INSERT INTO tasks (title, due) VALUES (?, ?)',
['Buy milk', '2026-03-25']
);
// Update
await CapsulDB.execute(
'UPDATE tasks SET done = 1 WHERE id = ?',
[taskId]
);
// Delete
await CapsulDB.execute('DELETE FROM tasks WHERE id = ?', [taskId]);CapsulDB.query(sql, params?)
SELECT — returns { rows: [...] }.
const { rows } = await CapsulDB.query(
'SELECT * FROM tasks WHERE done = ? ORDER BY id DESC',
[0]
);
// rows = [{ id: 3, title: 'Buy milk', done: 0, due: '2026-03-25' }, ...]CapsulDB.all(sql, params?)
Alias for query() — returns the rows array directly (no wrapper object).
const tasks = await CapsulDB.all('SELECT * FROM tasks ORDER BY id DESC');
// tasks = [{ id: 3, ... }, { id: 2, ... }]CapsulDB.transaction(operations)
Run multiple writes atomically. Either all succeed or all are rolled back.
await CapsulDB.transaction([
{ sql: 'INSERT INTO orders (item) VALUES (?)', params: ['Widget'] },
{ sql: 'UPDATE inventory SET stock = stock - 1 WHERE item = ?', params: ['Widget'] },
]);Best practices
- ►Always use CREATE TABLE IF NOT EXISTS — This is called every time the app loads. Using IF NOT EXISTS ensures it only creates on first load and safely re-runs on refresh.
- ►Load data on startup — After creating tables, immediately SELECT all rows and render them. Never hardcode sample data.
- ►Show a loading state — CapsulDB calls are async. Show a spinner or skeleton while initialising.
- ►Handle errors — Wrap CapsulDB calls in try/catch and show a user-friendly message if something goes wrong.
- ►Use parameterised queries — Always pass values as the second argument (params array) rather than string-concatenating them into SQL. This prevents SQL injection.
Modes
| Mode | Backend | Storage |
|---|---|---|
| Capsul (preview/deployed) | Next.js API routes | SQLite file on server (better-sqlite3) |
| Standalone (downloaded) | AlaSQL (pure JS, in-browser) | IndexedDB (persisted as JSON) |