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

ModeBackendStorage
Capsul (preview/deployed)Next.js API routesSQLite file on server (better-sqlite3)
Standalone (downloaded)AlaSQL (pure JS, in-browser)IndexedDB (persisted as JSON)