SQLite

On-device SQL storage for offline-first data and local caching.

adds it to an existing project · no account needed
Currentas published in v1.5.1

The rule

This is the whole text, exactly as your agent receives it. Nothing is held back for the paid tier.

Queries

  • Always bind parameters. Never concatenate a value into SQL — it breaks on an apostrophe and is injectable in general.
  • Select the columns you need, not SELECT *.
  • Index anything you filter, join or sort by.
  • Paginate with LIMIT/OFFSET; never load an unbounded table into memory.

Writes

  • Wrap bulk writes in withTransactionAsync. Row-by-row inserts are orders of magnitude slower because each is its own disk sync.
  • Set PRAGMA journal_mode = WAL at open, or concurrent access produces "database is locked".

Migrations

  • Ordered, idempotent, forward-only, tracked with PRAGMA user_version.
  • Never DROP or rewrite a column in an upgrade path — that is a user's real data on a device you cannot inspect or restore.
  • A migration must run cleanly from the oldest supported app version, not just from the current schema on your machine.

Access

  • All SQL lives in src/services/database/. Components and hooks never write queries.
  • Open the database once and reuse the handle.

Never

  • Never store credentials, tokens or personal data here — the file is not encrypted. Use secure storage.
  • Never assume the local database survives reinstall.
  • Never query inside a render.

6 formats, one per tool

Each tab is the file that tool actually reads, at the path it actually looks in. Knowing where each one looks is most of the work of supporting it.

.cursor/rules/sqlite.mdchand-written
---
description: SQLite conventions
globs: ["src/services/database/**", "src/services/**/*.sql"]
alwaysApply: false
---

# SQLite

## Queries

- **Always bind parameters.** Never concatenate a value into SQL — it breaks on
  an apostrophe and is injectable in general.
- Select the columns you need, not `SELECT *`.
- Index anything you filter, join or sort by.
- Paginate with `LIMIT`/`OFFSET`; never load an unbounded table into memory.

## Writes

- Wrap bulk writes in `withTransactionAsync`. Row-by-row inserts are orders of
  magnitude slower because each is its own disk sync.
- Set `PRAGMA journal_mode = WAL` at open, or concurrent access produces
  "database is locked".

## Migrations

- Ordered, idempotent, **forward-only**, tracked with `PRAGMA user_version`.
- Never `DROP` or rewrite a column in an upgrade path — that is a user's real
  data on a device you cannot inspect or restore.
- A migration must run cleanly from the oldest supported app version, not just
  from the current schema on your machine.

## Access

- All SQL lives in `src/services/database/`. Components and hooks never write
  queries.
- Open the database once and reuse the handle.

## Never

- Never store credentials, tokens or personal data here — the file is not
  encrypted. Use secure storage.
- Never assume the local database survives reinstall.
- Never query inside a render.

Hand-written by the module author, frontmatter and all. It is the source the four derived formats are rendered from, so a correction lands here first.

What else this module writes

The rule is one file of several. Selecting SQLite contributes all of this too — merged with every other module you pick, with conflicts resolved rather than duplicated.

Dependencies
expo-sqlite^57.0.0
Folders
src/services/database/migrations/

Advisory history

Every time this rule turned out to be wrong, and what we did about it.

No corrections yet

This rule has been accurate since it was published. That is a fact about the rule, not a promise about the future — which is the whole reason this section exists.

Pro tells you the day a correction lands that affects a repo you actually have.

See what Pro adds →

Rules people add alongside this one

Put this rule in a real project

The wizard picks the rest of the stack with you, writes all 6 formats, and leaves a manifest so check can tell you when any of it drifts.