Skip to main content
Change Data Capture is an early preview feature. The API and behavior may change in future releases.
Change Data Capture (CDC) automatically logs every insert, update, and delete to a dedicated table as changes happen — per connection. Unlike periodic table scans, CDC records changes in real time, making it useful for audit trails, event sourcing, and data synchronization.

Enabling CDC

Enable CDC for the current connection using the capture_data_changes_conn pragma:
PRAGMA capture_data_changes_conn('<mode>[,custom_cdc_table]');
Once enabled, all modifications made on that connection are logged to the CDC table (default: turso_cdc).

Modes

ModeCapturesDescription
offNothingDisable CDC for the connection
idrowid onlyMost compact; logs only the affected row ID
beforeRow state before the changeCaptures state before updates and deletes
afterRow state after the changeCaptures state after inserts and updates
fullBoth before and after statesComplete audit trail

Custom CDC table

You can direct CDC output to a custom table instead of turso_cdc:
PRAGMA capture_data_changes_conn('full,my_audit_log');

The turso_cdc table

When CDC is first enabled, the turso_cdc table is created automatically. Each row records one change event.
ColumnTypeDescription
change_idINTEGERMonotonically increasing integer. Always strictly increasing. Serves as the primary key.
change_timeINTEGERLocal Unix timestamp (seconds) when the change was recorded. Not guaranteed to be strictly increasing — it can drift or repeat.
change_typeINTEGER1 = INSERT, 0 = UPDATE (also used for ALTER TABLE), -1 = DELETE (also covers DROP TABLE, DROP INDEX)
table_nameTEXTName of the affected table. For DDL changes (CREATE TABLE, etc.), this is always sqlite_schema.
idINTEGERRowid of the affected row. For DDL operations, the rowid of the sqlite_schema entry.
beforeBLOBFull row state before an UPDATE or DELETE. NULL for INSERT. For DDL changes, may contain the object definition before modification.
afterBLOBFull row state after an INSERT or UPDATE. NULL for DELETE. For DDL changes, may contain the object definition after modification.
updatesBLOBGranular column-level details for UPDATE operations.
WITHOUT ROWID tables are not supported with CDC.

CDC with DDL changes

CDC also records schema changes. When you create or drop a table, the change is logged with table_name = 'sqlite_schema':
  • CREATE TABLEchange_type = 1 (INSERT into sqlite_schema)
  • ALTER TABLEchange_type = 0 (UPDATE of sqlite_schema)
  • DROP TABLE, DROP INDEXchange_type = -1 (DELETE from sqlite_schema)

Behavior notes

  • CDC records are visible even before a transaction commits.
  • Operations that fail due to constraint violations are not recorded.
  • Changes to the CDC table itself are also logged if CDC is enabled for that connection.

Performance considerations

In full mode, each update of size N bytes is written three times to disk: once for the before state, once for the after state, and once for the actual value in the WAL. Frequent updates in full mode can significantly increase disk I/O.
Choose the mode appropriate for your workload:
  • Use id mode when you only need to know which rows changed.
  • Use before or after mode when you need one side of the change.
  • Use full mode only when you need a complete audit trail and can tolerate the additional I/O.

Schema evolution and CDC

If you modify a table’s schema (adding or dropping columns), the table_columns_json_array() function returns the current schema, not the historical one at the time a CDC record was written. This can cause incorrect results when decoding older CDC records. Manually track schema versions by storing the output of table_columns_json_array() before making schema changes.

Example

-- Enable full CDC for this connection
PRAGMA capture_data_changes_conn('full');

-- Create a table (logged as a DDL change)
CREATE TABLE users (
    id   INTEGER PRIMARY KEY,
    name TEXT
);

-- Perform data changes
INSERT INTO users VALUES (1, 'John'), (2, 'Jane');
UPDATE users SET name = 'John Doe' WHERE id = 1;
DELETE FROM users WHERE id = 2;

-- Read the CDC log
SELECT change_id, change_type, table_name, id
FROM turso_cdc
ORDER BY change_id;
The turso_cdc table will contain:
change_idchange_typetable_nameidDescription
11sqlite_schema2CREATE TABLE users
21users1INSERT id=1
31users2INSERT id=2
40users1UPDATE id=1
5-1users2DELETE id=2

Build docs developers (and LLMs) love