# Audit Filesystem Changes
Source: https://docs.turso.tech/agentfs/guides/auditing

Inspect and analyze what an agent did during a session

AgentFS records every file operation and tool call, giving you complete visibility into agent behavior. This is essential for debugging, compliance, and understanding how agents work.

## Viewing the Timeline

See a chronological list of tool calls:

```bash theme={null}
agentfs timeline my-session
```

Output:

```
ID   TOOL                 STATUS       DURATION STARTED
4    execute_code         pending            -- 2024-01-05 09:44:20
3    api_call             error           300ms 2024-01-05 09:44:15
2    read_file            success          50ms 2024-01-05 09:44:10
1    web_search           success        1200ms 2024-01-05 09:43:45
```

### Filtering Results

Show only specific tool types:

```bash theme={null}
agentfs timeline my-session --filter web_search
```

Show only errors:

```bash theme={null}
agentfs timeline my-session --status error
```

Limit number of entries:

```bash theme={null}
agentfs timeline my-session --limit 20
```

### JSON Output

For programmatic analysis:

```bash theme={null}
agentfs timeline my-session --format json
```

```json theme={null}
[
  {
    "id": 1,
    "name": "web_search",
    "status": "success",
    "started_at": 1704447825,
    "completed_at": 1704447826,
    "duration_ms": 1200,
    "parameters": {"query": "AI agents"},
    "result": {"results": ["result1", "result2"]}
  }
]
```

## Inspecting Files

List all files in a session:

```bash theme={null}
agentfs fs ls my-session
```

Output:

```
d artifacts
f config.json
f output.txt
```

List a subdirectory:

```bash theme={null}
agentfs fs ls my-session /artifacts
```

Read file contents:

```bash theme={null}
agentfs fs cat my-session /output.txt
```

## Viewing Changes (Diff)

See what changed compared to the original filesystem:

```bash theme={null}
agentfs diff my-session
```

This shows:

* New files created
* Modified files
* Deleted files

## Querying with SQL

Since AgentFS uses SQLite, you can run arbitrary queries:

<Tabs>
  <Tab title="Turso">
    ```bash theme={null}
    tursodb .agentfs/my-session.db
    ```
  </Tab>

  <Tab title="SQLite">
    ```bash theme={null}
    sqlite3 .agentfs/my-session.db
    ```
  </Tab>
</Tabs>

Example queries:

```sql theme={null}
-- Find all tool calls that took longer than 1 second
SELECT name, duration_ms
FROM toolcalls
WHERE duration_ms > 1000;

-- Count tool usage by type
SELECT name, COUNT(*) as count
FROM toolcalls
GROUP BY name
ORDER BY count DESC;

-- Find files modified in the last hour
SELECT path, mtime
FROM fs_inode
WHERE mtime > strftime('%s', 'now', '-1 hour');

-- Get total bytes written
SELECT SUM(size) as total_bytes
FROM fs_inode
WHERE mode & 0170000 = 0100000;  -- regular files only
```

## Use Cases

### Debugging Agent Failures

When an agent fails, use the timeline to understand what happened:

```bash theme={null}
# See recent activity
agentfs timeline my-session --limit 20

# Focus on errors
agentfs timeline my-session --status error

# Check what files were created/modified
agentfs fs ls my-session
```

### Performance Analysis

Find slow operations:

```sql theme={null}
SELECT name, AVG(duration_ms) as avg_ms, COUNT(*) as count
FROM toolcalls
GROUP BY name
ORDER BY avg_ms DESC;
```

### Compliance Auditing

Export a complete record of agent activity:

```bash theme={null}
agentfs timeline my-session --format json > audit-log.json
```

## Next Steps

<CardGroup>
  <Card title="Overlay Filesystem" icon="layer-group" href="/agentfs/guides/overlay">
    Understanding copy-on-write
  </Card>

  <Card title="Syncing" icon="cloud" href="/agentfs/guides/sync">
    Sync sessions to Turso Cloud
  </Card>
</CardGroup>


# MCP Server Integration
Source: https://docs.turso.tech/agentfs/guides/mcp

Use AgentFS as an MCP server for AI assistants

AgentFS can run as an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server, exposing filesystem and key-value operations to AI assistants like Claude Desktop.

## Starting the MCP Server

```bash theme={null}
agentfs serve mcp my-agent
```

This starts an MCP server that exposes AgentFS tools over stdio.

## Available Tools

### Filesystem Tools

| Tool         | Description                   |
| ------------ | ----------------------------- |
| `read_file`  | Read file contents            |
| `write_file` | Write file contents           |
| `readdir`    | List directory contents       |
| `mkdir`      | Create directory              |
| `remove`     | Remove file or directory      |
| `rename`     | Rename file or directory      |
| `stat`       | Get file metadata             |
| `access`     | Check file access permissions |

### Key-Value Tools

| Tool        | Description        |
| ----------- | ------------------ |
| `kv_get`    | Get value by key   |
| `kv_set`    | Set key-value pair |
| `kv_delete` | Delete key         |
| `kv_list`   | List all keys      |

## Limiting Exposed Tools

For security, you can limit which tools are available:

```bash theme={null}
# Read-only mode
agentfs serve mcp my-agent --tools read_file,readdir,stat,kv_get,kv_list

# Filesystem only
agentfs serve mcp my-agent --tools read_file,write_file,readdir,mkdir,remove

# Key-value only
agentfs serve mcp my-agent --tools kv_get,kv_set,kv_delete,kv_list
```

## Security Considerations

<Warning>
  The MCP server gives AI assistants direct access to the AgentFS filesystem. Consider these security practices:

  * **Limit tools** - Only expose tools the assistant needs
  * **Use overlays** - Create overlay filesystems instead of giving access to real directories
  * **Review the agent database** - Check `.agentfs/*.db` periodically
</Warning>

## Debugging

Check if the MCP server is working:

```bash theme={null}
# Run manually to see output
agentfs serve mcp my-agent

# Check if the database exists
ls -la .agentfs/my-agent.db
```

## Next Steps

<CardGroup>
  <Card title="NFS Server" icon="network-wired" href="/agentfs/guides/nfs">
    Remote access via NFS
  </Card>

  <Card title="Copy-on-Write Overlays" icon="layer-group" href="/agentfs/guides/overlay">
    How isolation works
  </Card>
</CardGroup>


# NFS Server Access
Source: https://docs.turso.tech/agentfs/guides/nfs

Export AgentFS over the network via NFS

AgentFS can expose filesystems over NFS, enabling remote access from other machines, containers, or virtual machines.

## Starting the NFS Server

```bash theme={null}
agentfs serve nfs my-agent
```

By default, this listens on `127.0.0.1:11111`.

### Binding to All Interfaces

To allow remote access:

```bash theme={null}
agentfs serve nfs my-agent --bind 0.0.0.0 --port 2049
```

<Warning>
  Exposing NFS on `0.0.0.0` makes the filesystem accessible to anyone who can reach your machine. Use firewall rules or a VPN for production deployments.
</Warning>

## Mounting the Filesystem

### From the Same Machine

```bash theme={null}
mkdir /mnt/agentfs
mount -t nfs -o vers=3,tcp,port=11111,mountport=11111,nolock 127.0.0.1:/ /mnt/agentfs
```

### From a Remote Machine

```bash theme={null}
mount -t nfs -o vers=3,tcp,port=2049,mountport=2049,nolock server-ip:/ /mnt/agentfs
```

### Mount Options Explained

| Option            | Description                        |
| ----------------- | ---------------------------------- |
| `vers=3`          | Use NFSv3 protocol                 |
| `tcp`             | Use TCP transport                  |
| `port=11111`      | NFS server port                    |
| `mountport=11111` | Mount protocol port                |
| `nolock`          | Disable file locking (single-user) |

## Unmounting

```bash theme={null}
umount /mnt/agentfs
```

If the mount is busy:

```bash theme={null}
umount -f /mnt/agentfs  # Force unmount
# or
umount -l /mnt/agentfs  # Lazy unmount
```

## Next Steps

<CardGroup>
  <Card title="MCP Server" icon="plug" href="/agentfs/guides/mcp">
    Expose AgentFS via MCP
  </Card>

  <Card title="Sync with Turso Cloud" icon="cloud" href="/agentfs/guides/sync">
    Back up and share agent state
  </Card>
</CardGroup>


# Copy-on-Write Overlays
Source: https://docs.turso.tech/agentfs/guides/overlay

How copy-on-write isolation works in AgentFS

AgentFS uses an overlay filesystem to provide copy-on-write isolation. This lets agents freely modify files while keeping your original data safe.

## How Overlay Works

An overlay filesystem combines two layers:

```
┌────────────────────────────────────┐
│         Merged View (what you see) │
├────────────────────────────────────┤
│  Delta Layer (AgentFS database)    │  ← Writes go here
├────────────────────────────────────┤
│  Base Layer (original directory)   │  ← Read-only
└────────────────────────────────────┘
```

**Reading a file:**

1. Check delta layer first
2. If not found, read from base layer

**Writing a file:**

1. Copy file to delta layer (if from base)
2. Write changes to delta layer
3. Base layer is never modified

**Deleting a file:**

1. Create a "whiteout" marker in delta layer
2. File appears deleted in merged view
3. Original file in base layer is untouched

## Creating an Overlay Filesystem

### With `agentfs run`

The simplest way - automatically creates an overlay over your current directory:

```bash theme={null}
cd /path/to/project
agentfs run /bin/bash
```

### With `agentfs init --base`

Create an overlay explicitly:

```bash theme={null}
agentfs init my-overlay --base /path/to/project
```

Then mount it:

```bash theme={null}
agentfs mount my-overlay ./workspace
```

Or run a command in it:

```bash theme={null}
agentfs run --session my-overlay /bin/bash
```

## Viewing Changes

See what's different from the base:

```bash theme={null}
agentfs diff my-overlay
```

## Practical Examples

### Safe Refactoring

Let an agent refactor your code without risk:

```bash theme={null}
cd my-project
agentfs run --session refactor python3 refactor_agent.py

# Review what changed
agentfs diff refactor

# Happy? Apply changes manually or via script
# Not happy? Just delete the session
rm .agentfs/refactor.db
```

### Testing Destructive Operations

Try something dangerous safely:

```bash theme={null}
agentfs run --session test /bin/bash
$ rm -rf src/  # Yikes!
$ exit

# Original files are fine
ls src/  # Still there!

# Only the overlay was affected
agentfs fs ls test  # Shows the deletion
```

### Parallel Experiments

Run multiple experiments on the same codebase:

```bash theme={null}
# Experiment A
agentfs run --session exp-a python3 approach_a.py

# Experiment B (same base, different changes)
agentfs run --session exp-b python3 approach_b.py

# Compare results
agentfs diff exp-a
agentfs diff exp-b
```

## Storage Efficiency

The overlay only stores:

* **Modified files** - Full copy after first write
* **New files** - Stored entirely in delta
* **Deleted files** - Small whiteout marker

Original files are never duplicated. A 10GB project with 1MB of changes only uses \~1MB in the delta layer.

## Technical Details

### File Operations

| Operation           | Behavior                  |
| ------------------- | ------------------------- |
| Read existing file  | Pass through to base      |
| Write existing file | Copy to delta, then write |
| Create new file     | Write directly to delta   |
| Delete file         | Create whiteout in delta  |
| Rename file         | Delete old + create new   |

### Whiteouts

When you delete a base file, AgentFS creates a "whiteout" - a marker that hides the file:

```sql theme={null}
-- Whiteouts in the database
SELECT * FROM fs_inode WHERE whiteout = 1;
```

### Database Schema

The delta layer is stored in SQLite:

```sql theme={null}
-- Files and directories
CREATE TABLE fs_inode (
    ino INTEGER PRIMARY KEY,
    parent_ino INTEGER,
    name TEXT,
    mode INTEGER,
    size INTEGER,
    ...
    whiteout INTEGER DEFAULT 0
);

-- File contents
CREATE TABLE fs_block (
    ino INTEGER,
    block_num INTEGER,
    data BLOB
);
```

See the [AgentFS Specification](https://github.com/tursodatabase/agentfs/blob/main/SPEC.md) for the complete schema.

## Next Steps

<CardGroup>
  <Card title="Sessions" icon="clock" href="/agentfs/guides/sessions">
    Share state between agents
  </Card>

  <Card title="Syncing" icon="cloud" href="/agentfs/guides/sync">
    Sync overlays to Turso Cloud
  </Card>
</CardGroup>


# Agentic Coding
Source: https://docs.turso.tech/agentfs/guides/sandbox

Run AI coding agents safely with AgentFS sandboxing

AgentFS lets you run AI coding agents like Claude Code, Codex, and OpenCode in a sandboxed environment where file changes are isolated from your original source tree.

## Supported Agents

| Agent                                                   | Command                 |
| ------------------------------------------------------- | ----------------------- |
| [Claude Code](https://code.claude.com/docs/en/overview) | `agentfs run claude`    |
| [Codex](https://openai.com/codex/)                      | `agentfs run codex`     |
| [OpenCode](https://opencode.ai/)                        | `agentfs run opencode`  |
| Any CLI tool                                            | `agentfs run <command>` |

## Workflow

Here's a typical agentic coding workflow. This example uses Claude Code, but the workflow is the same with OpenCode, Codex, and others.

### Step 1: Clone the Repository

Clone the git repository to a host directory. Keep this source tree clean from build artifacts. When there's a merge to main, pull to this repository to keep it up-to-date.

```bash theme={null}
git clone https://github.com/tursodatabase/agentfs.git
cd agentfs
```

### Step 2: Start an AgentFS Session

Use `agentfs run` with the `--session` flag to start a session. Pick a name that can eventually become your git branch name.

<Tabs>
  <Tab title="Claude Code">
    ```bash theme={null}
    agentfs run --session fix-bug-123 claude
    ```
  </Tab>

  <Tab title="Codex">
    ```bash theme={null}
    agentfs run --session fix-bug-123 codex
    ```
  </Tab>

  <Tab title="OpenCode">
    ```bash theme={null}
    agentfs run --session fix-bug-123 opencode
    ```
  </Tab>
</Tabs>

The agent now runs in a copy-on-write environment. Your original source tree remains untouched while the agent works.

### Step 3 (Optional): Manual Investigation

If the agent does something unusual, open another terminal and join the same session to investigate:

```bash theme={null}
agentfs run --session fix-bug-123 bash
```

The filesystem is shared between the shell and the agent. You can browse files, run tests, or check what's happening while the agent works.

### Step 4: Push to Branch

Once the agent has completed the work, ask it to create a branch, commit the code, and push:

> "Create a branch called fix-bug-123, commit these changes, and push to origin"

You can then create a PR manually from the pushed branch.

## Why This Workflow?

* **Clean source tree** - Your original clone stays pristine and easy to update from upstream
* **Isolated changes** - Each task gets its own session, preventing cross-contamination
* **Easy investigation** - Join any session to see what's happening in real-time
* **Standard git flow** - Push branches and create PRs as usual

## Next Steps

<CardGroup>
  <Card title="Multi-Agent Sessions" icon="clock" href="/agentfs/guides/sessions">
    Share state between agents and terminals
  </Card>

  <Card title="Auditing" icon="magnifying-glass" href="/agentfs/guides/auditing">
    Inspect what an agent did with diff and timeline
  </Card>
</CardGroup>


# Shared Agent Sessions
Source: https://docs.turso.tech/agentfs/guides/sessions

Share state between multiple agents and terminals with named sessions

Named sessions let you share a filesystem between multiple agents, terminals, or runs. All participants see the same copy-on-write view of your project.

## Creating a Session

Use `--session` to create a named session:

```bash theme={null}
agentfs run --session my-project /bin/bash
```

This creates a session called `my-project`. All changes are stored in `.agentfs/my-project.db`.

## Resuming a Session

Run the same command to resume where you left off:

```bash theme={null}
# First run - make some changes
agentfs run --session my-project /bin/bash
$ echo "hello" > test.txt
$ exit

# Second run - changes are still there
agentfs run --session my-project /bin/bash
$ cat test.txt
hello
```

## Multi-Terminal Collaboration

Multiple terminals can share the same session simultaneously:

```bash theme={null}
# Terminal 1
agentfs run --session shared-work /bin/bash

# Terminal 2 - joins the same filesystem
agentfs run --session shared-work python3 agent.py

# Terminal 3 - also sees the same files
agentfs run --session shared-work vim
```

All terminals share the same copy-on-write filesystem. Changes made in one terminal are immediately visible in others.

## Use Cases

### Iterative Development

Work on a task across multiple sessions:

```bash theme={null}
# Morning session
agentfs run --session feature-x /bin/bash
# ... work on the feature, then exit for lunch

# Afternoon session - continue where you left off
agentfs run --session feature-x /bin/bash
```

### Safe Experimentation

Create a session, try something risky, then decide whether to keep it:

```bash theme={null}
agentfs run --session experiment /bin/bash
# ... try some changes

# Option A: Happy with changes
agentfs diff experiment  # Review
# Apply changes to real filesystem (manually or via script)

# Option B: Discard everything
rm .agentfs/experiment.db
```

### Agent Supervision

Run an agent and monitor its work from another terminal:

```bash theme={null}
# Terminal 1: Run the agent
agentfs run --session agent-task python3 agent.py

# Terminal 2: Watch what it's doing
agentfs run --session agent-task /bin/bash
$ watch ls -la  # See files being created
$ tail -f agent.log  # Monitor logs
```

## Managing Sessions

List files in a session:

```bash theme={null}
agentfs fs ls my-session
```

View session changes:

```bash theme={null}
agentfs diff my-session
```

Delete a session:

```bash theme={null}
rm .agentfs/my-session.db
```

## Session Storage

Sessions are stored as SQLite databases in `.agentfs/`:

```
.agentfs/
├── my-project.db
├── experiment.db
└── shared-work.db
```

Each database contains:

* All modified files
* Deleted file markers (whiteouts)
* Tool call audit log
* Key-value store data

## Next Steps

<CardGroup>
  <Card title="Auditing" icon="list" href="/agentfs/guides/auditing">
    Inspect what happened in a session
  </Card>

  <Card title="Overlay Filesystem" icon="layer-group" href="/agentfs/guides/overlay">
    How copy-on-write works
  </Card>
</CardGroup>


# Sync with Turso Cloud
Source: https://docs.turso.tech/agentfs/guides/sync

Synchronize agent filesystems with remote Turso databases

AgentFS can sync with [Turso Cloud](https://turso.tech), enabling backup, collaboration, and distributed agent deployments.

## Setting Up Sync

### 1. Create a Turso Database

If you don't have a Turso account, [sign up](https://turso.tech) first.

Create a database for your agent:

```bash theme={null}
turso db create my-agent-db
```

Get the database URL:

```bash theme={null}
turso db show my-agent-db --url
# Output: libsql://my-agent-db-username.turso.io
```

### 2. Initialize with Sync

Create a new agent filesystem with sync enabled:

```bash theme={null}
agentfs init my-agent --sync-remote-url libsql://my-agent-db-username.turso.io
```

Or add sync to an existing agent:

```bash theme={null}
# Edit the database to add sync configuration
# (Feature coming soon)
```

## Sync Commands

### Pull Remote Changes

Download changes from Turso Cloud:

```bash theme={null}
agentfs sync my-agent pull
```

### Push Local Changes

Upload local changes to Turso Cloud:

```bash theme={null}
agentfs sync my-agent push
```

### View Sync Status

Check sync statistics:

```bash theme={null}
agentfs sync my-agent stats
```

### Create Checkpoint

Force a sync checkpoint:

```bash theme={null}
agentfs sync my-agent checkpoint
```

## Use Cases

### Backup and Recovery

Automatically back up agent state:

```bash theme={null}
# After important work
agentfs sync my-agent push

# On a new machine
agentfs init my-agent --sync-remote-url libsql://...
agentfs sync my-agent pull
```

### Multi-Machine Agents

Run agents on multiple machines with shared state:

```bash theme={null}
# Machine A
agentfs run --session shared-agent python3 agent.py
agentfs sync shared-agent push

# Machine B
agentfs sync shared-agent pull
agentfs run --session shared-agent python3 agent.py
```

## Partial Sync

For large filesystems, use partial sync to only fetch what's needed:

```bash theme={null}
agentfs init my-agent \
  --sync-remote-url libsql://... \
  --sync-partial-prefetch \
  --sync-partial-segment-size 1000
```

### Partial Sync Options

| Option                            | Description                   |
| --------------------------------- | ----------------------------- |
| `--sync-partial-prefetch`         | Enable prefetching            |
| `--sync-partial-segment-size`     | Segment size for partial sync |
| `--sync-partial-bootstrap-query`  | Custom bootstrap query        |
| `--sync-partial-bootstrap-length` | Bootstrap prefix length       |

## Authentication

Sync requires a Turso auth token. Set it via environment variable:

```bash theme={null}
export TURSO_AUTH_TOKEN=$(turso db tokens create my-agent-db)
```

Or create a long-lived token:

```bash theme={null}
turso db tokens create my-agent-db --expiration none
```

## Conflict Resolution

AgentFS uses last-write-wins for conflicts. If the same file is modified on multiple machines:

1. Push from Machine A succeeds
2. Push from Machine B fails (conflict)
3. Machine B must pull first, then push

## Next Steps

<CardGroup>
  <Card title="Turso Cloud" icon="cloud" href="https://turso.tech">
    Learn more about Turso Cloud
  </Card>

  <Card title="NFS Server" icon="network-wired" href="/agentfs/guides/nfs">
    Remote access via NFS
  </Card>
</CardGroup>


# Installation
Source: https://docs.turso.tech/agentfs/installation

Install the AgentFS CLI on Linux, macOS, and Windows

## Quick Install

The fastest way to install AgentFS:

```bash theme={null}
curl -fsSL https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.sh | sh
```

This installs the `agentfs` binary to `~/.cargo/bin`.

## Platform-Specific Instructions

<Tabs>
  <Tab title="macOS">
    ```bash theme={null}
    curl -fsSL https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.sh | sh
    ```

    After installation, restart your terminal or run:

    ```bash theme={null}
    source ~/.zshrc  # or ~/.bashrc
    ```
  </Tab>

  <Tab title="Linux">
    **Using the installer:**

    ```bash theme={null}
    curl -fsSL https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.sh | sh
    ```

    After installation, restart your terminal or run:

    ```bash theme={null}
    source ~/.bashrc  # or ~/.zshrc
    ```

    **Requirements:**

    * FUSE must be available for filesystem mounting
    * On Ubuntu/Debian: `sudo apt install fuse3`
    * On Fedora: `sudo dnf install fuse3`
  </Tab>

  <Tab title="Windows">
    **Using PowerShell:**

    ```powershell theme={null}
    irm https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.ps1 | iex
    ```

    Or download the binary directly from the [releases page](https://github.com/tursodatabase/agentfs/releases).
  </Tab>
</Tabs>

## Verify Installation

```bash theme={null}
agentfs --version
```

## Shell Completions

Enable tab completion for your shell:

```bash theme={null}
# Auto-detect shell
agentfs completions install

# Or specify shell explicitly
agentfs completions install bash
agentfs completions install zsh
agentfs completions install fish
```

Restart your shell after installing completions.

## Building from Source

Requires Rust 1.70+:

```bash theme={null}
git clone https://github.com/tursodatabase/agentfs.git
cd agentfs/cli
cargo install --path .
```

## Next Steps

<CardGroup>
  <Card title="Agentic Coding" icon="robot" href="/agentfs/guides/sandbox">
    Run AI coding agents safely
  </Card>

  <Card title="CLI Reference" icon="terminal" href="/agentfs/reference/cli">
    Full command reference
  </Card>
</CardGroup>


# AgentFS
Source: https://docs.turso.tech/agentfs/introduction

A filesystem for AI agents, built on Turso

AgentFS is a filesystem for AI agents. It provides copy-on-write isolation, letting agents safely modify files while keeping your original data untouched.
It is available both as a CLI, that wraps an existing program in a sandboxed session, and an SDK that allows you to build custom agents with a filesystem
abstraction.

## Key Features

* **Copy-on-Write Isolation** — Run agents in sandboxed environments where changes are isolated from your source tree
* **Single File Storage** — Everything stored in one portable SQLite database for easy sharing and snapshotting
* **Built-in Auditing** — Every file operation is recorded and queryable
* **Cloud Sync** — Optionally sync agent state to Turso Cloud

## Quick Start

```bash theme={null}
# Install
curl -fsSL https://agentfs.ai/install | bash

# Run a shell in an isolated environment
cd /path/to/project
agentfs run bash

# Any changes are isolated - your original files are safe
```

## Next Steps

<CardGroup>
  <Card title="Installation" icon="download" href="/agentfs/installation">
    Install the AgentFS CLI
  </Card>

  <Card title="Agentic Coding" icon="robot" href="/agentfs/guides/sandbox">
    Run AI coding agents safely
  </Card>

  <Card title="AgentFS SDKs" icon="robot" href="/agentfs/sdk">
    Build custom agents with the AgentFS SDKs
  </Card>
</CardGroup>

<Warning>
  This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.
</Warning>


# CLI Reference
Source: https://docs.turso.tech/agentfs/reference/cli

Complete reference for the AgentFS command-line interface

Complete reference for the `agentfs` command-line tool.

<Info>
  For the latest reference, see [MANUAL.md](https://github.com/tursodatabase/agentfs/blob/main/MANUAL.md) in the repository.
</Info>

## Installation

```bash theme={null}
curl -fsSL https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.sh | sh
```

## Commands

### agentfs init

Initialize a new agent filesystem.

```bash theme={null}
agentfs init [OPTIONS] [ID]
```

**Arguments:**

* `ID` - Agent identifier (default: `agent-{timestamp}`)

**Options:**

| Option                    | Description                           |
| ------------------------- | ------------------------------------- |
| `--force`                 | Overwrite existing agent filesystem   |
| `--base <PATH>`           | Base directory for overlay filesystem |
| `--sync-remote-url <URL>` | Remote Turso database URL             |

**Example:**

```bash theme={null}
agentfs init my-agent
agentfs init my-overlay --base /path/to/project
```

***

### agentfs run

Execute a program in a sandboxed environment.

```bash theme={null}
agentfs run [OPTIONS] <COMMAND> [ARGS]...
```

**Options:**

| Option                   | Description                         |
| ------------------------ | ----------------------------------- |
| `--session <ID>`         | Named session for persistence       |
| `--allow <PATH>`         | Allow write access to directory     |
| `--no-default-allows`    | Disable default allowed directories |
| `--experimental-sandbox` | Use ptrace sandbox (Linux)          |
| `--strace`               | Show intercepted syscalls           |

**Examples:**

```bash theme={null}
agentfs run /bin/bash
agentfs run --session my-project python3 agent.py
agentfs run --allow /tmp --allow ~/.cache /bin/bash
```

***

### agentfs mount

Mount an agent filesystem or list mounts.

```bash theme={null}
agentfs mount [OPTIONS] [ID_OR_PATH] [MOUNT_POINT]
```

Without arguments, lists all mounted filesystems.

**Options:**

| Option               | Description                   |
| -------------------- | ----------------------------- |
| `-a, --auto-unmount` | Automatically unmount on exit |
| `--allow-root`       | Allow root user access        |
| `-f, --foreground`   | Run in foreground             |
| `--uid <UID>`        | User ID for files             |
| `--gid <GID>`        | Group ID for files            |

**Examples:**

```bash theme={null}
agentfs mount                              # List mounts
agentfs mount my-agent ./mnt               # Mount agent
agentfs mount my-agent ./mnt -a -f         # Foreground with auto-unmount
```

**Unmounting:**

* Linux: `fusermount -u <MOUNT_POINT>`
* macOS: `umount <MOUNT_POINT>`

***

### agentfs serve mcp

Start an MCP (Model Context Protocol) server.

```bash theme={null}
agentfs serve mcp <ID_OR_PATH> [OPTIONS]
```

**Options:**

| Option            | Description                   |
| ----------------- | ----------------------------- |
| `--tools <TOOLS>` | Comma-separated list of tools |

**Available Tools:**

| Category   | Tools                                                                               |
| ---------- | ----------------------------------------------------------------------------------- |
| Filesystem | `read_file`, `write_file`, `readdir`, `mkdir`, `remove`, `rename`, `stat`, `access` |
| Key-Value  | `kv_get`, `kv_set`, `kv_delete`, `kv_list`                                          |

**Examples:**

```bash theme={null}
agentfs serve mcp my-agent
agentfs serve mcp my-agent --tools read_file,readdir,stat
```

***

### agentfs serve nfs

Start an NFS server.

```bash theme={null}
agentfs serve nfs <ID_OR_PATH> [OPTIONS]
```

**Options:**

| Option          | Description                       |
| --------------- | --------------------------------- |
| `--bind <IP>`   | IP to bind (default: `127.0.0.1`) |
| `--port <PORT>` | Port (default: `11111`)           |

**Example:**

```bash theme={null}
agentfs serve nfs my-agent --bind 0.0.0.0 --port 2049
```

**Mounting:**

```bash theme={null}
mount -t nfs -o vers=3,tcp,port=11111,mountport=11111,nolock localhost:/ /mnt
```

***

### agentfs sync

Synchronize with a remote Turso database.

```bash theme={null}
agentfs sync <ID_OR_PATH> <SUBCOMMAND>
```

**Subcommands:**

| Command      | Description          |
| ------------ | -------------------- |
| `pull`       | Pull remote changes  |
| `push`       | Push local changes   |
| `stats`      | View sync statistics |
| `checkpoint` | Create checkpoint    |

**Example:**

```bash theme={null}
agentfs sync my-agent pull
agentfs sync my-agent push
```

***

### agentfs fs

Filesystem operations on agent databases.

#### agentfs fs ls

```bash theme={null}
agentfs fs ls <ID_OR_PATH> [FS_PATH]
```

List files. Output: `f <name>` for files, `d <name>` for directories.

#### agentfs fs cat

```bash theme={null}
agentfs fs cat <ID_OR_PATH> <FILE_PATH>
```

Display file contents.

#### agentfs fs write

```bash theme={null}
agentfs fs write <ID_OR_PATH> <FILE_PATH> <CONTENT>
```

Write content to a file.

**Examples:**

```bash theme={null}
agentfs fs ls my-agent
agentfs fs cat my-agent /config.json
agentfs fs write my-agent /hello.txt "Hello, world!"
```

***

### agentfs diff

Show filesystem changes in overlay mode.

```bash theme={null}
agentfs diff <ID_OR_PATH>
```

***

### agentfs timeline

Display agent action timeline.

```bash theme={null}
agentfs timeline [OPTIONS] <ID_OR_PATH>
```

**Options:**

| Option              | Description                           |
| ------------------- | ------------------------------------- |
| `--limit <N>`       | Limit entries (default: 100)          |
| `--filter <TOOL>`   | Filter by tool name                   |
| `--status <STATUS>` | Filter: `pending`, `success`, `error` |
| `--format <FORMAT>` | Output: `table`, `json`               |

**Examples:**

```bash theme={null}
agentfs timeline my-agent
agentfs timeline my-agent --filter web_search --status error
agentfs timeline my-agent --format json
```

***

### agentfs completions

Manage shell completions.

```bash theme={null}
agentfs completions install [SHELL]
agentfs completions uninstall [SHELL]
agentfs completions show
```

Supported: `bash`, `zsh`, `fish`, `powershell`

## Environment Variables

Variables set inside the sandbox:

| Variable          | Description               |
| ----------------- | ------------------------- |
| `AGENTFS`         | Set to `1` inside sandbox |
| `AGENTFS_SANDBOX` | Sandbox type              |
| `AGENTFS_SESSION` | Session ID                |

## Files

| Path                 | Description               |
| -------------------- | ------------------------- |
| `.agentfs/<ID>.db`   | Agent filesystem database |
| `~/.config/agentfs/` | Configuration directory   |

## See Also

<CardGroup>
  <Card title="Installation" icon="download" href="/agentfs/installation">
    Installation instructions
  </Card>

  <Card title="Sandbox Guide" icon="shield" href="/agentfs/guides/sandbox">
    Running agents in sandboxes
  </Card>
</CardGroup>


# Python SDK
Source: https://docs.turso.tech/agentfs/sdk/python

AgentFS Python SDK for building AI agents

The AgentFS Python SDK provides an async interface for building AI agents with persistent storage.

## Installation

```bash theme={null}
pip install agentfs-sdk
```

## Quick Start

```python theme={null}
import asyncio
from agentfs_sdk import AgentFS, AgentFSOptions

async def main():
    # Open an agent filesystem
    agent = await AgentFS.open(AgentFSOptions(id='my-agent'))

    # Use key-value store
    await agent.kv.set('config', {'debug': True})
    config = await agent.kv.get('config')

    # Use filesystem
    await agent.fs.write_file('/notes.txt', 'Hello, AgentFS!')
    content = await agent.fs.read_file('/notes.txt')

    # Track tool calls
    call_id = await agent.tools.start('search', {'query': 'Python'})
    await agent.tools.success(call_id, {'results': ['result1']})

    await agent.close()

asyncio.run(main())
```

## Opening a Filesystem

### By Agent ID

Creates database at `.agentfs/{id}.db`:

```python theme={null}
agent = await AgentFS.open(AgentFSOptions(id='my-agent'))
```

### By Path

Specify a custom database path:

```python theme={null}
agent = await AgentFS.open(AgentFSOptions(path='./data/mydb.db'))
```

### Context Manager

Automatically closes the database:

```python theme={null}
async with await AgentFS.open(AgentFSOptions(id='my-agent')) as agent:
    await agent.kv.set('key', 'value')
    # Database is closed when exiting
```

## Key-Value Store

Simple key-value storage with JSON serialization.

### Set a Value

```python theme={null}
await agent.kv.set('user:123', {'name': 'Alice', 'age': 30})
await agent.kv.set('counter', 42)
await agent.kv.set('active', True)
```

### Get a Value

```python theme={null}
user = await agent.kv.get('user:123')
# Returns None if key doesn't exist
```

### List Keys

```python theme={null}
# List all keys with prefix
keys = await agent.kv.list('user:')
# Returns: ['user:123', 'user:456', ...]
```

### Delete a Key

```python theme={null}
await agent.kv.delete('user:123')
```

## Filesystem

POSIX-like filesystem operations.

### Write a File

Creates parent directories automatically:

```python theme={null}
await agent.fs.write_file('/data/config.json', '{"key": "value"}')

# Write bytes
await agent.fs.write_file('/data/image.png', image_bytes)
```

### Read a File

```python theme={null}
# Read as string (default)
content = await agent.fs.read_file('/data/config.json')

# Read as bytes
data = await agent.fs.read_file('/data/image.png', encoding=None)
```

### List Directory

```python theme={null}
entries = await agent.fs.readdir('/data')
# Returns: ['config.json', 'image.png', ...]
```

### Get File Stats

```python theme={null}
stats = await agent.fs.stat('/data/config.json')
print(f"Size: {stats.size} bytes")
print(f"Modified: {stats.mtime}")
print(f"Is file: {stats.is_file()}")
print(f"Is directory: {stats.is_dir()}")
```

### Delete a File

```python theme={null}
await agent.fs.delete_file('/data/config.json')
```

### Create Directory

```python theme={null}
await agent.fs.mkdir('/data/subdir')
```

## Tool Calls

Track and analyze tool/function calls for debugging and auditing.

### Start and Complete

```python theme={null}
# Start a tool call
call_id = await agent.tools.start('search', {'query': 'Python'})

# ... perform the operation ...

# Mark as successful
await agent.tools.success(call_id, {'results': ['result1', 'result2']})

# Or mark as failed
await agent.tools.error(call_id, 'Connection timeout')
```

### Record Completed Call

If you have start and end times:

```python theme={null}
await agent.tools.record(
    name='search',
    started_at=1234567890.0,
    completed_at=1234567892.0,
    parameters={'query': 'Python'},
    result={'results': ['result1']}
)
```

### Query Tool Calls

```python theme={null}
# Get by name
calls = await agent.tools.get_by_name('search', limit=10)

# Get recent calls
recent = await agent.tools.get_recent(since=1234567890.0)

# Get by ID
call = await agent.tools.get(42)
```

### Get Statistics

```python theme={null}
stats = await agent.tools.get_stats()
for stat in stats:
    print(f"{stat.name}:")
    print(f"  Total calls: {stat.total_calls}")
    print(f"  Successful: {stat.successful}")
    print(f"  Failed: {stat.failed}")
    print(f"  Avg duration: {stat.avg_duration_ms:.2f}ms")
```

## Complete Example

```python theme={null}
import asyncio
import time
from agentfs_sdk import AgentFS, AgentFSOptions

async def main():
    async with await AgentFS.open(AgentFSOptions(id='demo-agent')) as agent:
        # Store configuration
        await agent.kv.set('agent:config', {
            'model': 'gpt-4',
            'temperature': 0.7
        })

        # Create a research document
        research = """
        # Research Notes

        ## Topic: AgentFS

        AgentFS provides persistent storage for AI agents.
        """

        await agent.fs.write_file('/research/agentfs.md', research)

        # Track a simulated tool call
        call_id = await agent.tools.start('web_search', {
            'query': 'AgentFS documentation'
        })

        # Simulate some work
        await asyncio.sleep(0.1)

        await agent.tools.success(call_id, {
            'results_count': 5,
            'top_result': 'https://docs.turso.tech/agentfs'
        })

        # List what we created
        files = await agent.fs.readdir('/research')
        print(f"Files: {files}")

        # Get tool stats
        stats = await agent.tools.get_stats()
        for stat in stats:
            print(f"{stat.name}: {stat.total_calls} calls")

asyncio.run(main())
```

## API Reference

### AgentFS

| Method                  | Description                        |
| ----------------------- | ---------------------------------- |
| `AgentFS.open(options)` | Open or create an agent filesystem |
| `agent.close()`         | Close the database connection      |
| `agent.kv`              | Key-value store interface          |
| `agent.fs`              | Filesystem interface               |
| `agent.tools`           | Tool calls interface               |

### AgentFSOptions

| Property | Type  | Description                                   |
| -------- | ----- | --------------------------------------------- |
| `id`     | `str` | Agent identifier (creates `.agentfs/{id}.db`) |
| `path`   | `str` | Custom database path                          |

### KvStore

| Method            | Description           |
| ----------------- | --------------------- |
| `set(key, value)` | Store a value         |
| `get(key)`        | Retrieve a value      |
| `delete(key)`     | Delete a key          |
| `list(prefix)`    | List keys with prefix |

### Filesystem

| Method                      | Description            |
| --------------------------- | ---------------------- |
| `write_file(path, content)` | Write file contents    |
| `read_file(path, encoding)` | Read file contents     |
| `readdir(path)`             | List directory entries |
| `stat(path)`                | Get file metadata      |
| `delete_file(path)`         | Delete a file          |
| `mkdir(path)`               | Create directory       |

### ToolCalls

| Method                     | Description             |
| -------------------------- | ----------------------- |
| `start(name, params)`      | Start tracking a call   |
| `success(id, result)`      | Mark call as successful |
| `error(id, message)`       | Mark call as failed     |
| `record(...)`              | Record a completed call |
| `get(id)`                  | Get call by ID          |
| `get_by_name(name, limit)` | Query by tool name      |
| `get_recent(since)`        | Get recent calls        |
| `get_stats()`              | Get usage statistics    |

## Next Steps

<CardGroup>
  <Card title="OpenAI Agents" icon="robot" href="/agentfs/integrations/openai-agents">
    Using AgentFS with OpenAI Agents SDK
  </Card>

  <Card title="LlamaIndex" icon="llama" href="/agentfs/integrations/llamaindex">
    Using AgentFS with LlamaIndex
  </Card>
</CardGroup>


# Rust SDK
Source: https://docs.turso.tech/agentfs/sdk/rust

Complete reference for the AgentFS Rust SDK

Build high-performance stateful AI agents with the AgentFS Rust SDK.

## Installation

Add AgentFS to your `Cargo.toml`:

```toml theme={null}
[dependencies]
agentfs = "0.1"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```

## Quick Start

```rust theme={null}
use agentfs::{AgentFS, AgentFSOptions};
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
    // Persistent storage with identifier
    let agent = AgentFS::open(AgentFSOptions::with_id("my-agent")).await?;
    // Creates: .agentfs/my-agent.db

    // Or use ephemeral in-memory database
    let ephemeral = AgentFS::open(AgentFSOptions::ephemeral()).await?;

    // Use the three main APIs
    agent.kv.set("key", "value").await?;              // Key-value store
    agent.fs.write_file("/file.txt", b"data").await?; // Filesystem
    agent.tools.record(...).await?;                   // Tool tracking

    Ok(())
}
```

## Core APIs

### AgentFS Struct

The main entry point for all AgentFS operations.

#### `AgentFS::open(options: AgentFSOptions)`

Creates or opens an AgentFS database.

```rust theme={null}
use agentfs::{AgentFS, AgentFSOptions};

// Persistent storage with identifier
let agent = AgentFS::open(AgentFSOptions::with_id("my-agent")).await?;
// Creates: .agentfs/my-agent.db

// Ephemeral in-memory database
let ephemeral = AgentFS::open(AgentFSOptions::ephemeral()).await?;
```

**AgentFSOptions Configuration:**

```rust theme={null}
pub struct AgentFSOptions {
    /// Optional unique identifier for the agent.
    /// - If Some(id): Creates persistent storage at `.agentfs/{id}.db`
    /// - If None: Uses ephemeral in-memory database
    pub id: Option<String>,
}

impl AgentFSOptions {
    /// Create options for a persistent agent with the given ID
    pub fn with_id(id: impl Into<String>) -> Self;

    /// Create options for an ephemeral in-memory agent
    pub fn ephemeral() -> Self;
}
```

#### Fields

* `kv`: Key-value store interface
* `fs`: Filesystem interface
* `tools`: Tool call tracking interface
* `db`: Direct access to the underlying Turso database

### Key-Value Store API

Fast, typed storage with Serde serialization support.

#### `kv.set<T: Serialize>(key: &str, value: T)`

Store any serializable value.

```rust theme={null}
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct UserPreferences {
    theme: String,
    language: String,
}

// Store structured data
agent.kv.set("user:preferences", UserPreferences {
    theme: "dark".to_string(),
    language: "en".to_string(),
}).await?;

// Store primitives
agent.kv.set("counter", 42).await?;
agent.kv.set("active", true).await?;
```

#### `kv.get<T: DeserializeOwned>(key: &str) -> Option<T>`

Retrieve and deserialize values.

```rust theme={null}
// Get with type inference
let prefs: Option<UserPreferences> = agent.kv.get("user:preferences").await?;

// Handle missing values
if let Some(count) = agent.kv.get::<i32>("counter").await? {
    println!("Counter: {}", count);
}

// With default
let theme = agent.kv.get::<String>("theme").await?.unwrap_or("light".to_string());
```

#### `kv.delete(key: &str)`

Remove a key-value pair.

```rust theme={null}
agent.kv.delete("session:expired").await?;
```

#### `kv.list(options: ListOptions)`

List keys with filtering and pagination.

```rust theme={null}
use agentfs::ListOptions;

// List all keys
let all_keys = agent.kv.list(ListOptions::default()).await?;

// Filter by prefix
let user_keys = agent.kv.list(ListOptions {
    prefix: Some("user:".to_string()),
    limit: Some(100),
    cursor: None,
}).await?;

// Paginate
let page1 = agent.kv.list(ListOptions {
    limit: Some(10),
    ..Default::default()
}).await?;

let page2 = agent.kv.list(ListOptions {
    limit: Some(10),
    cursor: page1.cursor,
    ..Default::default()
}).await?;
```

#### `kv.clear()`

Remove all key-value pairs.

```rust theme={null}
agent.kv.clear().await?;
```

### Filesystem API

POSIX-like filesystem operations for managing agent data.

#### `fs.write_file(path: &str, data: &[u8])`

Write bytes to a file, creating parent directories as needed.

```rust theme={null}
// Write text
agent.fs.write_file("/report.md", b"# Report\nContent...").await?;

// Write string
let content = "Hello, World!";
agent.fs.write_file("/greeting.txt", content.as_bytes()).await?;

// Write binary
let image_data = std::fs::read("./chart.png")?;
agent.fs.write_file("/images/chart.png", &image_data).await?;
```

#### `fs.read_file(path: &str) -> Vec<u8>`

Read file contents as bytes.

```rust theme={null}
// Read as bytes
let data = agent.fs.read_file("/report.md").await?;

// Convert to string
let text = String::from_utf8(data)?;

// Parse JSON
let json_data = agent.fs.read_file("/config.json").await?;
let config: serde_json::Value = serde_json::from_slice(&json_data)?;
```

#### `fs.mkdir(path: &str)`

Create a directory, optionally creating parent directories.

```rust theme={null}
// Create single directory
agent.fs.mkdir("/reports").await?;

// Create nested directories
agent.fs.mkdir_p("/reports/2024/Q4").await?;
```

#### `fs.readdir(path: &str) -> Vec<DirEntry>`

List directory contents with metadata.

```rust theme={null}
let entries = agent.fs.readdir("/reports").await?;

for entry in entries {
    println!("Name: {}", entry.name);
    println!("Type: {:?}", entry.entry_type);
    println!("Size: {} bytes", entry.size);
    println!("Modified: {:?}", entry.modified);
}

// Filter by type
let files: Vec<_> = entries
    .iter()
    .filter(|e| e.entry_type == EntryType::File)
    .collect();
```

#### `fs.stat(path: &str) -> FileStat`

Get file or directory metadata.

```rust theme={null}
let stat = agent.fs.stat("/report.md").await?;

println!("Size: {} bytes", stat.size);
println!("Type: {:?}", stat.file_type);
println!("Created: {:?}", stat.created);
println!("Modified: {:?}", stat.modified);
println!("Permissions: {:o}", stat.mode);

if stat.is_file() {
    println!("It's a file");
} else if stat.is_directory() {
    println!("It's a directory");
}
```

#### `fs.exists(path: &str) -> bool`

Check if a file or directory exists.

```rust theme={null}
if agent.fs.exists("/config.json").await? {
    let config = agent.fs.read_file("/config.json").await?;
}
```

#### `fs.rm(path: &str)`

Remove a file or empty directory.

```rust theme={null}
// Remove file
agent.fs.rm("/old-report.md").await?;

// Remove empty directory
agent.fs.rm("/empty-dir").await?;

// Remove directory tree
agent.fs.rm_rf("/old-data").await?;
```

#### `fs.rename(old_path: &str, new_path: &str)`

Move or rename files and directories.

```rust theme={null}
// Rename file
agent.fs.rename("/draft.md", "/final.md").await?;

// Move to different directory
agent.fs.rename("/temp/data.json", "/archive/data.json").await?;
```

#### `fs.copy_file(src: &str, dest: &str)`

Copy a file to a new location.

```rust theme={null}
agent.fs.copy_file("/template.md", "/new-report.md").await?;
```

### Tool Call Tracking API

Record and query agent tool invocations.

#### `tools.record()`

Record a tool invocation with timing and I/O data.

```rust theme={null}
use agentfs::ToolCall;
use chrono::Utc;
use serde_json::json;

let start = Utc::now();
// ... perform operation ...
let end = Utc::now();

agent.tools.record(ToolCall {
    name: "web_search".to_string(),
    started_at: start,
    ended_at: end,
    input: json!({
        "query": "rust async programming",
        "max_results": 10
    }),
    output: json!({
        "results_count": 8,
        "success": true
    }),
}).await?;
```

#### `tools.list(options: ToolListOptions)`

Query recorded tool calls.

```rust theme={null}
use agentfs::ToolListOptions;
use chrono::{Duration, Utc};

// Get recent calls
let recent = agent.tools.list(ToolListOptions {
    limit: Some(10),
    ..Default::default()
}).await?;

// Filter by name
let searches = agent.tools.list(ToolListOptions {
    name: Some("web_search".to_string()),
    limit: Some(100),
    ..Default::default()
}).await?;

// Time range query
let last_hour = agent.tools.list(ToolListOptions {
    since: Some(Utc::now() - Duration::hours(1)),
    until: Some(Utc::now()),
    ..Default::default()
}).await?;
```

#### `tools.get(id: &str) -> Option<ToolCall>`

Get details of a specific tool call.

```rust theme={null}
if let Some(call) = agent.tools.get("call_123abc").await? {
    let duration = call.ended_at - call.started_at;
    println!("Tool: {}", call.name);
    println!("Duration: {}ms", duration.num_milliseconds());
}
```

#### `tools.delete(id: &str)`

Remove a tool call record.

```rust theme={null}
agent.tools.delete("call_123abc").await?;
```

#### `tools.clear()`

Remove all tool call records.

```rust theme={null}
agent.tools.clear().await?;
```

## Support

* [GitHub Issues](https://github.com/tursodatabase/agentfs/issues) - Bug reports and feature requests
* [Discord](https://tur.so/discord) - Community support
* [Examples](https://github.com/tursodatabase/agentfs/tree/main/examples/rust) - Sample applications


# TypeScript/JavaScript SDK
Source: https://docs.turso.tech/agentfs/sdk/typescript

Complete reference for the AgentFS TypeScript and JavaScript SDK

Build stateful AI agents with the AgentFS TypeScript SDK, supporting both Node.js and browser environments.

## Installation

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install agentfs-sdk
    ```
  </Tab>

  <Tab title="bun">
    ```bash theme={null}
    bun add agentfs-sdk
    ```
  </Tab>

  <Tab title="deno">
    ```typescript theme={null}
    import { AgentFS } from "npm:agentfs-sdk";
    ```
  </Tab>
</Tabs>

## Quick Start

```typescript theme={null}
import { AgentFS } from 'agentfs-sdk';

// Persistent storage with identifier
const agent = await AgentFS.open({ id: 'my-agent' });
// Creates: .agentfs/my-agent.db

// Or use ephemeral in-memory database
const ephemeralAgent = await AgentFS.open();

// Use the three main APIs
// Key-value store
await agent.kv.set('key', 'value');
// Filesystem
await agent.fs.writeFile('/file.txt', 'data');
// Tool tracking
await agent.tools.record(...);
```

## Core APIs

### AgentFS Class

The main entry point for all AgentFS operations.

#### `AgentFS.open(options?: AgentFSOptions)`

Creates or opens an AgentFS database.

```typescript theme={null}
interface AgentFSOptions {
  /**
   * Optional unique identifier for the agent.
   * - If provided: Creates persistent storage at `.agentfs/{id}.db`
   * - If omitted: Uses ephemeral in-memory database
   */
  id?: string;
}

// Persistent storage
const agent = await AgentFS.open({ id: 'my-agent' });
// Creates: .agentfs/my-agent.db

// Ephemeral in-memory database
const ephemeralAgent = await AgentFS.open();
```

#### Properties

* `kv`: Key-value store interface
* `fs`: Filesystem interface
* `tools`: Tool call tracking interface
* `db`: Direct access to the underlying Turso database

### Key-Value Store API

Fast, typed storage for agent state and configuration.

#### `kv.set(key: string, value: any)`

Store a value with automatic JSON serialization.

```typescript theme={null}
await agent.kv.set('user:123', {
  name: 'Alice',
  preferences: { theme: 'dark' }
});

await agent.kv.set('session:current', 'abc-123');
await agent.kv.set('counter', 42);
```

#### `kv.get<T>(key: string): Promise<T | undefined>`

Retrieve a value with automatic deserialization.

```typescript theme={null}
interface UserData {
  name: string;
  preferences: { theme: string };
}

const user = await agent.kv.get<UserData>('user:123');
if (user) {
  console.log(user.name); // Type-safe access
}
```

#### `kv.delete(key: string)`

Remove a key-value pair.

```typescript theme={null}
await agent.kv.delete('session:expired');
```

#### `kv.list(prefix: string): Promise<{ key: string, value: any }[]>`

List keys matching a prefix.

```typescript theme={null}
// List all user keys
const userEntries = await agent.kv.list('user:');
for (const { key, value } of userEntries) {
  console.log(key, value);
}

// List all keys (empty prefix)
const allEntries = await agent.kv.list('');
```

### Filesystem API

POSIX-like filesystem operations for managing agent data.

#### `fs.writeFile(path: string, data: string | Buffer)`

Write data to a file, creating parent directories as needed.

```typescript theme={null}
// Write text
await agent.fs.writeFile('/reports/summary.md', '# Report\nContent...');

// Write binary data
const imageBuffer = await fetch(url).then(r => r.arrayBuffer());
await agent.fs.writeFile('/images/chart.png', Buffer.from(imageBuffer));
```

#### `fs.readFile(path: string, options?: BufferEncoding): Promise<Buffer | string>`

Read file contents. Returns a Buffer by default, or a string if encoding is specified.

```typescript theme={null}
// Read as Buffer (default)
const data = await agent.fs.readFile('/reports/summary.md');
const text = data.toString('utf-8');

// Read as string with encoding
const content = await agent.fs.readFile('/reports/summary.md', 'utf-8');

// For JSON files
const jsonData = await agent.fs.readFile('/config.json');
const config = JSON.parse(jsonData.toString());
```

#### `fs.readdir(path: string): Promise<string[]>`

List directory contents.

```typescript theme={null}
const files = await agent.fs.readdir('/reports');
// Returns: ['summary.md', '2024/', 'archive/']

// Check each entry type
for (const entry of files) {
  const stats = await agent.fs.stat(`/reports/${entry}`);
  if (stats.isDirectory()) {
    console.log(`Directory: ${entry}`);
  } else {
    console.log(`File: ${entry} (${stats.size} bytes)`);
  }
}
```

#### `fs.stat(path: string): Promise<Stats>`

Get file or directory metadata.

```typescript theme={null}
interface Stats {
  size: number;        // File size in bytes
  mode: number;        // File mode/permissions
  mtime: number;       // Modified time (Unix timestamp)
  ctime: number;       // Created time (Unix timestamp)
  isFile(): boolean;
  isDirectory(): boolean;
}

const stats = await agent.fs.stat('/reports/summary.md');
console.log(`Size: ${stats.size} bytes`);
console.log(`Modified: ${new Date(stats.mtime * 1000)}`);
```

#### `fs.exists(path: string): Promise<boolean>`

Check if a file or directory exists.

```typescript theme={null}
if (await agent.fs.exists('/reports/draft.md')) {
  console.log('Draft exists');
}
```

#### `fs.deleteFile(path: string)`

Delete a file.

```typescript theme={null}
await agent.fs.deleteFile('/reports/old.md');
```

### Tool Call Tracking API

Record and query agent tool invocations for debugging and compliance.

#### `tools.record(name, started_at, completed_at, parameters?, result?, error?): Promise<number>`

Record a tool invocation.

```typescript theme={null}
await agent.tools.record(
  name: string,           // Tool identifier
  started_at: number,     // Unix timestamp (seconds)
  completed_at: number,   // Unix timestamp (seconds)
  parameters?: any,       // Tool parameters (JSON-serializable)
  result?: any,           // Tool result (JSON-serializable)
  error?: string          // Error message if failed
);

// Example: Track an API call
const start = Date.now() / 1000;
const response = await callOpenAI(prompt);

const id = await agent.tools.record(
  'openai_completion',
  start,
  Date.now() / 1000,
  { prompt, model: 'gpt-4', temperature: 0.7 },
  { response, tokensUsed: 150 }
);
```

#### `tools.get(id: number): Promise<ToolCall | undefined>`

Get details of a specific tool call.

```typescript theme={null}
const toolCall = await agent.tools.get(42);
if (toolCall) {
  console.log('Duration:', toolCall.duration_ms, 'ms');
}
```

## Browser Support

AgentFS works in browser environments using WebAssembly:

```html theme={null}
<!DOCTYPE html>
<html>
<head>
  <script type="module">
    import { AgentFS } from 'https://unpkg.com/agentfs-sdk/dist/browser.js';

    const agent = await AgentFS.open({ id: 'browser-agent' });

    // All APIs work the same in browser
    await agent.kv.set('browser:data', { platform: 'web' });
    await agent.fs.writeFile('/notes.txt', 'Hello from browser!');
  </script>
</head>
</html>
```

## Support

* [GitHub Issues](https://github.com/tursodatabase/agentfs/issues) - Bug reports and feature requests
* [Discord](https://tur.so/discord) - Community support and discussions
* [Examples](https://github.com/tursodatabase/agentfs/tree/main/examples) - Sample applications


# List Audit Logs
Source: https://docs.turso.tech/api-reference/audit-logs/list

GET /v1/organizations/{organizationSlug}/audit-logs
Return the audit logs for the given organization, ordered by the `created_at` field in descending order.

<Warning>Audit Logs are limited to scaler plan and above.</Warning>

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/audit-logs \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Authentication
Source: https://docs.turso.tech/api-reference/authentication



The Turso API uses API tokens to authenticate requests. You can create and revoke API tokens using the [Turso CLI](/cli) and [Authentication API](/api-reference/tokens/create).

Tokens can be minted at three levels of restriction:

| Restriction                 | What it can do                                                                | How to mint                                                                    |
| --------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Organization-scoped         | Restricted to a single organization.                                          | `turso auth api-tokens mint my-token --org my-org`                             |
| Group-scoped                | Pinned to one group inside an organization, with a chosen set of permissions. | `turso auth api-tokens mint my-token --org my-org --group default --read-only` |
| Unrestricted *(deprecated)* | Acts on every organization the caller belongs to.                             | `turso auth api-tokens mint my-token`                                          |

<Warning>
  **Unrestricted (cross-org) tokens are deprecated and will be removed in a future release.** New tokens should always be scoped to an organization with `--org`. Existing unrestricted tokens continue to work for now, but you should rotate them to scoped tokens at your earliest convenience.
</Warning>

```bash theme={null}
# Token scoped to a single organization
turso auth api-tokens mint my-token --org my-org

# Group-scoped token, read-only (tighter scope, for automations that only need one group)
turso auth api-tokens mint my-token --org my-org --group default --read-only

# Token with access to all organizations — deprecated
turso auth api-tokens mint my-token
```

* Scope tokens to a single organization with `--org`, especially in CI/CD and production environments.
* Use environment variables when working with API tokens.
* Never share your API token in public, including repositories, and CI/CD Actions.

Turso uses Bearer authentication, and requires your API token to be passed with all protected requests in the `Authorization` header:

```bash theme={null}
Authorization: Bearer TOKEN
```

## Group-scoped tokens

A group-scoped token is a Platform API credential pinned to one group inside an organization, with an explicit list of allowed operations. It's the right shape for automations that should be able to provision and manage databases inside a single group without being able to reach the rest of the organization.

The caller minting a group-scoped token must be an admin or owner of the organization.

<Note>
  Group-scoped tokens are control-plane credentials — they authorize requests to the Turso Platform API. They are independent from the [SQL-engine tokens](/sdk/authorization/tokens) that your application uses to query a database. The two systems do not share key material or vocabulary.
</Note>

### Scope vocabulary

The platform expands the presets `read-only` and `full-access` to the corresponding individual scopes server-side, so the values you see on a token after creation are always individual scopes.

| Scope                | Allows                                                                                                |
| -------------------- | ----------------------------------------------------------------------------------------------------- |
| `read`               | All GET-style routes: list/retrieve databases and groups, configuration, usage, instances, locations. |
| `db:create`          | Create databases inside the group (`POST /databases`), seed from a dump, restore.                     |
| `db:delete`          | Delete a database inside the group.                                                                   |
| `db:configure`       | Patch database configuration, transfer, wake, add or remove instances.                                |
| `db:mint-token`      | Issue a SQL-engine token or TLS client certificate for a database inside the group.                   |
| `db:rotate-creds`    | Rotate the database signing key, invalidating every SQL token previously issued for it.               |
| `group:configure`    | Configure, rename, update, unarchive the group, or add and remove locations.                          |
| `group:mint-token`   | Issue a group-level SQL-engine token.                                                                 |
| `group:rotate-creds` | Rotate the group signing key, invalidating every SQL token issued against any database in the group.  |

<Warning>
  `db:mint-token` and `db:rotate-creds` are deliberately separate scopes. Minting a new SQL credential is additive; rotating invalidates every credential currently in use, which can take down running applications. Grant rotation only to automations that need it.
</Warning>

### Presets

| Preset        | Expands to                           |
| ------------- | ------------------------------------ |
| `read-only`   | `read`                               |
| `full-access` | Every individual scope listed above. |

Group create, group delete, group transfer, and AWS migration are intentionally not reachable from a group-scoped token at any scope — those operations are gated to organization-level credentials.

### Lifecycle

* Group-scoped tokens are pinned by the group's UUID, not its name. If a group is deleted and a new one is created with the same name, old tokens do not transfer — they are revoked along with the group.
* Deleting a group cascades a revoke to every token scoped to it.
* Transferring a group to a different organization cascades a revoke to every token scoped to it.

### Managing organization tokens

Two endpoints let admins manage every token scoped to an organization in one place — they are what backs the dashboard's organization-level token table:

* [`GET /v1/organizations/{organizationSlug}/api-tokens`](/api-reference/tokens/list-organization) — list every org-scoped and group-scoped token, with the minting user attached.
* [`DELETE /v1/organizations/{organizationSlug}/api-tokens/{tokenId}`](/api-reference/tokens/revoke-organization) — revoke by ID.

Members and viewers can call the same endpoints, but only see and revoke tokens they minted themselves.

## Base URL

The Turso API is located at the following URL:

```bash theme={null}
https://api.turso.tech
```


# Retrieve Database Configuration
Source: https://docs.turso.tech/api-reference/databases/configuration

GET /v1/organizations/{organizationSlug}/databases/{databaseName}/configuration
Retrieve an individual database configuration belonging to the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X GET 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/configuration' \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Create Database
Source: https://docs.turso.tech/api-reference/databases/create

POST /v1/organizations/{organizationSlug}/databases
Creates a new database in a group for the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "name": "new-database",
        "group": "default"
    }'
  ```

  ```bash cURL (for upload) theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "name": "new-database",
        "group": "default",
        "seed": {
          "type": "database_upload"
        }
    }'
  ```

  ```bash cURL (for encrypted upload) theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "name": "new-database",
        "group": "default",
        "seed": {
          "type": "database_upload"
        },
        "remote_encryption": {
          "encryption_key": "BASE64_ENCRYPTION_KEY",
          "encryption_cipher": "aes256gcm"
        }
    }'
  ```

  ```bash cURL (new encrypted database) theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "name": "new-database",
        "group": "default",
        "remote_encryption": {
          "encryption_key": "BASE64_ENCRYPTION_KEY",
          "encryption_cipher": "aes256gcm"
        }
    }'
  ```

  ```bash cURL (fork encrypted database) theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "name": "new-database",
        "group": "default",
        "seed": {
          "type": "database",
          "name": "source-encrypted-db"
        },
        "remote_encryption": {
          "encryption_key": "BASE64_ENCRYPTION_KEY",
          "encryption_cipher": "aes256gcm"
        }
    }'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const database = await turso.databases.create("new-database", {
    group: "default",
  });
  ```
</RequestExample>


# Generate Database Auth Token
Source: https://docs.turso.tech/api-reference/databases/create-token

POST /v1/organizations/{organizationSlug}/databases/{databaseName}/auth/tokens
Generates an authorization token for the specified database.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/auth/tokens?expiration=2w&authorization=full-access' \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const token = await turso.databases.createToken("my-db", {
    expiration: "2w",
    authorization: "full-access",
  });
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "jwt": "TOKEN"
  }
  ```
</ResponseExample>


# Delete Database
Source: https://docs.turso.tech/api-reference/databases/delete

DELETE /v1/organizations/{organizationSlug}/databases/{databaseName}
Delete a database belonging to the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X DELETE 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}' \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const database = await turso.databases.delete("my-db");
  ```
</RequestExample>


# Invalidate All Database Auth Tokens
Source: https://docs.turso.tech/api-reference/databases/invalidate-tokens

POST /v1/organizations/{organizationSlug}/databases/{databaseName}/auth/rotate
Invalidates all authorization tokens for the specified database.

<Warning>
  A short downtime is required to complete the changes.
</Warning>

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/auth/rotate' \
  -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const response = await turso.databases.rotateTokens("my-db");
  ```
</RequestExample>


# List Databases
Source: https://docs.turso.tech/api-reference/databases/list

GET /v1/organizations/{organizationSlug}/databases
Returns a list of databases belonging to the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/databases \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const databases = await turso.databases.list();
  ```
</RequestExample>


# List Database Instances
Source: https://docs.turso.tech/api-reference/databases/list-instances

GET /v1/organizations/{organizationSlug}/databases/{databaseName}/instances
Returns a list of instances of a database. Instances are the individual primary or replica databases in each region defined by the group.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/instances \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const instances = await turso.databases.listInstances("my-db");
  ```
</RequestExample>


# Retrieve Database
Source: https://docs.turso.tech/api-reference/databases/retrieve

GET /v1/organizations/{organizationSlug}/databases/{databaseName}
Returns a database belonging to the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}' \
  -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const database = await turso.databases.retrieve("my-db");
  ```
</RequestExample>


# Retrieve Database Instance
Source: https://docs.turso.tech/api-reference/databases/retrieve-instance

GET /v1/organizations/{organizationSlug}/databases/{databaseName}/instances/{instanceName}
Return the individual database instance by name.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/instances/{instanceName} \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const instance = await turso.databases.retrieveInstance(
    "my-db",
    "instanceName",
  );
  ```
</RequestExample>


# Retrieve Database Stats
Source: https://docs.turso.tech/api-reference/databases/stats

GET /v1/organizations/{organizationSlug}/databases/{databaseName}/stats
Fetch the top queries of a database, including the count of rows read and written.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X GET 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/stats' \
  -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Update Database Configuration
Source: https://docs.turso.tech/api-reference/databases/update-configuration

PATCH /v1/organizations/{organizationSlug}/databases/{databaseName}/configuration
Update a database configuration belonging to the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X PATCH 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/configuration' \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "size_limit": "500mb",
        "delete_protection": true,
        "block_reads": false,
        "block_writes": false,
        "allowed_ips": ["203.0.113.7", "10.0.0.0/8"],
        "allowed_aws_vpc_ids": ["vpce-0fe6c8807461bba49"]
    }'
  ```
</RequestExample>


# Upload Database
Source: https://docs.turso.tech/api-reference/databases/upload

POST https://{database_hostname}/v1/upload

Upload a SQLite database file to a database created with `seed.type = "database_upload"`.

<Note>
  This endpoint is on the **database hostname** (e.g., `my-db-my-org.turso.io`), not `api.turso.tech`.
</Note>

<Note>
  You must first [create a database](/api-reference/databases/create) with `seed.type = "database_upload"`.
</Note>

<Note>
  This endpoint requires a **database token**, not a platform API token. You can [create a database token](/api-reference/databases/create-token) after creating the database.
</Note>

## Headers

| Header                      | Required | Description                                                                                |
| --------------------------- | -------- | ------------------------------------------------------------------------------------------ |
| `Authorization`             | Yes      | Bearer token for the database                                                              |
| `Content-Length`            | Yes      | Size of the database file in bytes                                                         |
| `x-turso-encryption-key`    | No       | Base64-encoded encryption key (32 bytes for 256-bit ciphers, 16 bytes for 128-bit ciphers) |
| `x-turso-encryption-cipher` | No       | Encryption cipher. See table below for options.                                            |

## Request Body

The raw binary SQLite database file.

## Database File Requirements

The SQLite database file must meet these requirements:

* **Journal mode**: WAL (`PRAGMA journal_mode = WAL`)
* **Page size**: 4096 bytes (`PRAGMA page_size = 4096`)
* **Auto vacuum**: Disabled (`PRAGMA auto_vacuum = 0`)
* **Encoding**: UTF-8 (`PRAGMA encoding = 'UTF-8'`)

For encrypted databases, you must also set the reserved bytes to match the cipher:

| Cipher             | Reserved Bytes |
| ------------------ | -------------- |
| `aes256gcm`        | 28             |
| `aes128gcm`        | 28             |
| `chacha20poly1305` | 28             |
| `aegis128l`        | 32             |
| `aegis128x2`       | 32             |
| `aegis128x4`       | 32             |
| `aegis256`         | 48             |
| `aegis256x2`       | 48             |
| `aegis256x4`       | 48             |

## Responses

### Success

Returns an empty response with status `200 OK` on success.

### Errors

| Status | Description                                                   |
| ------ | ------------------------------------------------------------- |
| `400`  | Invalid database file (wrong format, settings, or corruption) |
| `401`  | Invalid or missing authorization token                        |

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST 'https://{database_hostname}/v1/upload' \
    -H 'Authorization: Bearer DATABASE_TOKEN' \
    -H 'Content-Length: FILE_SIZE' \
    --data-binary '@/path/to/database.db'
  ```

  ```bash cURL (with encryption) theme={null}
  curl -X POST 'https://{database_hostname}/v1/upload' \
    -H 'Authorization: Bearer DATABASE_TOKEN' \
    -H 'Content-Length: FILE_SIZE' \
    -H 'x-turso-encryption-key: BASE64_ENCRYPTION_KEY' \
    -H 'x-turso-encryption-cipher: aes256gcm' \
    --data-binary '@/path/to/database.db'
  ```
</RequestExample>


# Retrieve Database Usage
Source: https://docs.turso.tech/api-reference/databases/usage

GET /v1/organizations/{organizationSlug}/databases/{databaseName}/usage
Fetch activity usage for a database in a given time period.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X GET 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/usage?from=2023-01-01T00:00:00Z&to=2023-02-01T00:00:00Z' \
  -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const usageStatsWithDate = await turso.databases.usage("my-db");

  const usageStatsWithDate = await turso.databases.usage("my-db", {
    from: new Date("2023-01-01"),
    to: new Date("2023-02-01"),
  });

  const usageStatsWithString = await turso.databases.usage("my-db", {
    from: "2023-01-01T00:00:00Z",
    to: "2023-02-01T00:00:00Z",
  });
  ```
</RequestExample>


# Retrieve Group Configuration
Source: https://docs.turso.tech/api-reference/groups/configuration

GET /v1/organizations/{organizationSlug}/groups/{groupName}/configuration
Retrieve an individual group configuration belonging to the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X GET 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/configuration' \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Create Group
Source: https://docs.turso.tech/api-reference/groups/create

POST /v1/organizations/{organizationSlug}/groups
Creates a new group for the organization or user.

<Warning>
  Creating more than one group is limited to Scaler, Pro and Enterprise plans.
</Warning>

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups' \
  -H 'Authorization: Bearer TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
      "name": "new-group",
      "location": "lhr"
  }'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const group = await turso.groups.create("new-group", {
    location: "lhr",
    extensions: ["vector", "uuid"], // 'all'
  });
  ```
</RequestExample>


# Create Group Auth Token
Source: https://docs.turso.tech/api-reference/groups/create-token

POST /v1/organizations/{organizationSlug}/groups/{groupName}/auth/tokens
Generates an authorization token for the specified group.

<Info>
  Tokens cannot be retrieved once created, and cannot be revoked individually.
</Info>

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/auth/tokens?expiration=2w&authorization=full-access' \
  -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const token = await turso.groups.createToken("default", {
    expiration: "2w",
    authorization: "full-access",
  });
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "jwt": "TOKEN"
  }
  ```
</ResponseExample>


# Delete Group
Source: https://docs.turso.tech/api-reference/groups/delete

DELETE /v1/organizations/{organizationSlug}/groups/{groupName}
Delete a group belonging to the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X DELETE 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}' \
  -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const group = await turso.groups.delete("new-group");
  ```
</RequestExample>


# Invalidate All Group Auth Tokens
Source: https://docs.turso.tech/api-reference/groups/invalidate-tokens

POST /v1/organizations/{organizationSlug}/groups/{groupName}/auth/rotate
Invalidates all authorization tokens for the specified group.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/auth/rotate' \
  -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const response = await turso.groups.invalidateTokens("default");
  ```
</RequestExample>


# List Groups
Source: https://docs.turso.tech/api-reference/groups/list

GET /v1/organizations/{organizationSlug}/groups
Returns a list of groups belonging to the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/groups \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const groups = await turso.groups.list();
  ```
</RequestExample>


# Retrieve Group
Source: https://docs.turso.tech/api-reference/groups/retrieve

GET /v1/organizations/{organizationSlug}/groups/{groupName}
Returns a group belonging to the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}' \
  -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const group = await turso.groups.retrieve("default");
  ```
</RequestExample>


# Transfer Group
Source: https://docs.turso.tech/api-reference/groups/transfer

POST /v1/organizations/{organizationSlug}/groups/{groupName}/transfer
Transfer a group to another organization that you own or a member of.

<Info>
  You can only transfer groups to organizations you own or are an admin.
</Info>

<Warning>
  Existing database URL and tokens will continue to work, but should update your application to use the new URL and token as soon as possible.
</Warning>

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/transfer' \
  -H 'Authorization: Bearer TOKEN' \
  -d '{
      "organization": "new-organization-slug"
  }'
  ```
</RequestExample>


# Unarchive Group
Source: https://docs.turso.tech/api-reference/groups/unarchive

POST /v1/organizations/{organizationSlug}/groups/{groupName}/unarchive
Unarchive a group that has been archived due to inactivity.

<Info>
  Databases get archived after 10 days of inactivity for users on a free plan. You can unarchive inactive groups using the API.
</Info>

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/unarchive' \
  -H 'Authorization: Bearer TOKEN' \
  ```
</RequestExample>


# Update Group Configuration
Source: https://docs.turso.tech/api-reference/groups/update-configuration

PATCH /v1/organizations/{organizationSlug}/groups/{groupName}/configuration
Update a group configuration belonging to the organization or user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X PATCH 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/configuration' \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "delete_protection": true
    }'
  ```
</RequestExample>


# Turso Platform API
Source: https://docs.turso.tech/api-reference/introduction

Programmatically manage databases, groups, organizations, and tokens with the Turso Platform API.

## Quickstart with the TypeScript SDK

The fastest way to use the Platform API from JavaScript or TypeScript is the official [`@tursodatabase/api`](https://www.npmjs.com/package/@tursodatabase/api) client:

```bash theme={null}
npm install @tursodatabase/api
```

```ts theme={null}
import { createClient } from "@tursodatabase/api";

const turso = createClient({
  org: process.env.TURSO_ORG!,
  token: process.env.TURSO_PLATFORM_TOKEN!,
});

// Create a database
const db = await turso.databases.create("user-abc123", { group: "default" });

// Generate a scoped auth token for that database
const { jwt } = await turso.databases.createToken("user-abc123");

// Delete it when no longer needed
await turso.databases.delete("user-abc123");
```

For the raw HTTP reference, see the resource endpoints below.

## Common use cases

### Database per user / database per agent (multi-tenant)

Provision a dedicated database per user or per AI agent. Each entity gets its own isolated SQLite database that you create, token-scope, and tear down via the API.

```ts theme={null}
import { createClient } from "@tursodatabase/api";

const turso = createClient({
  org: process.env.TURSO_ORG!,
  token: process.env.TURSO_PLATFORM_TOKEN!,
});

// On user signup or agent creation — provision a database
const db = await turso.databases.create(`agent-${agentId}`, { group: "default" });

// Generate a scoped token for that database
const { jwt } = await turso.databases.createToken(`agent-${agentId}`);

// Store db.hostname and jwt for the session
```

Once the database is created, connect to it from your application:

* **Over the wire** — query the database remotely using [`@tursodatabase/serverless`](/sdk/ts/quickstart)
* **Local-first with sync** — sync the database to and from a local file using [`@tursodatabase/sync`](/sync/usage)

### Platform / reseller

Create and manage databases on behalf of your end users. Use the Platform API to build your own database-as-a-service on top of Turso.

```ts theme={null}
// List all databases in your organization
const databases = await turso.databases.list();

// Get details for a specific database
const db = await turso.databases.get("customer-db");
```

<CardGroup>
  <Card title="API Quickstart" icon="play" href="/api-reference/quickstart">
    Step-by-step guide to create your first database with the API.
  </Card>
</CardGroup>

## API Resources

Start integrating the Turso API with your platform in a few simple steps.

<Snippet />


# Closest Region
Source: https://docs.turso.tech/api-reference/locations/closest-region

GET https://region.turso.io
Returns the closest region to the user's location.

#### Response

<ResponseField name="server" type="string">
  The location code for the server responding.
</ResponseField>

<ResponseField name="client" type="string">
  The location code for the client request.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl https://region.turso.io
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const { server, client } = await turso.locations.closest();
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "server": "lhr",
    "client": "lhr"
  }
  ```
</ResponseExample>


# List Locations
Source: https://docs.turso.tech/api-reference/locations/list

GET /v1/locations
Returns a list of locations where you can create or replicate databases.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/locations \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const locations = await turso.locations.list();
  ```
</RequestExample>


# Create Invite
Source: https://docs.turso.tech/api-reference/organizations/invites/create-v2

POST /v2/organizations/{organizationSlug}/invites
Invite a user to an organization. If the user isn't already registered with Turso, they will receive a signup link. If an existing pending invite exists for the same email, it will be replaced.

<Warning>Invites are limited to scaler plan and above.</Warning>

<Info>
  You can invite by **email** or by **username** — provide exactly one. When inviting by username, the invite is sent to that user's registered email.
</Info>

<Info>
  You must be an `owner` or `admin` to invite other users. **You can only invite users to a team and not your personal account.**
</Info>

<Info>
  If a pending invite already exists for the same email, it will be replaced with a new one.
</Info>

<RequestExample>
  ```bash Invite by email theme={null}
  curl -L -X POST https://api.turso.tech/v2/organizations/{organizationSlug}/invites \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{"email": "user@example.com", "role": "member"}'
  ```

  ```bash Invite by username theme={null}
  curl -L -X POST https://api.turso.tech/v2/organizations/{organizationSlug}/invites \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{"username": "turso-user", "role": "member"}'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "mycompany",
    token: "",
  });

  const invite = await turso.organizations.inviteUser(
    "user@example.com",
    "member"
  );
  ```
</RequestExample>


# Delete Invite
Source: https://docs.turso.tech/api-reference/organizations/invites/delete-v2

DELETE /v2/organizations/{organizationSlug}/invites/{email}
Delete a pending invite for the organization by email.

<Warning>Invites are limited to scaler plan and above.</Warning>

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X DELETE https://api.turso.tech/v2/organizations/{organizationSlug}/invites/{email} \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "mycompany",
    token: "",
  });

  await turso.organizations.deleteInvite("user@example.com");
  ```
</RequestExample>


# List Invites
Source: https://docs.turso.tech/api-reference/organizations/invites/list-v2

GET /v2/organizations/{organizationSlug}/invites
Returns a list of pending invites for the organization.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v2/organizations/{organizationSlug}/invites \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "mycompany",
    token: "",
  });

  const invites = await turso.organizations.listInvites();
  ```
</RequestExample>


# List Invoices
Source: https://docs.turso.tech/api-reference/organizations/invoices

GET /v1/organizations/{organizationSlug}/invoices
Returns a list of invoices for the organization.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/invoices \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# List Organizations
Source: https://docs.turso.tech/api-reference/organizations/list

GET /v1/organizations
Returns a list of organizations the authenticated user owns or is a member of.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const organizations = await turso.organizations.list();
  ```
</RequestExample>


# Add Member
Source: https://docs.turso.tech/api-reference/organizations/members/add

POST /v1/organizations/{organizationSlug}/members
Add an existing Turso user to an organization.

<Info>
  If you want to add someone who is not a registered Turso user, you can [create an invite](/api-reference/organizations/invites/create) instead.
</Info>

<Info>
  You must be an `owner` or `admin` to add other members. **You can only add users to a team and not your personal account.**
</Info>


# List Members
Source: https://docs.turso.tech/api-reference/organizations/members/list

GET /v1/organizations/{organizationSlug}/members
Returns a list of members part of the organization.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/members \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const members = await turso.organizations.members("mycompany");
  ```
</RequestExample>


# Remove Member
Source: https://docs.turso.tech/api-reference/organizations/members/remove

DELETE /v1/organizations/{organizationSlug}/members/{username}
Remove a user from the organization by username.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X DELETE https://api.turso.tech/v1/organizations/{organizationSlug}/members/{username} \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const member = await turso.organizations.removeMember("mycompany", "iku");
  ```
</RequestExample>


# Retrieve Member
Source: https://docs.turso.tech/api-reference/organizations/members/retrieve

GET /v1/organizations/{organizationSlug}/members/{username}
Retrieve details of a specific member in the organization.

<RequestExample>
  ```bash theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/members/{username} \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Update Member Role
Source: https://docs.turso.tech/api-reference/organizations/members/update

PATCH /v1/organizations/{organizationSlug}/members/{username}
Update the role of an organization member. Only organization admins or owners can perform this action.

<RequestExample>
  ```bash theme={null}
  curl -L -X PATCH https://api.turso.tech/v1/organizations/{organizationSlug}/members/{username} \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
      "role": "member"
    }'
  ```
</RequestExample>


# List Plans
Source: https://docs.turso.tech/api-reference/organizations/plans

GET /v1/organizations/{organizationSlug}/plans
Returns a list of available plans and their quotas.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/plans \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Retrieve Organization
Source: https://docs.turso.tech/api-reference/organizations/retrieve

GET /v1/organizations/{organizationSlug}
Retrieve details of a specific organization.

<RequestExample>
  ```bash theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug} \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Current Subscription
Source: https://docs.turso.tech/api-reference/organizations/subscription

GET /v1/organizations/{organizationSlug}/subscription
Returns the current subscription details for the organization.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/subscription \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Update Organization
Source: https://docs.turso.tech/api-reference/organizations/update

PATCH /v1/organizations/{organizationSlug}
Update an organization you own or are a member of.

<RequestExample>
  ```bash cURL (overages) theme={null}
  curl -L -X PATCH https://api.turso.tech/v1/organizations/{organizationSlug} \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "overages": true
    }'
  ```

  ```bash cURL (require MFA) theme={null}
  curl -L -X PATCH https://api.turso.tech/v1/organizations/{organizationSlug} \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "require_mfa": true
    }'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const organization = await turso.organizations.update("mycompany", {
    overages: true,
  });
  ```
</RequestExample>


# Organization Usage
Source: https://docs.turso.tech/api-reference/organizations/usage

GET /v1/organizations/{organizationSlug}/usage
Fetch current billing cycle usage for an organization.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/usage \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Quickstart
Source: https://docs.turso.tech/api-reference/quickstart

Get started with Turso API in a few easy steps.

<Info>
  **Using TypeScript?** You can skip the raw HTTP examples and use the [`@tursodatabase/api`](https://www.npmjs.com/package/@tursodatabase/api) client instead. See the [SDK quickstart](/api-reference/introduction#quickstart-with-the-typescript-sdk).
</Info>

<Steps>
  <Step title="Signup or Login using the Turso CLI">
    Make sure to [install the Turso CLI](/cli/installation) if you haven't
    already.

    <CodeGroup>
      ```bash Signup theme={null}
      turso auth signup
      ```

      ```bash Login theme={null}
      turso auth login
      ```
    </CodeGroup>
  </Step>

  <Step title="Retrieve your account or organization slug">
    The Platform API can be used with your personal account or with an organization. You'll need the obtain the `slug` of your account or organization using using the Turso CLI:

    ```bash theme={null}
    turso org list
    ```
  </Step>

  <Step title="Create a new Platform API Token">
    Now create a new API Token using the Turso CLI:

    ```bash theme={null}
    turso auth api-tokens mint quickstart
    ```

    You can scope the token to a specific organization with `--org`:

    ```bash theme={null}
    turso auth api-tokens mint quickstart --org my-org
    ```

    <Info>
      Make sure to save the token somewhere safe. We'll need it next.
    </Info>
  </Step>

  <Step title="Fetch available Locations">
    Before we can create a group or database in a specific region, we'll need to fetch the list of available regions:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -L 'https://api.turso.tech/v1/locations' \
        -H 'Authorization: Bearer TOKEN' \
      ```

      ```ts Node.js theme={null}
      import { createClient } from "@tursodatabase/api";

      const turso = createClient({
        org: "...",
        token: "",
      });

      const locations = await turso.locations.list();
      ```
    </CodeGroup>

    <br />

    <Info>
      The `organizationSlug` is the name of your organization or personal account.
    </Info>
  </Step>

  <Step title="Create a Group">
    All databases belong to a group that can exist in one or more locations.

    Begin by creating a group, giving it a `name` and primary `location`:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups' \
        -H 'Authorization: Bearer TOKEN' \
        -H 'Content-Type: application/json' \
        -d '{
            "name": "default",
            "location": "lhr"
        }'
      ```

      ```ts Node.js theme={null}
      import { createClient } from "@tursodatabase/api";

      const turso = createClient({
        org: "...",
        token: "",
      });

      const group = await turso.groups.addLocation("default", "lhr");
      ```
    </CodeGroup>
  </Step>

  <Step title="Create a Database">
    Now create your first database in the group you created above:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \
        -H 'Authorization: Bearer TOKEN' \
        -H 'Content-Type: application/json' \
        -d '{
            "name": "my-db",
            "group": "default"
        }'
      ```

      ```ts Node.js theme={null}
      import { createClient } from "@tursodatabase/api";

      const turso = createClient({
        org: "...",
        token: "",
      });

      const database = await turso.databases.create("my-db", "ams");
      ```
    </CodeGroup>
  </Step>

  <Step title="Connect to your database">
    You now have a database, distributed across multiple regions that you can connect to using one of the official or experimental SDKs:

    <Snippet />
  </Step>
</Steps>


# Errors
Source: https://docs.turso.tech/api-reference/response-codes



The Turso API will respond with an HTTP status code, and in the event of an error, return an error message in the response body.

| Code  | Description                                                      |
| ----- | ---------------------------------------------------------------- |
| `200` | `OK` — Successful request                                        |
| `401` | `Unauthorized` — Invalid or expired auth token                   |
| `402` | `Payment required` — Check you have an active subscription       |
| `403` | `Forbidden` — You do not have permission to access this resource |
| `409` | `Conflict` — Resource already exists                             |


# Create API Token
Source: https://docs.turso.tech/api-reference/tokens/create

POST /v1/auth/api-tokens/{tokenName}
Returns a new API token belonging to a user.

The token can be minted at three levels of restriction, in increasing order of narrowness:

- **Organization-scoped** — pass `organization`. The token can only act on resources inside that organization.
- **Group-scoped** — pass `organization`, `group`, and `scopes`. The token is pinned to a single group inside the organization and only the operations listed in `scopes` are allowed. The caller must be an admin or owner of the organization.
- **Unrestricted** *(deprecated)* — no request body. The token can act on every organization the caller belongs to. **Unrestricted tokens are deprecated and will be removed in a future release.** Always pass `organization` for new tokens and rotate existing unrestricted tokens to scoped tokens.

Group-scoped tokens are designed for automations that should be able to provision and manage databases inside a single group without being able to touch the rest of the organization.

<Warning>
  The `token` in the response is never revealed again. Store this somewhere safe, and never share or commit it to source control.
</Warning>

<Warning>
  **Unrestricted (cross-org) tokens are deprecated and will be removed in a future release.** Always include at least `organization` in the request body.
</Warning>

<RequestExample>
  ```bash cURL (org-scoped) theme={null}
  curl -L -X POST https://api.turso.tech/v1/auth/api-tokens/{tokenName} \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{"organization": "my-org"}'
  ```

  ```bash cURL (deprecated, unrestricted) theme={null}
  curl -L -X POST https://api.turso.tech/v1/auth/api-tokens/{tokenName} \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```bash cURL (group-scoped, presets) theme={null}
  curl -L -X POST https://api.turso.tech/v1/auth/api-tokens/{tokenName} \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
      "organization": "my-org",
      "group": "default",
      "scopes": ["read-only"]
    }'
  ```

  ```bash cURL (group-scoped, fine-grained) theme={null}
  curl -L -X POST https://api.turso.tech/v1/auth/api-tokens/{tokenName} \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
      "organization": "my-org",
      "group": "default",
      "scopes": ["db:create", "db:configure", "db:mint-token"]
    }'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const apiToken = await turso.apiTokens.create("my-token");
  ```
</RequestExample>


# List API Tokens
Source: https://docs.turso.tech/api-reference/tokens/list

GET /v1/auth/api-tokens
Returns a list of API tokens belonging to a user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/auth/api-tokens \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const apiTokens = await turso.apiTokens.list();
  ```
</RequestExample>


# List Organization API Tokens
Source: https://docs.turso.tech/api-reference/tokens/list-organization

GET /v1/organizations/{organizationSlug}/api-tokens
Returns the API tokens scoped to this organization (both organization-scoped and group-scoped). Unrestricted tokens are not returned here — manage those via [`GET /v1/auth/api-tokens`](/api-reference/tokens/list).

Authorization is symmetric with the revoke endpoint:

- **Admins and owners** see every token scoped to the organization, with the minting user attached in the `owner` field.
- **Members and viewers** see only tokens they minted themselves.

This mirrors the personal-access-token model used in GitHub organization settings: admins get the full attribution list; everyone else sees their own access.

<Info>
  Use this endpoint to enumerate the tokens that act on a single organization (both org-scoped and group-scoped). Admins and owners see every token in the organization with the minting user attached; members and viewers see only their own. To list tokens across every organization the caller belongs to, use [`GET /v1/auth/api-tokens`](/api-reference/tokens/list) instead.
</Info>

<RequestExample>
  ```bash cURL theme={null}
  curl -L 'https://api.turso.tech/v1/organizations/{organizationSlug}/api-tokens' \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Revoke API Token
Source: https://docs.turso.tech/api-reference/tokens/revoke

DELETE /v1/auth/api-tokens/{tokenName}
Revokes the provided API token belonging to a user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X DELETE 'https://api.turso.tech/v1/auth/api-tokens/{tokenName}' \
  -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const response = await turso.apiTokens.revoke("my-token");
  ```
</RequestExample>


# Revoke Organization API Token
Source: https://docs.turso.tech/api-reference/tokens/revoke-organization

DELETE /v1/organizations/{organizationSlug}/api-tokens/{tokenId}
Revokes a token scoped to this organization.

The path takes a token **ID**, not a name, because names are unique per user but not across users — an admin revoking a member's token can't disambiguate by name alone.

Authorization is symmetric with the list endpoint:

- **Admins and owners** can revoke any token scoped to the organization (org-scoped or group-scoped, regardless of who minted it).
- **Members and viewers** can revoke only tokens they minted themselves.

A token scoped to a different organization returns `404`, not `403`, so the endpoint does not leak the existence of cross-org token IDs. Unrestricted tokens are also unreachable here and must be revoked via [`DELETE /v1/auth/api-tokens/{tokenName}`](/api-reference/tokens/revoke).

<Info>
  This endpoint takes a token **ID** (not a name) because token names are not unique across users in an organization. Admins and owners may revoke any token scoped to the organization; members and viewers may only revoke tokens they minted themselves. Use [`DELETE /v1/auth/api-tokens/{tokenName}`](/api-reference/tokens/revoke) to revoke unrestricted tokens.
</Info>

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X DELETE 'https://api.turso.tech/v1/organizations/{organizationSlug}/api-tokens/{tokenId}' \
    -H 'Authorization: Bearer TOKEN'
  ```
</RequestExample>


# Validate API Token
Source: https://docs.turso.tech/api-reference/tokens/validate

GET /v1/auth/validate
Validates an API token belonging to a user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L 'https://api.turso.tech/v1/auth/validate' \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const response = await turso.apiTokens.validate("...");
  ```
</RequestExample>


# Get Current User
Source: https://docs.turso.tech/api-reference/user/get-current

GET /v1/user
Returns information about the currently authenticated user.

<RequestExample>
  ```bash cURL theme={null}
  curl -L https://api.turso.tech/v1/user \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```ts Node.js theme={null}
  import { createClient } from "@tursodatabase/api";

  const turso = createClient({
    org: "...",
    token: "",
  });

  const user = await turso.users.getCurrent();
  ```
</RequestExample>


# auth api-tokens list
Source: https://docs.turso.tech/cli/auth/api-tokens/list



To list every API token you have minted across every organization you belong to, run the following command:

```bash theme={null}
turso auth api-tokens list
```

The output is a table:

```
NAME            SCOPE                 PERMISSIONS                                CREATED AT
ci-bot          all                   —                                          2026-04-12
deploy-bot      my-org                —                                          2026-05-02
provisioner     my-org/default        read-only                                  2026-05-21
schema-bot      my-org/default        db:create, db:configure, db:mint-token     2026-05-23
admin-bot       my-org/default        full-access                                2026-05-24
```

* **Scope** shows how the token is bound:
  * `all` — unrestricted, can act on every organization the caller belongs to.
  * `<org>` — organization-scoped.
  * `<org>/<group>` — group-scoped.
* **Permissions** summarizes the scope grant for group-scoped tokens:
  * `—` for tokens without an explicit scope set (unrestricted and org-scoped tokens).
  * `read-only` when the only scope is `read`.
  * `full-access` when every scope is granted.
  * Otherwise, the comma-separated scope list.

The list always reflects what the platform stores. Presets passed at creation time (`--read-only`, `--full-access`) are expanded into individual scopes before storage and are surfaced here in expanded form.


# auth api-tokens mint
Source: https://docs.turso.tech/cli/auth/api-tokens/mint



To create a new API token, scoped to a single organization, run the following command:

```bash theme={null}
turso auth api-tokens mint <api-token-name> --org <organization-slug>
```

<Warning>
  **Unrestricted (cross-org) tokens — minting without `--org` — are deprecated and will be removed in a future release.** Always pass `--org` for new tokens. Existing unrestricted tokens continue to work for now, but you should rotate them to scoped tokens at your earliest convenience.
</Warning>

## Flags

| Flag            | Description                                                                                     |
| --------------- | ----------------------------------------------------------------------------------------------- |
| `--org`         | Restrict the token to a specific organization.                                                  |
| `--group`       | Restrict the token to a specific group inside `--org`. Requires `--org` and at least one scope. |
| `--scope`       | Permission to grant a group-scoped token. Repeatable.                                           |
| `--read-only`   | Shorthand for `--scope read`.                                                                   |
| `--full-access` | Shorthand for granting every scope.                                                             |

## Organization-scoped tokens

Pass `--org` to restrict a token to a single organization:

```bash theme={null}
turso auth api-tokens mint <api-token-name> --org <organization-slug>
```

An org-scoped token can only manage resources (groups, databases, members) within that organization. Requests to any other organization will be rejected.

## Group-scoped tokens

A group-scoped token is pinned to a single group inside an organization and is restricted to an explicit set of operations. It's the right shape for automations that should be able to provision and manage databases inside one group without being able to touch the rest of the organization.

You must be an admin or owner of the organization to mint a group-scoped token.

```bash theme={null}
# Read-only access to one group
turso auth api-tokens mint deploy-bot --org my-org --group default --read-only

# Full access inside one group, but nothing outside it
turso auth api-tokens mint deploy-bot --org my-org --group default --full-access

# Fine-grained: provision and configure databases, mint SQL tokens
turso auth api-tokens mint deploy-bot --org my-org --group default \
  --scope db:create --scope db:configure --scope db:mint-token
```

### Available scopes

`--scope` can be repeated and accepts any of the following:

| Scope                | Allows                                                                                                |
| -------------------- | ----------------------------------------------------------------------------------------------------- |
| `read`               | All GET-style routes: list/retrieve databases and groups, configuration, usage, instances, locations. |
| `db:create`          | Create databases inside the group, seed from a dump, restore.                                         |
| `db:delete`          | Delete a database inside the group.                                                                   |
| `db:configure`       | Patch database configuration, transfer, wake, add or remove instances.                                |
| `db:mint-token`      | Issue a SQL-engine token or TLS client certificate for a database inside the group.                   |
| `db:rotate-creds`    | Rotate the database signing key, invalidating every SQL token previously issued for it.               |
| `group:configure`    | Configure, rename, update, unarchive the group, or add and remove locations.                          |
| `group:mint-token`   | Issue a group-level SQL-engine token.                                                                 |
| `group:rotate-creds` | Rotate the group signing key, invalidating every SQL token issued against any database in the group.  |

<Warning>
  `db:mint-token` and `db:rotate-creds` are deliberately separate scopes. Minting a new SQL credential is additive; rotating invalidates every credential currently in use, which can take down running applications. Grant rotation only to automations that need it.
</Warning>

`--scope`, `--read-only`, and `--full-access` are mutually exclusive — pass one flavor at a time. Unknown scope labels are rejected before the request is sent. Group create, group delete, group transfer, and AWS migration are never reachable from a group-scoped token at any scope.

### Lifecycle

Group-scoped tokens are pinned by the group's UUID, not its name. Deleting a group, or transferring it to another organization, cascades a revoke to every token scoped to it. A new group created with the same name does not inherit tokens from the previous one.

<Warning>
  The token value is only shown once at creation time. Store it somewhere safe.
</Warning>


# auth api-tokens revoke
Source: https://docs.turso.tech/cli/auth/api-tokens/revoke



To revoke an API token for the current organization, run the following command:

```bash theme={null}
turso auth api-tokens revoke <api-token-name>
```


# auth login
Source: https://docs.turso.tech/cli/auth/login



To authenticate the Turso CLI with your account, run the following command:

```bash theme={null}
turso auth login
```

<Note>
  If you're using Windows with WSL or a remote CI/CD environment, pass the --headless flag.
</Note>

## Flags

| Flag         | Description                                                       |
| ------------ | ----------------------------------------------------------------- |
| `--headless` | Useful when the CLI can't interact with the system's web browser. |


# auth logout
Source: https://docs.turso.tech/cli/auth/logout



To logout of the Turso CLI, run the following command:

```bash theme={null}
turso auth logout
```

## Flags

| Flag        | Description                                   |
| ----------- | --------------------------------------------- |
| `-a, --all` | Invalidate all sessions for the current user. |


# auth signup
Source: https://docs.turso.tech/cli/auth/signup



To sign up for a new Turso account using the CLI, run the following command:

```bash theme={null}
turso auth signup
```

<Note>
  If you're using Windows with WSL or a remote CI/CD environment, pass the --headless flag.
</Note>

## Flags

| Flag         | Description                                                       |
| ------------ | ----------------------------------------------------------------- |
| `--headless` | Useful when the CLI can't interact with the system's web browser. |


# auth token
Source: https://docs.turso.tech/cli/auth/token



You can obtain your current API token that is used to authenticate with the Turso Platform API by running the following command:

```bash theme={null}
turso auth token
```


# auth whoami
Source: https://docs.turso.tech/cli/auth/whoami



You can obtain the username for the currently logged in user by running the following command:

```bash theme={null}
turso auth whoami
```


# Authentication
Source: https://docs.turso.tech/cli/authentication



## `login`

The Turso CLI uses an access token to authenticate with Turso. You can login using the command:

```bash theme={null}
turso auth login
```

This will open a browser window, asking you to log into your account with GitHub.

<Note>
  If you're using Windows with WSL or a remote CI/CD environment, pass the `--headless` flag.
</Note>

Once you're logged in, you can manage your account and databases. You can explore all of the commands via the docs, or by using the `--help` flag for each subcommand:

```bash theme={null}
turso
```

<Info>
  The Turso CLI requires you to reauthenticate after one week. Don't share your token.
</Info>

## `signup`

If you're new to Turso, you can create an account using the Turso CLI:

```bash theme={null}
turso auth signup
```

## `logout`

You can logout of the Turso CLI using the `logout` command:

```bash theme={null}
turso auth logout
```


# contact bookmeeting
Source: https://docs.turso.tech/cli/contact/bookmeeting



You can book a meeting with the Turso team to discuss your project and requirements using the following command:

```bash theme={null}
turso contact bookmeeting
```

<Info>
  You will be redirected to a webpage where you can select a date and time for the meeting.
</Info>


# contact feedback
Source: https://docs.turso.tech/cli/contact/feedback



You can send feedback to the Turso team by using the following command:

```bash theme={null}
turso contact feedback
```


# db config allow-rules clear
Source: https://docs.turso.tech/cli/db/config/allow-rules/clear



Clear the access allow rules for a database, allowing connections from any source.

```bash theme={null}
turso db config allow-rules clear <database-name> [flags]
```

Without any flags, both the IP list and the AWS VPC endpoint list are cleared. Use `--ips` or `--aws-vpcs` to clear only one list.

## Flags

| Flag         | Description                                          |
| ------------ | ---------------------------------------------------- |
| `--ips`      | Clear only the list of allowed IPs.                  |
| `--aws-vpcs` | Clear only the list of allowed AWS VPC endpoint IDs. |

## Examples

### Clear all rules

```bash theme={null}
turso db config allow-rules clear my-db
```

### Clear only the IP list

```bash theme={null}
turso db config allow-rules clear my-db --ips
```

### Clear only the VPC list

```bash theme={null}
turso db config allow-rules clear my-db --aws-vpcs
```


# db config allow-rules set
Source: https://docs.turso.tech/cli/db/config/allow-rules/set



Set the access allow rules for a database. Each flag replaces the corresponding list; a list whose flag is not provided is left unchanged.

```bash theme={null}
turso db config allow-rules set <database-name> [--ip <address-or-cidr>]... [--aws-vpc <vpce-id>]...
```

At least one of `--ip` or `--aws-vpc` must be provided. To remove restrictions use [`turso db config allow-rules clear`](/cli/db/config/allow-rules/clear).

## Flags

| Flag        | Description                                                                           |
| ----------- | ------------------------------------------------------------------------------------- |
| `--ip`      | IP address or CIDR block to allow. Repeatable. Replaces the current IP list.          |
| `--aws-vpc` | AWS VPC endpoint ID (`vpce-...`) to allow. Repeatable. Replaces the current VPC list. |

## Examples

### Allow a single IP

```bash theme={null}
turso db config allow-rules set my-db --ip 203.0.113.7
```

### Allow a CIDR range

```bash theme={null}
turso db config allow-rules set my-db --ip 10.0.0.0/8
```

### Allow multiple IPs

```bash theme={null}
turso db config allow-rules set my-db --ip 203.0.113.7 --ip 10.0.0.0/8
```

### Restrict to an AWS VPC endpoint

```bash theme={null}
turso db config allow-rules set my-db --aws-vpc vpce-0fe6c8807461bba49
```

### Combine IP and VPC rules

When both lists are set, connections must satisfy both constraints.

```bash theme={null}
turso db config allow-rules set my-db \
  --ip 10.0.0.0/8 \
  --aws-vpc vpce-0fe6c8807461bba49
```


# db config allow-rules show
Source: https://docs.turso.tech/cli/db/config/allow-rules/show



Print the access allow rules for a database.

```bash theme={null}
turso db config allow-rules show <database-name>
```

When no rules are configured, all connections are accepted and the command reports:

```
Access allow rules are empty: connections from any source are accepted
```

When rules are set, each list is printed:

```
Allowed IPs:
  203.0.113.7
  10.0.0.0/8
Allowed AWS VPC endpoint IDs:
  vpce-0fe6c8807461bba49
```


# db create
Source: https://docs.turso.tech/cli/db/create



You can create a new database in a specific group using the following command:

```bash theme={null}
turso db create [database-name]
```

<Info>
  The `[database-name]` argument is optional. If you don't provide a name, the Turso team will use the name of your project.
</Info>

<Info>
  If you have more than one group, you will need to pass the `--group` flag.
</Info>

## Flags

| Flag                  | Description                                                                                                                                                                                                                           |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--enable-extensions` | Enables experimental support for SQLite extensions.                                                                                                                                                                                   |
| `--from-csv`          | Create the database from a csv file.                                                                                                                                                                                                  |
| `--csv-table-name`    | Name of the table in the csv file.                                                                                                                                                                                                    |
| `--from-db`           | Select another database to copy data from.                                                                                                                                                                                            |
| `--timestamp`         | Set a point in time in the past to copy data from the selected database.<br /><br />Must be used with the 'from-db' flag. Must be in [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) format like '2023-09-29T10:16:13-03:00' |
| `--from-dump`         | Create the database from a local SQLite dump.                                                                                                                                                                                         |
| `--from-dump-url`     | Create the database from a remote SQLite dump.                                                                                                                                                                                        |
| `--from-file`         | Create the database from a local SQLite3-compatible file.                                                                                                                                                                             |
| `--group`             | Create the database in the specified group.                                                                                                                                                                                           |
| `--size-limit`        | The maximum size of the database in bytes. Values with units are also accepted, e.g. 1mb, 256mb, 1gb.                                                                                                                                 |
| `--canary`            | Use database canary build.                                                                                                                                                                                                            |
| `-w`, `--wait`        | Wait for the database to be ready to receive requests.                                                                                                                                                                                |
| `-h`, `--help`        | Get help for create.                                                                                                                                                                                                                  |

## Examples

The examples below outline the most common use cases for the `db create` command.

### Create database in a specific group

You can create a new database in a specific group using the `--group` flag:

```bash theme={null}
turso db create [database-name] --group <group-name>
```

### Create database from SQLite file

You can create a new Turso database from an existing SQLite file using the `--from-file` flag:

```bash theme={null}
turso db create [database-name] --from-file ./path/to/file.db
```

<Info>
  The file size is limited to 2GB.
</Info>

### Create database from SQLite dump

You can create a new database using the output of `.dump` using the `--from-dump` flag:

```bash theme={null}
turso db create [database-name] --from-dump ./path/to/dump.sql
```

### Create database from another existing database

You can create a new Turso database from an existing database using the `--from-db` flag:

```bash theme={null}
turso db create [database-name] --from-db <existing-database-name>
```

### Create database from a point in time

You can create a new Turso database from an existing database at a specific point in time using the `--from-db` and `--timestamp` flags:

```bash theme={null}
turso db create [database-name] --from-db <existing-database-name> --timestamp 2024-01-01T10:10:10-10:00
```

### Create database from a CSV file

You can create a new Turso database from a CSV file using the `--from-csv` flag:

```bash theme={null}
turso db create [database-name] --from-csv ./path/to/file.csv --csv-table-name <desired-table-name>
```

### Create a database with extensions enabled

You can create a database with experimental support for SQLite [extensions](/libsql#extensions) using the `--enable-extensions` flag:

```bash theme={null}
turso db create [database-name] --enable-extensions
```

<Note>
  Using `--enable-extensions` will enable extensions on all databases in the group.
</Note>


# db destroy
Source: https://docs.turso.tech/cli/db/destroy



You can destroy a database by using the following command:

```bash theme={null}
turso db destroy <database-name> [flags]
```

## Flags

| Flag          | Description                            |
| ------------- | -------------------------------------- |
| `-y`, `--yes` | Confirms the destruction the database. |


# db export
Source: https://docs.turso.tech/cli/db/export



Export a database snapshot from Turso to a SQLite file.

This command exports a snapshot of the current generation of a Turso database to a local SQLite file. Note that the exported file may not contain the latest changes. Use SDK to sync the database after exporting to ensure you have the most recent version.

```bash theme={null}
turso db export <database> [flags]
```

## Flags

| Flag              | Description                                             |
| ----------------- | ------------------------------------------------------- |
| `--output-file`   | Specify the output file name (default: `<database>.db`) |
| `--overwrite`     | Overwrite output file if it exists                      |
| `--with-metadata` | Include metadata in the export                          |
| `-h`, `--help`    | Help for export                                         |


# db import
Source: https://docs.turso.tech/cli/db/import



You can import an existing SQLite file to Turso Cloud using the following command:

```bash theme={null}
turso db import ~/path/to/database.db
```

<Info>
  Make sure the SQLite file has WAL journal mode enabled.
</Info>

<Info>
  If you have more than one group, you will need to pass the `--group` flag.
</Info>

## Flags

| Flag           | Description                                 |
| -------------- | ------------------------------------------- |
| `--group`      | Import the database in the specified group. |
| `-h`, `--help` | Get help for import.                        |


# db inspect
Source: https://docs.turso.tech/cli/db/inspect



You can inspect the usage of a database, including the total space used, rows read and written with the following command:

```bash theme={null}
turso db inspect <database-name>
```

## Flags

| Flag        | Description                                                          |
| ----------- | -------------------------------------------------------------------- |
| `--queries` | Show database query statistics.                                      |
| `--verbose` | Show detailed information, including the location and instance type. |

## Examples

### Top Queries

You can fetch the most executed, resource-intensive SQL statements that have been run against your database using the Turso CLI:

<CodeGroup>
  ```bash Command theme={null}
  turso db inspect <database-name> --queries
  ```

  ```bash Response theme={null}
  SELECT * FROM Orders WHERE Status = 'Pending';
  SELECT COUNT(*), CustomerID FROM Orders GROUP BY CustomerID HAVING COUNT(*) > 5;
  SELECT Orders.OrderID, Customers.CustomerName FROM Orders INNER JOIN Customers ON Orders.CustomerID = Customers.CustomerID;
  UPDATE Products SET Price = Price * 1.10 WHERE Category = 'Electronics';
  ```
</CodeGroup>


# db list
Source: https://docs.turso.tech/cli/db/list



You can obtain a list of all databases for the current user or organization by running the following command:

```bash theme={null}
turso db list
```

## Flags

| Flag                   | Description                |
| ---------------------- | -------------------------- |
| `--group <group-name>` | Filter databases by group. |


# db locations
Source: https://docs.turso.tech/cli/db/locations



You can fetch a list of supported locations where databases can be located by running the following command:

```bash theme={null}
turso db locations
```

## Flags

| Flag                     | Description                                                               |
| ------------------------ | ------------------------------------------------------------------------- |
| `-l`, `--show-latencies` | Display latencies from your current location to each of Turso's locations |


# db shell
Source: https://docs.turso.tech/cli/db/shell



You can connect directly to a Turso database by using the following command:

```bash theme={null}
turso db shell <database-name> [sql] [flags]
```

## Flags

| Flag                         | Description                                        |
| ---------------------------- | -------------------------------------------------- |
| `--instance <instance>`      | Connect to the specified database instance.        |
| `--location <location-code>` | Connect to the database at the specified location. |
| `--proxy <url>`              | The proxy to use when connecting to the database.  |

## Examples

### Execute SQL

You can execute SQL directly against a database using the shell:

```bash theme={null}
turso db shell <database-name> "SELECT * FROM users"
```

### Database dump

You can dump the contents of a Turso database using the following command:

```bash theme={null}
turso db shell <database-name> .dump > dump.sql
```

<Info>
  The `.dump` can be used to rebuild a database and doesn't contain any libSQL or SQLite internal tables.
</Info>

### Load from dump

You can load a dump file into a new database using the following command:

```bash theme={null}
turso db shell <database-name> < dump.sql
```

### Shell with libSQL server

If you're using `turso dev` locally, you can use the shell by providing the URL to your database:

```bash theme={null}
turso db shell http://127.0.0.1:8080
```


# db show
Source: https://docs.turso.tech/cli/db/show



You can obtain details about the database, including the name, ID, libSQL server version, group, size and location.

```bash theme={null}
turso db show <database-name> [flags]
```

You can also obtain the different URLs for the database using the following:

## Flags

| Flag         | Description                              |
| ------------ | ---------------------------------------- |
| `--url`      | Show URL for the database HTTP API.      |
| `--http-url` | Show HTTP URL for the database HTTP API. |


# db tokens create
Source: https://docs.turso.tech/cli/db/tokens/create



You can create a new token that can be used to connect to one database using the command:

```bash theme={null}
turso db tokens create <database-name> [flags]
```

## Flags

| Flag                 | Description                                                                     |
| -------------------- | ------------------------------------------------------------------------------- |
| `-e`, `--expiration` | The expiration time for a token, can be `never` or a value in days, e.g. `7d` . |
| `-r`, `--read-only`  | Restrict the token to read only access.                                         |

## Examples

The examples below outline the most common use cases for the `db tokens create` command.

### Create a token with read only access

You can create a token with read only access to a database using the following command:

```bash theme={null}
turso db tokens create <database-name> --read-only
```

### Create a token with a specific expiration time

You can create a token with a specific expiration time using the following command:

```bash theme={null}
turso db tokens create <database-name> --expiration 7d3h2m1s
```


# db tokens invalidate
Source: https://docs.turso.tech/cli/db/tokens/invalidate



You can invalidate all tokens for a database by running the following command:

```bash theme={null}
turso db tokens invalidate <database-name> [flags]
```

## Flags

| Flag          | Description                                          |
| ------------- | ---------------------------------------------------- |
| `-y`, `--yes` | Confirms the invalidation of all existing db tokens. |

<Warning>
  All tokens in the group that provided database belongs will also be invalidated. This means that all existing tokens will no longer be valid and will need to be regenerated.
</Warning>


# dev
Source: https://docs.turso.tech/cli/dev





# group aws-migration abort
Source: https://docs.turso.tech/cli/group/aws-migration/abort



You can abort the AWS migration process by using the following command:

```bash theme={null}
turso group aws-migration abort <group-name>
```


# group aws-migration info
Source: https://docs.turso.tech/cli/group/aws-migration/info



You can check the AWS migration details by using the following command:

```bash theme={null}
turso group aws-migration info <group-name>
```


# group aws-migration start
Source: https://docs.turso.tech/cli/group/aws-migration/start



You can start the AWS migration process by using the following command:

```bash theme={null}
turso group aws-migration start <group-name>
```


# group create
Source: https://docs.turso.tech/cli/group/create



You can create a new group of databases. Groups belong to a primary region.

```bash theme={null}
turso group create <group-name> [flags]
```

<Warning>
  Creating more than one group is limited to Scaler, Pro and Enterprise plans.
</Warning>

<Info>
  Turso will automatically detect the closest region and use that as the primary region. You can override this by specifying the `--location` flag.
</Info>

## Flags

| Flag           | Description                                                                  |
| -------------- | ---------------------------------------------------------------------------- |
| `--canary`     | Use database canary build.                                                   |
| `--location`   | Create the group in the specified primary location using a three-digit code. |
| `-w`, `--wait` | Wait for group to be ready before exiting.                                   |


# group destroy
Source: https://docs.turso.tech/cli/group/destroy



You can destroy a group and all of its databases using the following command:

```bash theme={null}
turso group destroy <group-name> [flags]
```

## Flags

| Flag          | Description                                                      |
| ------------- | ---------------------------------------------------------------- |
| `-y`, `--yes` | Confirms destroying the group, with all locations and databases. |


# group list
Source: https://docs.turso.tech/cli/group/list



You can obtain a list of all groups by using the following command:

```bash theme={null}
turso group list
```


# group tokens create
Source: https://docs.turso.tech/cli/group/tokens/create



You can create a new token that can be used to connect to any database in the group using the command:

```bash theme={null}
turso group tokens create <group-name> [flags]
```

## Flags

| Flag                 | Description                                                                     |
| -------------------- | ------------------------------------------------------------------------------- |
| `-e`, `--expiration` | The expiration time for a token, can be `never` or a value in days, e.g. `7d` . |
| `-r`, `--read-only`  | Restrict the token to read only access.                                         |

## Examples

The examples below outline the most common use cases for the `group tokens create` command.

### Create a token with read only access

You can create a token with read only access using the command:

```bash theme={null}
turso group tokens create <group-name> --read-only
```

### Create a token with a specific expiration time

You can create a token with a specific expiration time using the command:

```bash theme={null}
turso group tokens create <group-name> --expiration 7d
```


# group tokens invalidate
Source: https://docs.turso.tech/cli/group/tokens/invalidate



You can invalidate all tokens for a group by running the command:

```bash theme={null}
turso group tokens invalidate <group-name> [flags]
```

## Flags

| Flag          | Description                                             |
| ------------- | ------------------------------------------------------- |
| `-y`, `--yes` | Confirms the invalidation of all existing group tokens. |


# group transfer
Source: https://docs.turso.tech/cli/group/transfer



You can transfer a group (including its databases) to an organization you're an admin or owner of using the following command:

```bash theme={null}
turso group transfer <group-name> <organization-name> [flags]
```

<Warning>
  Existing database URL and tokens will continue to work, but should update your application to use the new URL and token as soon as possible.
</Warning>

## Flags

| Flag          | Description                                  |
| ------------- | -------------------------------------------- |
| `-y`, `--yes` | Confirms the transfer to a new organization. |


# group unarchive
Source: https://docs.turso.tech/cli/group/unarchive



You can unarchive inactive databases by running the following command:

```bash theme={null}
turso group unarchive <group-name>
```

<Info>
  Databases get archived after 10 days of inactivity for users on a free plan.
</Info>


# group update
Source: https://docs.turso.tech/cli/group/update



You can update the group, including all databases the following command:

```bash theme={null}
turso group update <group-name> [flags]
```

## Flags

| Flag                  | Description                                                                         |
| --------------------- | ----------------------------------------------------------------------------------- |
| `--extensions string` | Enable extensions by passing `all` or `none`.                                       |
| `--version string`    | Specify the version of the group to update to. Values include `latest` or `canary`. |
| `-y`, `--yes`         | Skip confirmation prompt and confirm.                                               |

## Examples

### Update a group to enable all extensions

You can update a group and all its databases to enable `all` extensions:

```bash theme={null}
turso group update <group-name> --extensions all
```


# Headless Mode
Source: https://docs.turso.tech/cli/headless-mode



The Turso CLI will automatically attempt to open a browser, or wait for further instructions when interacting with various commands.

You can opt out of this behaviour by passing the `--headless` flag with operations:

```bash theme={null}
turso auth login --headless
```

<Note>
  If you're using Windows with WSL or a remote CI/CD environment, pass the `--headless` flag.
</Note>


# Help
Source: https://docs.turso.tech/cli/help



You can always find a summary of all commands by running the following command:

```bash theme={null}
turso help
```

You can also find out more about each command, as well as any required or optional arguments by passing the `--help` flag to the command:

```bash theme={null}
turso auth --help
turso db create --help
```

If you require support integrating Turso into your project, [we can help](/support).


# Installation
Source: https://docs.turso.tech/cli/installation

Learn how to install the Turso CLI on Mac, Linux and Windows.

## macOS

To install the Turso CLI on macOS, we recommend that you use Homebrew:

```bash theme={null}
brew install tursodatabase/tap/turso
```

If you don't use Homebrew, you can use the following command to execute a shell script that installs the CLI:

```bash theme={null}
curl -sSfL https://get.tur.so/install.sh | bash
```

## Linux

Run the following command to execute a shell script that installs the CLI:

```bash theme={null}
curl -sSfL https://get.tur.so/install.sh | bash
```

## Windows

Installing the Turso CLI on Windows requires that you have [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) installed.

Once WSL is installed, run the following in PowerShell:

```bash theme={null}
wsl
```

You can then execute the script to install the Turso CLI:

```bash theme={null}
curl -sSfL https://get.tur.so/install.sh | bash
```

## Verify installation

You must open a new shell to verify the installation with the following command:

```bash theme={null}
turso
```


# Introduction
Source: https://docs.turso.tech/cli/introduction



The Turso CLI gives you everything you need from the command line to manage your database, API tokens, inviting users, and launching the database shell. If you're waiting for a migration to run, there's also a relax command.

You can also programmatically manage your Turso account, including groups, databases, organizations and invites using the [Platform API](/api-reference).

<Steps>
  <Step title="Install">
    Begin by installing the Turso CLI:

    <CodeGroup>
      ```bash macOS theme={null}
      brew install tursodatabase/tap/turso
      ```

      ```bash Linux theme={null}
      curl -sSfL https://get.tur.so/install.sh | bash
      ```

      ```bash Windows (WSL) theme={null}
      curl -sSfL https://get.tur.so/install.sh | bash
      ```
    </CodeGroup>
  </Step>

  <Step title="Authenticate">
    Now signup or login:

    <CodeGroup>
      ```bash Signup theme={null}
      turso auth signup
      ```

      ```bash Login theme={null}
      turso auth login
      ```

      ```bash Signup (WSL) theme={null}
      turso auth signup --headless
      ```

      ```bash Login (WSL) theme={null}
      turso auth login --headless
      ```
    </CodeGroup>
  </Step>

  <Step title="Operate">
    The Turso CLI provides the following commands:

    | Command                     | Description                                                          |
    | --------------------------- | -------------------------------------------------------------------- |
    | [`auth`](/cli/auth)         | Authenticate and manage API tokens.                                  |
    | [`contact`](/cli/contact)   | Submit your feedback, ideas and create a meeting with the team.      |
    | [`db`](/cli/db)             | Create and manage databases, access tokens and connect to the shell. |
    | `dev`                       | Run Turso [locally](/local-development) for development.             |
    | [`group`](/cli/group)       | Create groups for databases with a shared location.                  |
    | [`org`](/cli/org)           | Manage billing and invite members.                                   |
    | [`plan`](/cli/plan)         | `overages`, `select`, `show`, `upgrade`                              |
    | [`quickstart`](/quickstart) | Get started with Turso in 5 minutes.                                 |
    | `relax`                     | Take some time out and relax with Turso.                             |
    | `update`                    | Update to the Turso CLI to the latest version with one command.      |
  </Step>
</Steps>


# org billing
Source: https://docs.turso.tech/cli/org/billing



To open the billing portal for your organization:

```bash theme={null}
turso org billing
```


# org create
Source: https://docs.turso.tech/cli/org/create



To create a new organization that you own, run the following command:

```bash theme={null}
turso org create <name>
```

<Info>
  You will be asked to add a payment method and subscribe to the Scaler plan to successfully create a new organization.
</Info>


# destroy
Source: https://docs.turso.tech/cli/org/destroy



To delete an existing organization that you own, run the following command:

```bash theme={null}
turso org destroy <organization-slug>
```

<Info>
  You will need to transfer or delete any existing projects before you can delete an organization. Users cannot also destroy the a personal account or active organization.
</Info>


# org list
Source: https://docs.turso.tech/cli/org/list



To list organizations of which you are the owner or a member, run the following command:

```bash theme={null}
turso org list
```


# org members add
Source: https://docs.turso.tech/cli/org/members/add



To add an existing Turso user as a member to the current active organization, run the following command:

```bash theme={null}
turso org members add <username> [flags]
```

## Flags

| Flag            | Description               |
| --------------- | ------------------------- |
| `-a`, `--admin` | Add the user as an admin. |


# org members invite
Source: https://docs.turso.tech/cli/org/members/invite



To invite someone who isn't already a Turso user to the current active organization, run the following command:

```bash theme={null}
turso org members invite <email> [flags]
```

## Flags

| Flag            | Description               |
| --------------- | ------------------------- |
| `-a`, `--admin` | Add the user as an admin. |


# org members list
Source: https://docs.turso.tech/cli/org/members/list



To list all members of the current active organization, run the following command:

```bash theme={null}
turso org members list
```

<Info>
  This command is only available to active organizations, use [`org switch`](/cli/org/switch) if you're currently authenticated with your personal account.
</Info>


# org members rm
Source: https://docs.turso.tech/cli/org/members/rm



To remove a member from the current organization, use the following command:

```bash theme={null}
turso org members rm <username>
```


# org switch
Source: https://docs.turso.tech/cli/org/switch



To switch the current active organization for the CLI, use the following command:

```bash theme={null}
turso org switch <organization-slug>
```


# plan overages disable
Source: https://docs.turso.tech/cli/plan/overages/disable



You can disable overages for the current repository by running the following command:

```bash theme={null}
turso plan overages disable
```

<Info>
  Any overages that have already been incurred will still be charged.
</Info>


# plan overages enable
Source: https://docs.turso.tech/cli/plan/overages/enable



You can enable overages for the current repository by running the following command:

```bash theme={null}
turso plan overages enable
```


# plan select
Source: https://docs.turso.tech/cli/plan/select



You can switch your current plan at any time using the command:

```bash theme={null}
turso plan select
```

<Info>
  You will need to add your credit card details to your account before you can upgrade to a paid plan. [Learn more](https://turso.tech/pricing) about pricing.
</Info>


# plan show
Source: https://docs.turso.tech/cli/plan/show



You can show the current plan usage and limits with the command:

```bash theme={null}
turso plan show
```


# plan upgrade
Source: https://docs.turso.tech/cli/plan/upgrade



You can upgrade your account to a paid plan at any time using the command:

```bash theme={null}
turso plan upgrade
```

<Info>
  You will need to add your credit card details to your account before you can upgrade to a paid plan.
</Info>


# Upgrading
Source: https://docs.turso.tech/cli/upgrading

Update the Turso CLI to the latest version to benefit from all the latest features, and fixes.

## macOS

```bash theme={null}
brew update
brew upgrade
```

## Linux

```bash theme={null}
turso update
```

## Windows

```bash theme={null}
turso update
```


# Database Access Allow Rules
Source: https://docs.turso.tech/cloud/allow-rules

Restrict database access to specific IP addresses, CIDR ranges, or AWS VPC endpoints.

Allow rules let you lock down a database so that only connections from specific sources are accepted. You can restrict by IP address or CIDR range, by AWS VPC endpoint ID, or both at the same time.

<Info>
  **AND semantics.** When both lists are configured, a connection must satisfy **both** rules: the client IP must be on the allowed-IP list **and** the connection must arrive through one of the allowed VPC endpoints.
</Info>

## Show Current Rules

```bash theme={null}
turso db config allow-rules show <database-name>
```

If no rules are configured, all connections are accepted:

```
Access allow rules are empty: connections from any source are accepted
```

When rules are set, the command prints each list:

```
Allowed IPs:
  203.0.113.7
  10.0.0.0/8
Allowed AWS VPC endpoint IDs:
  vpce-0fe6c8807461bba49
```

## Restrict by IP Address or CIDR

Use `--ip` (repeatable) to set the list of allowed IP addresses and CIDR blocks. The flag **replaces** the current list each time it is used.

```bash theme={null}
# Allow a single IP
turso db config allow-rules set my-db --ip 203.0.113.7

# Allow a CIDR range
turso db config allow-rules set my-db --ip 10.0.0.0/8

# Allow multiple entries at once
turso db config allow-rules set my-db --ip 203.0.113.7 --ip 10.0.0.0/8
```

Both IPv4 and IPv6 addresses are accepted. CIDR notation (e.g. `10.0.0.0/8`) is supported for ranges.

## Restrict by AWS VPC Endpoint

Use `--aws-vpc` (repeatable) to set the list of allowed [AWS VPC endpoint IDs](/cloud/private-endpoints). IDs must start with `vpce-`.

```bash theme={null}
turso db config allow-rules set my-db --aws-vpc vpce-0fe6c8807461bba49
```

## Combine IP and VPC Rules

You can set both lists in a single command. Connections must satisfy both constraints.

```bash theme={null}
turso db config allow-rules set my-db \
  --ip 10.0.0.0/8 \
  --aws-vpc vpce-0fe6c8807461bba49
```

A later call that only specifies `--ip` leaves the VPC list unchanged, and vice versa:

```bash theme={null}
# Add a new IP without touching the VPC list
turso db config allow-rules set my-db --ip 198.51.100.5
```

## Clear Rules

Remove all allow rules to allow connections from any source:

```bash theme={null}
turso db config allow-rules clear my-db
```

Clear only one list while leaving the other intact:

```bash theme={null}
# Clear only the IP list
turso db config allow-rules clear my-db --ips

# Clear only the VPC list
turso db config allow-rules clear my-db --aws-vpcs
```

## API

Allow rules are managed through the database [configuration endpoint](/api-reference/databases/update-configuration). Set `allowed_ips` and/or `allowed_aws_vpc_ids` in the request body:

```bash theme={null}
curl -L -X PATCH 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/configuration' \
  -H 'Authorization: Bearer TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "allowed_ips": ["203.0.113.7", "10.0.0.0/8"],
    "allowed_aws_vpc_ids": ["vpce-0fe6c8807461bba49"]
  }'
```

Pass an explicit empty array to clear a list:

```bash theme={null}
curl -L -X PATCH 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/configuration' \
  -H 'Authorization: Bearer TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "allowed_ips": [],
    "allowed_aws_vpc_ids": []
  }'
```

Omitting a field from the request body leaves that list unchanged.


# Durability Guarantees
Source: https://docs.turso.tech/cloud/durability



The following are the durability guarantees provided by the Turso Cloud platform for users who registered or upgraded **after March 17, 2025**:

|                   | Durability    | Added commit latency |
| ----------------- | ------------- | -------------------- |
| **Free**          | 99.999999999% | up to 100ms          |
| **Developer**     | 99.999999999% | up to 50ms           |
| **Scaler**        | 99.999999999% | up to 25ms           |
| **Pro and above** | 99.999999999% | up to 10ms           |

For all Turso AWS regions, we employ a diskless architecture where data is backed by a combination of S3-Express One Zone and S3.

Commits are only acknowledged once data is safely stored in either S3 or S3-express. Compute nodes can come and go at any time, and local disks act as a local cache. Therefore, we can provide the same durability guarantees as AWS’s best-in-class S3 storage.

<Note>
  Pro and Enterprise customers can use their own S3 / S3-express buckets,
  guaranteeing that data lives inside their infrastructure. In our future
  roadmap, compute will also be runnable inside your account, for a full BYOC
  solution
</Note>

## Short term and long term guarantees

Any SQLite-compatible database has mainly two storage components: The Write-Ahead Log, or WAL, which contains the most recent commits, and the database file. Periodically, the data in the WAL is folded back into the main database file.

New commits to the database that are sent to the Turso Cloud are stored in S3-express One Zone before they are acknowledged to the application. Periodically, the data is folded back into the main database file and stored on S3. As per AWS service agreement, both technologies provide a 99.999999999% durability guarantee.

## Commit latency

One of the reasons why an S3-based architecture is not as popular for transactional workloads as it is for analytical workloads is the high cost of writing to object store. Due to Turso Cloud’s multi-tenant architecture, commits are batched across many databases to amortize this cost.

Furthermore, commits are also batched in time, that is the added commit latency as described in Table 1.

The commit latency is not the expected latency in every commit, but the ceiling of added latency in each commit.

To understand how this works, let's consider the following scenario:

```mermaid theme={null}
sequenceDiagram
    participant Timeline
    participant DB1 as Database 1
    participant DB2 as Database 2

    Note over Timeline: 0ms - Commit Window Starts

    Timeline->>DB1: Commit c1 arrives
    Note over DB1: Waits 10ms for batch

    Note over Timeline: 4ms

    Timeline->>DB2: Commit c1 arrives
    Note over DB2: Waits 6ms for batch

    Note over Timeline: 10ms - Commit Window Ends

    Timeline->>DB1: Commit c2 arrives
    Note over DB1: No wait (batch is ready)

    Timeline-->>DB1: Acknowledge c1 and c2
    Timeline-->>DB2: Acknowledge c1
```

Let's break it down:

* The first commit (c1) to database d1 that happens at the beginning of the 10ms commit window must wait the full 10ms
* The commit (c1) to database d2 that happens 4ms into the window only waits the remaining 6ms
* The second commit (c2) to database d1 that arrives exactly at the end of the commit window has no added latency

## Testing methodology

Turso Cloud server is written from the ground up to use Deterministic Simulation Testing. DST, for short, is a technique that automatically tests the code across a variety of possible, randomly generated inputs, and upon any failure generates a seed that deterministically reproduces the issue with all its preconditions.

In addition to our simulator, we rely on [Antithesis](https://antithesis.com) full-system Deterministic Simulation Hypervisor to test the interaction of the system with external components, like AWS S3 and S3-Express One Zone.

The combination of our ground-up DST server and Antithesis allows us to test years of possible combinations of unlikely scenarios over the course of mere hours, making sure the system works as specified.


# BYOK Encryption
Source: https://docs.turso.tech/cloud/encryption



All databases are encrypted-at-rest at the volume level in the Turso Cloud, as part of our SOC2 standards. See [our trust center](https://trust.turso.tech) for details.

For Pro, and Enterprise customers, there is the option to encrypt the databases with a key that you provide (Bring-Your-Own-Key Encryption, or BYOK), as described below.

BYOK Encryption happens at the server level, making sure that nobody has access to your data at any time. In fact, because it is possible to encrypt different databases with different keys, it is possible for you to use different keys for different customers' databases. If the keys themselves are provided by your customers, BYOK Encryption offers your customers the guarantee that not even you can have access to their data.

Turso Cloud BYOK encryption for data at rest uses industry-standard AEAD algorithms. Each database page is encrypted individually, ensuring data integrity and security across all environments. BYOK encryption is available on Pro, and [Enterprise](https://tur.so/turso-enterprise-chat) plans.

<Info>
  **You hold the keys.** Each database can be encrypted with its own unique key that only you control. Turso never sees or stores your encryption keys. Encryption works seamlessly with both remote queries and sync.
</Info>

* **Page-level encryption**: Each 4 KiB page is encrypted individually with a unique nonce
* **AEAD algorithms**: Support for AEGIS-256, AES-GCM, and ChaCha20Poly1305
* **Data integrity**: Built-in authentication tags prevent tampering and corruption
* **In-memory keys**: Encryption keys are never stored on disk

Turso Cloud with BYOK encryption is perfect for:

<CardGroup>
  <Card title="AI Agents" icon="robot">
    Manage a fleet of trusted agents that deal with personal private data
  </Card>

  <Card title="Fintech Applications" icon="credit-card">
    Meet regulatory requirements for sensitive financial data
  </Card>

  <Card title="Privacy-Focused Products" icon="user-shield">
    Build user trust with strong data protection
  </Card>

  <Card title="Healthcare Apps" icon="heart-pulse">
    Protect patient data and meet HIPAA compliance
  </Card>

  <Card title="AI/ML Applications" icon="brain">
    Secure training data and model outputs
  </Card>
</CardGroup>

## What's Encrypted

<CardGroup>
  <Card title="Encrypted" icon="lock">
    * The database file and Write-Ahead Log (WAL) file on disk and on S3.
  </Card>
</CardGroup>

## Generate an Encryption Key

Generate a secure encryption key in base64 format. The key size depends on the cipher you choose:

<Tabs>
  <Tab title="256-bit (32 bytes)">
    For AEGIS-256 variants and AES-256-GCM:

    ```bash theme={null}
    openssl rand -base64 32
    ```

    Output example:

    ```
    YUfkdsD2SJe4KzTB8n6CjwCgs1YIW6k1xxMEguRuI5s=
    ```
  </Tab>

  <Tab title="128-bit (16 bytes)">
    For AEGIS-128 variants and AES-128-GCM:

    ```bash theme={null}
    openssl rand -base64 16
    ```

    Output example:

    ```
    XG1iWMFBFYEYrmYxjWDf5w==
    ```
  </Tab>
</Tabs>

<Warning>
  **Store your key securely!** If you lose the encryption key, you will not be
  able to access your encrypted database. Keys are never stored anywhere in the Turso Cloud.
</Warning>

## Create an Encrypted Database

<Steps>
  <Step title="Launch with encryption">
    Use the `--remote-encryption-key`, `--remote-encryption-cipher` flags and specify your cipher and key in database creation command:

    ```bash theme={null}
    turso db create my-secret-db --remote-encryption-key "YOUR_SECRET_KEY" --remote-encryption-cipher aegis128l
    ```

    Replace `YOUR_SECRET_KEY` with the key you generated above. You can also set the `TURSO_DB_REMOTE_ENCRYPTION_KEY` environment variable instead of passing the flag each time.
  </Step>

  <Step title="Create and insert data">
    Use `--remote-encryption-key` flag to start the shell, create a table and insert some data:

    ```bash theme={null}
    turso db shell my-secret-db --remote-encryption-key "YOUR_SECRET_KEY"
    ```

    ```sql theme={null}
    CREATE TABLE secrets (id INT, data TEXT);
    INSERT INTO secrets VALUES (1, 'sensitive information');
    INSERT INTO secrets VALUES (2, 'confidential data');
    ```
  </Step>

  <Step title="Verify encryption">
    Exit the shell (type `.quit`) and try to open the database without the key:

    ```bash theme={null}
    turso db shell my-secret-db
    ```

    You won't be able to access the data. The database is encrypted in the cloud.
  </Step>
</Steps>

## Upload Existing Database

To upload an existing encrypted database, provide the cipher and key flags during creation:

```bash theme={null}
turso db create my-secret-db --remote-encryption-key "YOUR_SECRET_KEY" --remote-encryption-cipher aegis128l --from-file my-db.db
```

## Branching and Point-in-Time Recovery (PITR)

Encrypted databases support [branching](/features/branching), and [point-in-time recovery (PITR)](/features/point-in-time-recovery). The new branch will use the same cipher algorithm and the same encryption key. During creation you need to specify the same encryption key that was used for the parent database:

```bash theme={null}
turso db create my-secret-db-fork --remote-encryption-key "YOUR_SECRET_KEY" --from-db my-secret-db
```

## Rekeying

Rekeying is not supported yet. However, you can export the database and create a new one with a different cipher and key:

```bash theme={null}
turso db export my-secret-db --remote-encryption-key "YOUR_SECRET_KEY"
turso db create my-new-secret-db --remote-encryption-key "YOUR_NEW_SECRET_KEY" --remote-encryption-cipher aegis256 --from-file my-secret-db.db
```

## Supported Ciphers

Turso Cloud supports multiple variants of three AEAD encryption algorithms, offering different trade-offs between performance and compatibility.

### AES-GCM

NIST-approved standard for compliance requirements. Widely supported across industries.

| Cipher          | Key Size           | Cipher Code | Use Case                             |
| --------------- | ------------------ | ----------- | ------------------------------------ |
| **AES-128-GCM** | 128-bit (16 bytes) | `aes128gcm` | Compliance with 128-bit requirements |
| **AES-256-GCM** | 256-bit (32 bytes) | `aes256gcm` | Maximum security for compliance      |

### ChaCha20-Poly1305

| Cipher                | Key Size           | Cipher Code        | Use Case            |
| --------------------- | ------------------ | ------------------ | ------------------- |
| **ChaCha20-Poly1305** | 256-bit (32 bytes) | `chacha20poly1305` | Alternative for AES |

### AEGIS

Modern, high-performance cipher family optimized for speed. **Recommended for most use cases.**

| Cipher          | Key Size           | Cipher Code  | Use Case                               |
| --------------- | ------------------ | ------------ | -------------------------------------- |
| **AEGIS-128L**  | 128-bit (16 bytes) | `aegis128l`  | Balanced performance, 128-bit security |
| **AEGIS-128X2** | 128-bit (16 bytes) | `aegis128x2` | 2x parallel processing                 |
| **AEGIS-128X4** | 128-bit (16 bytes) | `aegis128x4` | 4x parallel processing, maximum speed  |
| **AEGIS-256**   | 256-bit (32 bytes) | `aegis256`   | Balanced 256-bit security              |
| **AEGIS-256X2** | 256-bit (32 bytes) | `aegis256x2` | 2x parallel processing, 256-bit        |
| **AEGIS-256X4** | 256-bit (32 bytes) | `aegis256x4` | 4x parallel, maximum speed & security  |

<Info>
  **Choosing a cipher:**

  * Use `aegis128l` for 128 bit encryption, `aegis256` for 256 bit encryption (default recommendation)
  * For AES, use `aes128gcm` for 128 bit encryption, `aes256gcm` or `chacha20poly1305` for 256 bit encryption
</Info>

## Future Work

Future encryption features in development:

* **Encrypt existing databases**: Migrate unencrypted databases to encrypted format
* **Key rotation**: Update encryption keys without data loss


# Limitations
Source: https://docs.turso.tech/cloud/limitations

Known limitations and differences when using SQLite on Turso Cloud.

# Limitations

Turso Cloud is compatible with most SQLite features, but there are some known limitations to be aware of — especially if you're migrating from vanilla SQLite.

## Pragma statements

Some SQLite pragma statements behave differently on Turso Cloud or are not supported.

| Pragma           | Notes                                                                                                  |
| ---------------- | ------------------------------------------------------------------------------------------------------ |
| `user_version`   | Read-only on Turso Cloud. Use a `_schema_version` table instead to track schema migrations.            |
| `application_id` | Read-only on Turso Cloud. Use a `_metadata` table instead to store application identifiers.            |
| `busy_timeout`   | Not supported. Turso Cloud manages concurrency internally, so setting a busy timeout has no effect.    |
| `journal_mode`   | Not supported. Turso Cloud manages journaling internally and does not allow changing the journal mode. |

<Warning>
  If you're using `PRAGMA user_version` to track schema migrations (common in vanilla SQLite), you'll need to switch to a table-based approach on Turso Cloud:

  ```sql theme={null}
  CREATE TABLE IF NOT EXISTS _schema_version (version INTEGER NOT NULL);
  INSERT INTO _schema_version (version) VALUES (0);
  ```
</Warning>


# Migrate to Turso
Source: https://docs.turso.tech/cloud/migrate-to-turso

Learn how to import your existing SQLite database to Turso.

This guide will walk you through the process of migrating your existing SQLite database to Turso Cloud. You can choose between using the Turso CLI, or the Platform API.

<Frame>
  <img alt="Migrate to Turso" />
</Frame>

## Preparing to Migrate

Before importing your SQLite database to Turso, you need to ensure it's compatible with Turso's requirements. Specifically, your SQLite database should be using WAL (Write-Ahead Logging) mode.

<Steps>
  <Step title="Open your SQLite database">
    Use the SQLite command-line tool or any SQLite client to open your database:

    ```bash theme={null}
    sqlite3 path/to/your/database.db
    ```
  </Step>

  <Step title="Set WAL journal mode">
    Run the following command to switch your database to WAL mode:

    ```sql theme={null}
    PRAGMA journal_mode='wal';
    ```

    This should return `wal` to confirm the change was successful.
  </Step>

  <Step title="Checkpoint and truncate the WAL file">
    Execute a checkpoint to ensure all changes are written to the main database file and truncate the WAL file:

    ```sql theme={null}
    PRAGMA wal_checkpoint(truncate);
    ```
  </Step>

  <Step title="Verify the journal mode">
    Confirm that your database is now in WAL mode:

    ```sql theme={null}
    PRAGMA journal_mode;
    ```

    This should return `wal`.
  </Step>

  <Step title="Close the database">
    Exit the SQLite shell:

    ```sql theme={null}
    .exit
    ```

    Your database is now ready for migration to Turso.
  </Step>
</Steps>

## Using the CLI

You can create a new database from a local SQLite file using the Turso CLI:

<Steps>
  <Step title="Install the Turso CLI">
    Make sure you have the [Turso CLI installed](/cli/introduction), and you're authenticated.
  </Step>

  <Step title="Import your SQLite Database">
    Import your existing SQLite database file using the `db import` command:

    ```bash theme={null}
    turso db import ~/path/to/my-database.db
    ```

    <Note>
      Your database will be named after the file (without the .db extension), and
      will import all your tables, data, and schema.
    </Note>
  </Step>

  <Step title="Connect to your database">
    You can now connect to your database using the shell:

    ```bash theme={null}
    turso db shell <database-name>
    ```
  </Step>
</Steps>

## Using the Platform API

You can also use the Platform API to migrate your existing SQLite database:

<Steps>
  <Step title="Signup or Login using the Turso CLI">
    Make sure to [install the Turso CLI](/cli/installation) if you haven't
    already.

    <CodeGroup>
      ```bash Signup theme={null}
      turso auth signup
      ```

      ```bash Login theme={null}
      turso auth login
      ```
    </CodeGroup>
  </Step>

  <Step title="Create a new Platform API Token">
    Now create a new API Token using the Turso CLI:

    ```bash theme={null}
    turso auth api-tokens mint quickstart
    ```

    <Info>
      Make sure to save the token somewhere safe. You'll need it to create a database and database token.
    </Info>
  </Step>

  <Step title="Retrieve your account or organization slug">
    The Platform API can be used with your personal account or with an organization. You'll need the obtain the `slug` of your account or organization using using the Turso CLI:

    ```bash theme={null}
    turso org list
    ```
  </Step>

  <Step title="Create a Database for Import">
    First, create a database that's ready to receive an import:

    ```bash theme={null}
    curl -X POST "https://api.turso.tech/v1/organizations/{organizationSlug}/databases" \
      -L \
      -H "Authorization: Bearer TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
          "name": "new-database",
          "group": "default",
          "seed": { "type": "database_upload" }
      }'
    ```

    <Note>
      The `seed` parameter with `"type": "database_upload"` indicates that you plan to upload a database file. If you don't proceed to upload a database, this database will count towards your quota, but will not be usable.
    </Note>
  </Step>

  <Step title="Create a Database Token">
    Generate an authentication token for your database:

    ```bash theme={null}
    curl -X POST "https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/auth/tokens" \
      -L \
      -H "Authorization: Bearer TOKEN"
    ```

    This token will be used to authenticate your upload request, and future requests to the database.
  </Step>

  <Step title="Upload Your SQLite Database">
    Finally, upload your SQLite database file:

    ```bash theme={null}
    curl -X POST "https://{databaseName}-{organizationSlug}.turso.io/v1/upload" \
      -H "Authorization: Bearer DATABASE_TOKEN" \
      --data-binary @/path/to/your/database.db
    ```

    <Note>
      The `Authorization` header uses the database token you generated in the
      previous step, not your Platform API token.
    </Note>
  </Step>
</Steps>

You're now ready to connect to your new Turso database using any of the Turso client libraries.

<Snippet />


# Private Endpoints
Source: https://docs.turso.tech/cloud/private-endpoints

Learn how to configure private endpoints for your Turso Database on AWS.

Set up an AWS VPC endpoint to securely access Turso databases through AWS PrivateLink, keeping all traffic within AWS's private network.

## Prerequisites

* Existing VPC in your target AWS region
* Subnets in supported availability zones (see region-specific requirements below)
* IAM permissions to create VPC endpoints and modify security groups
* Active Turso account with configured databases

## Quickstart

<Tabs>
  <Tab title="us-east-1">
    <Steps>
      <Step title="Create VPC Endpoint">
        Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure:

        <AccordionGroup>
          <Accordion title="Basic Settings">
            * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`)
            * **Service category**: Select "Other endpoint services"
            * **Service name**: Enter `com.amazonaws.vpce.us-east-1.vpce-svc-0d9cc6f6a688ce8a5`
            * Click **Verify service**
          </Accordion>

          <Accordion title="Network Configuration">
            * Select your **VPC**
            * Enable **DNS name**
            * Choose **IPv4** for DNS record type
            * Select subnets in supported AZs (`use1-az4` and/or `use1-az6`)
            * Configure security groups and click **Create endpoint**
          </Accordion>
        </AccordionGroup>
      </Step>

      <Step title="Configure Security Groups">
        Update security groups to allow proper communication:

        **VPC Endpoint Security Group:**

        * Inbound: Allow HTTPS (port 443) from your application security groups

        **Application Security Groups:**

        * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group
      </Step>

      <Step title="Update Application Connections">
        Make sure you are using the regional Turso URL format:

        ```bash theme={null}
        curl https://<database_name>-<organization_slug>.aws-us-east-1.turso.io
        ```
      </Step>

      <Step title="Verify Setup">
        Test your VPC endpoint configuration from within your VPC:

        ```bash theme={null}
        # Test connectivity
        curl -v https://<database_name>-<organization_slug>.aws-us-east-1.turso.io

        # Verify private routing (should show traffic staying within AWS network)
        traceroute -T <database_name>-<organization_slug>.aws-us-east-1.turso.io
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="us-east-2">
    <Steps>
      <Step title="Create VPC Endpoint">
        Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure:

        <AccordionGroup>
          <Accordion title="Basic Settings">
            * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`)
            * **Service category**: Select "Other endpoint services"
            * **Service name**: Enter `com.amazonaws.vpce.us-east-2.vpce-svc-08cc04ea0962d5036`
            * Click **Verify service**
          </Accordion>

          <Accordion title="Network Configuration">
            * Select your **VPC**
            * Enable **DNS name**
            * Choose **IPv4** for DNS record type
            * Select subnets in supported AZs (`use2-az1` and/or `use2-az2`)
            * Configure security groups and click **Create endpoint**
          </Accordion>
        </AccordionGroup>
      </Step>

      <Step title="Configure Security Groups">
        Update security groups to allow proper communication:

        **VPC Endpoint Security Group:**

        * Inbound: Allow HTTPS (port 443) from your application security groups

        **Application Security Groups:**

        * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group
      </Step>

      <Step title="Update Application Connections">
        Make sure you are using the regional Turso URL format:

        ```bash theme={null}
        curl https://<database_name>-<organization_slug>.aws-us-east-2.turso.io
        ```
      </Step>

      <Step title="Verify Setup">
        Test your VPC endpoint configuration from within your VPC:

        ```bash theme={null}
        # Test connectivity
        curl -v https://<database_name>-<organization_slug>.aws-us-east-2.turso.io

        # Verify private routing (should show traffic staying within AWS network)
        traceroute -T <database_name>-<organization_slug>.aws-us-east-2.turso.io
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="us-west-2">
    <Steps>
      <Step title="Create VPC Endpoint">
        Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure:

        <AccordionGroup>
          <Accordion title="Basic Settings">
            * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`)
            * **Service category**: Select "Other endpoint services"
            * **Service name**: Enter `com.amazonaws.vpce.us-west-2.vpce-svc-0372e98cdacb325b7`
            * Click **Verify service**
          </Accordion>

          <Accordion title="Network Configuration">
            * Select your **VPC**
            * Enable **DNS name**
            * Choose **IPv4** for DNS record type
            * Select subnets in supported AZs (`usw2-az1` and/or `usw2-az3`)
            * Configure security groups and click **Create endpoint**
          </Accordion>
        </AccordionGroup>
      </Step>

      <Step title="Configure Security Groups">
        Update security groups to allow proper communication:

        **VPC Endpoint Security Group:**

        * Inbound: Allow HTTPS (port 443) from your application security groups

        **Application Security Groups:**

        * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group
      </Step>

      <Step title="Update Application Connections">
        Make sure you are using the regional Turso URL format:

        ```bash theme={null}
        curl https://<database_name>-<organization_slug>.aws-us-west-2.turso.io
        ```
      </Step>

      <Step title="Verify Setup">
        Test your VPC endpoint configuration from within your VPC:

        ```bash theme={null}
        # Test connectivity
        curl -v https://<database_name>-<organization_slug>.aws-us-west-2.turso.io

        # Verify private routing (should show traffic staying within AWS network)
        traceroute -T <database_name>-<organization_slug>.aws-us-west-2.turso.io
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="eu-west-1">
    <Steps>
      <Step title="Create VPC Endpoint">
        Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure:

        <AccordionGroup>
          <Accordion title="Basic Settings">
            * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`)
            * **Service category**: Select "Other endpoint services"
            * **Service name**: Enter `com.amazonaws.vpce.eu-west-1.vpce-svc-09559a4fb467b07fd`
            * Click **Verify service**
          </Accordion>

          <Accordion title="Network Configuration">
            * Select your **VPC**
            * Enable **DNS name**
            * Choose **IPv4** for DNS record type
            * Select subnets in supported AZs (`euw1-az1` and/or `euw1-az3`)
            * Configure security groups and click **Create endpoint**
          </Accordion>
        </AccordionGroup>
      </Step>

      <Step title="Configure Security Groups">
        Update security groups to allow proper communication:

        **VPC Endpoint Security Group:**

        * Inbound: Allow HTTPS (port 443) from your application security groups

        **Application Security Groups:**

        * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group
      </Step>

      <Step title="Update Application Connections">
        Make sure you are using the regional Turso URL format:

        ```bash theme={null}
        curl https://<database_name>-<organization_slug>.aws-eu-west-1.turso.io
        ```
      </Step>

      <Step title="Verify Setup">
        Test your VPC endpoint configuration from within your VPC:

        ```bash theme={null}
        # Test connectivity
        curl -v https://<database_name>-<organization_slug>.aws-eu-west-1.turso.io

        # Verify private routing (should show traffic staying within AWS network)
        traceroute -T <database_name>-<organization_slug>.aws-eu-west-1.turso.io
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="ap-south-1">
    <Steps>
      <Step title="Create VPC Endpoint">
        Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure:

        <AccordionGroup>
          <Accordion title="Basic Settings">
            * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`)
            * **Service category**: Select "Other endpoint services"
            * **Service name**: Enter `com.amazonaws.vpce.ap-south-1.vpce-svc-0c1607d1026a49817`
            * Click **Verify service**
          </Accordion>

          <Accordion title="Network Configuration">
            * Select your **VPC**
            * Enable **DNS name**
            * Choose **IPv4** for DNS record type
            * Select subnets in supported AZs (`aps1-az1` and/or `aps1-az3`)
            * Configure security groups and click **Create endpoint**
          </Accordion>
        </AccordionGroup>
      </Step>

      <Step title="Configure Security Groups">
        Update security groups to allow proper communication:

        **VPC Endpoint Security Group:**

        * Inbound: Allow HTTPS (port 443) from your application security groups

        **Application Security Groups:**

        * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group
      </Step>

      <Step title="Update Application Connections">
        Make sure you are using the regional Turso URL format:

        ```bash theme={null}
        curl https://<database_name>-<organization_slug>.aws-ap-south-1.turso.io
        ```
      </Step>

      <Step title="Verify Setup">
        Test your VPC endpoint configuration from within your VPC:

        ```bash theme={null}
        # Test connectivity
        curl -v https://<database_name>-<organization_slug>.aws-ap-south-1.turso.io

        # Verify private routing (should show traffic staying within AWS network)
        traceroute -T <database_name>-<organization_slug>.aws-ap-south-1.turso.io
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="ap-northeast-1">
    <Steps>
      <Step title="Create VPC Endpoint">
        Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure:

        <AccordionGroup>
          <Accordion title="Basic Settings">
            * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`)
            * **Service category**: Select "Other endpoint services"
            * **Service name**: Enter `com.amazonaws.vpce.ap-northeast-1.vpce-svc-070f604683ada07db`
            * Click **Verify service**
          </Accordion>

          <Accordion title="Network Configuration">
            * Select your **VPC**
            * Enable **DNS name**
            * Choose **IPv4** for DNS record type
            * Select subnets in supported AZs (`apne1-az4` and/or `apne1-az1`)
            * Configure security groups and click **Create endpoint**
          </Accordion>
        </AccordionGroup>
      </Step>

      <Step title="Configure Security Groups">
        Update security groups to allow proper communication:

        **VPC Endpoint Security Group:**

        * Inbound: Allow HTTPS (port 443) from your application security groups

        **Application Security Groups:**

        * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group
      </Step>

      <Step title="Update Application Connections">
        Make sure you are using the regional Turso URL format:

        ```bash theme={null}
        curl https://<database_name>-<organization_slug>.aws-ap-northeast-1.turso.io
        ```
      </Step>

      <Step title="Verify Setup">
        Test your VPC endpoint configuration from within your VPC:

        ```bash theme={null}
        # Test connectivity
        curl -v https://<database_name>-<organization_slug>.aws-ap-northeast-1.turso.io

        # Verify private routing (should show traffic staying within AWS network)
        traceroute -T <database_name>-<organization_slug>.aws-ap-northeast-1.turso.io
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Important Notes

* Traffic remains within AWS's private network
* Standard AWS VPC endpoint pricing applies
* Applications can access the endpoint from any AZ in your VPC
* Each region has specific service names and supported availability zones


# Connect to Turso using Dart
Source: https://docs.turso.tech/connect/dart



<Steps>
  <Step title="Install">
    Add the turso\_dart package to your `pubspec.yaml`:

    ```yaml theme={null}
    dependencies:
      turso_dart:
    ```

    Then run:

    ```bash theme={null}
    dart pub get
    ```
  </Step>

  <Step title="Connect">
    Create a client connection. You can connect to an in-memory database or a local file:

    ```dart theme={null}
    import 'package:turso_dart/turso_dart.dart';

    // In memory
    final client = TursoClient.memory();

    // Or local file
    final client = TursoClient.local('/path/to/local.db');

    await client.connect();
    ```
  </Step>

  <Step title="Create table">
    Create a table for customers:

    ```dart theme={null}
    await client.execute(
      "CREATE TABLE IF NOT EXISTS customers (id INTEGER PRIMARY KEY, name TEXT)"
    );
    ```
  </Step>

  <Step title="Insert data">
    Insert some data into the customers table:

    ```dart theme={null}
    await client.query("INSERT INTO customers(name) VALUES ('John Doe')");
    await client.query("INSERT INTO customers(name) VALUES ('Jane Smith')");
    ```
  </Step>

  <Step title="Query data">
    Query all customers from the table:

    ```dart theme={null}
    final result = await client.query("SELECT * FROM customers");
    print(result);
    ```
  </Step>

  <Step title="Prepared statements">
    Use prepared statements for better performance and security:

    ```dart theme={null}
    final statement = await client.prepare("SELECT * FROM customers WHERE id = ?");
    final result = await statement.query(positional: [1]);
    print(result);
    ```
  </Step>
</Steps>


# Connect to Turso using Go
Source: https://docs.turso.tech/connect/go



<Steps>
  <Step title="Install">
    Add the Turso package to your Go project:

    ```bash theme={null}
    go get turso.tech/database/tursogo
    ```
  </Step>

  <Step title="Connect">
    Here's how you can connect to a local SQLite database:

    ```go theme={null}
    package main

    import (
        "database/sql"
        "fmt"
        _ "turso.tech/database/tursogo"
    )

    func main() {
        db, err := sql.Open("turso", "sqlite.db")
        if err != nil {
            panic(err)
        }
        defer db.Close()
    }
    ```
  </Step>

  <Step title="Create table">
    Create a table for users:

    ```go theme={null}
    _, err = db.Exec(`
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT NOT NULL
      )
    `)
    if err != nil {
        panic(err)
    }
    ```
  </Step>

  <Step title="Insert data">
    Insert some data into the users table:

    ```go theme={null}
    _, err = db.Exec("INSERT INTO users (username) VALUES (?)", "alice")
    if err != nil {
        panic(err)
    }

    _, err = db.Exec("INSERT INTO users (username) VALUES (?)", "bob")
    if err != nil {
        panic(err)
    }
    ```
  </Step>

  <Step title="Query data">
    Query all users from the table:

    ```go theme={null}
    rows, err := db.Query("SELECT * FROM users")
    if err != nil {
        panic(err)
    }
    defer rows.Close()

    for rows.Next() {
        var id int
        var username string
        if err := rows.Scan(&id, &username); err != nil {
            panic(err)
        }
        fmt.Printf("User: ID: %d, Username: %s\n", id, username)
    }
    ```
  </Step>
</Steps>


# Connect to Turso using Java
Source: https://docs.turso.tech/connect/java



<Steps>
  <Step title="Install">
    Currently, we have not published to the maven central. Instead, you can locally build the jar and deploy it to maven local to use it.

    Build jar and publish to maven local:

    ```bash theme={null}
    cd bindings/java

    # Please select the appropriate target platform, currently supports `macos_x86`, `macos_arm64`, `windows` and `linux_x86`
    make macos_x86

    # deploy to maven local
    make publish_local
    ```

    Now you can use the dependency as follows:

    ```gradle theme={null}
    dependencies {
        implementation("tech.turso:turso:0.0.1-SNAPSHOT")
    }
    ```
  </Step>
</Steps>


# Connect to Turso using JavaScript
Source: https://docs.turso.tech/connect/javascript



## Quickstart

<Steps>
  <Step title="Install">
    Add the Turso database package to your JavaScript project:

    ```bash theme={null}
    npm i @tursodatabase/database
    ```
  </Step>

  <Step title="Connect">
    Here's how you can connect to a local SQLite database:

    ```javascript theme={null}
    import { connect } from "@tursodatabase/database";

    const db = await connect("sqlite.db");
    ```
  </Step>

  <Step title="Create table">
    Create a table for users:

    ```javascript theme={null}
    const createTable = db.prepare(`
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT NOT NULL
      )
    `);
    createTable.run();
    ```
  </Step>

  <Step title="Insert data">
    Insert some data into the users table:

    ```javascript theme={null}
    const insertUser = db.prepare("INSERT INTO users (username) VALUES (?)");
    insertUser.run("alice");
    insertUser.run("bob");
    ```
  </Step>

  <Step title="Query data">
    Query all users from the table:

    ```javascript theme={null}
    const stmt = db.prepare("SELECT * FROM users");
    const users = stmt.all();
    console.log(users);
    ```
  </Step>
</Steps>

## Examples

Explore these JavaScript examples to learn more about using Turso Database:

* [Node](https://github.com/tursodatabase/turso/blob/main/examples/javascript/database-node) — Node.js, local file database (no sync)
* [Wasm + Vite](https://github.com/tursodatabase/turso/blob/main/examples/javascript/database-wasm-vite) — Browser (WASM), local database in the browser


# Connect to Turso using Python
Source: https://docs.turso.tech/connect/python



<Steps>
  <Step title="Install">
    Add the Turso package to your Python project:

    ```bash theme={null}
    uv pip install pyturso
    ```
  </Step>

  <Step title="Connect">
    Here's how you can connect to a local SQLite database:

    ```python theme={null}
    import turso

    con = turso.connect("sqlite.db")
    cur = con.cursor()
    ```
  </Step>

  <Step title="Create table">
    Create a table for users:

    ```python theme={null}
    cur.execute("""
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT NOT NULL
      )
    """)
    con.commit()
    ```
  </Step>

  <Step title="Insert data">
    Insert some data into the users table:

    ```python theme={null}
    cur.execute("INSERT INTO users (username) VALUES (?)", ("alice",))
    cur.execute("INSERT INTO users (username) VALUES (?)", ("bob",))
    con.commit()
    ```
  </Step>

  <Step title="Query data">
    Query all users from the table:

    ```python theme={null}
    res = cur.execute("SELECT * FROM users")
    users = res.fetchall()
    print(users)
    ```
  </Step>
</Steps>


# Connect to Turso using Rust
Source: https://docs.turso.tech/connect/rust



<Steps>
  <Step title="Install">
    Add the Turso crate to your Rust project:

    ```bash theme={null}
    cargo add turso
    ```
  </Step>

  <Step title="Connect">
    Here's how you can connect to a local SQLite database:

    ```rust theme={null}
    use turso::Builder;

    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let db = Builder::new_local("sqlite.db").build().await?;
        let conn = db.connect()?;

        Ok(())
    }
    ```
  </Step>

  <Step title="Create table">
    Create a table for users:

    ```rust theme={null}
    conn.execute(
        "CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT NOT NULL
        )",
        ()
    ).await?;
    ```
  </Step>

  <Step title="Insert data">
    Insert some data into the users table:

    ```rust theme={null}
    conn.execute("INSERT INTO users (username) VALUES (?)", ("alice",)).await?;
    conn.execute("INSERT INTO users (username) VALUES (?)", ("bob",)).await?;
    ```
  </Step>

  <Step title="Query data">
    Query all users from the table:

    ```rust theme={null}
    let res = conn.query("SELECT * FROM users", ()).await?;
    println!("{:?}", res);
    ```
  </Step>
</Steps>


# AI & Embeddings
Source: https://docs.turso.tech/features/ai-and-embeddings

Vector Similarity Search is built into Turso and libSQL Server as a native feature.

Turso and libSQL enable vector search capability without an extension.

## How it works

* Create a table with one or more vector columns (e.g. `FLOAT32`)
* Provide vector values in binary format or convert text representation to binary using the appropriate conversion function (e.g. `vector32(...)`)
* Calculate vector similarity between vectors in the table or from the query itself using dedicated vector functions (e.g. `vector_distance_cos`)
* Create a special vector index to speed up nearest neighbors queries (use the `libsql_vector_idx(column)` expression in the `CREATE INDEX` statement to create vector index)
* Query the index with the special `vector_top_k(idx_name, q_vector, k)` [table-valued function](https://www.sqlite.org/vtab.html#table_valued_functions)

# Vectors

### Types

LibSQL uses the native SQLite BLOB [storage class](https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes) for vector columns. To align with SQLite [affinity rules](https://www.sqlite.org/datatype3.html#determination_of_column_affinity), all type names have two alternatives: one that is easy to type and another with a `_BLOB` suffix that is consistent with affinity rules.

<Info>
  We suggest library authors use type names with the `_BLOB` suffix to make
  results more generic and universal. For regular applications, developers can
  choose either alternative, as the type name only serves as a **hint** for
  SQLite and external extensions.
</Info>

<Info>
  As LibSQL does not introduce a new storage class, all metadata about vectors
  is also encoded in the `BLOB` itself. This comes at the cost of a few bytes
  per row but greatly simplifies the design of the feature.
</Info>

The table below lists six vector types currently supported by LibSQL. Types are listed from more precise and storage-heavy to more compact but less precise alternatives (the number of dimensions in vector $D$ is used to estimate storage requirements for a single vector).

| Type name                   | Storage (bytes)                 | Description                                                                                                                                                                                        |
| --------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FLOAT64` \| `F64_BLOB`     | $8D + 1$                        | Implementation of [IEEE 754 double precision format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) for 64-bit floating point numbers                                       |
| `FLOAT32` \| `F32_BLOB`     | $4D$                            | Implementation of [IEEE 754 single precision format](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) for 32-bit floating point numbers                                       |
| `FLOAT16` \| `F16_BLOB`     | $2D + 1$                        | Implementation of [IEEE 754-2008 half precision format](https://en.wikipedia.org/wiki/Half-precision_floating-point_format) for 16-bit floating point numbers                                      |
| `FLOATB16` \| `FB16_BLOB`   | $2D + 1$                        | Implementation of [bfloat16 format](https://en.wikipedia.org/wiki/Bfloat16_floating-point_format) for 16-bit floating point numbers                                                                |
| `FLOAT8` \| `F8_BLOB`       | $D + 14$                        | LibSQL specific implementation which compresses each vector component to single `u8` byte `b` and reconstruct value from it using simple transformation: $\texttt{shift} + \texttt{alpha} \cdot b$ |
| `FLOAT1BIT` \| `F1BIT_BLOB` | $\lceil \frac{D}{8} \rceil + 3$ | LibSQL-specific implementation which compresses each vector component down to 1-bit and packs multiple components into a single machine word, achieving a very compact representation              |

<Info>
  For most applications, the `FLOAT32` type should be a good starting point, but
  you may want to explore more compact options if your table has a large number
  of rows with vectors.
</Info>

<Info>
  While `FLOAT16` and `FLOATB16` use the same amount of storage, they provide
  different trade-offs between speed and accuracy. Generally, operations over
  `bfloat16` are faster but come at the expense of lower precision.
</Info>

### Functions

To work with vectors, LibSQL provides several functions that operate in the vector domain. Each function understands vectors in binary format aligned with the six types described above or in text format as a single JSON array of numbers.

Currently, LibSQL supports the following functions:

| Function name                                                                      | Description                                                                                                                                                                  |
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vector64` \| `vector32` \| `vector16` \| `vectorb16` \| `vector8` \| `vector1bit` | Conversion function which accepts a valid vector and converts it to the corresponding target type                                                                            |
| `vector`                                                                           | Alias for `vector32` conversion function                                                                                                                                     |
| `vector_extract`                                                                   | Extraction function which accepts valid vector and return its text representation                                                                                            |
| `vector_distance_cos`                                                              | Cosine distance (1 - [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity)) function which operates over vector of **same type** with **same dimensionality** |
| `vector_distance_l2`                                                               | Euclidean distance function which operates over vector of **same type** with **same dimensionality**                                                                         |

### Vectors usage

<Steps>
  <Step title="Create a table">
    Begin by declaring a column used for storing vectors with the `F32_BLOB` datatype:

    ```sql theme={null}
    CREATE TABLE movies (
      title     TEXT,
      year      INT,
      embedding F32_BLOB(4) -- 4-dimensional f32 vector
    );
    ```

    The number in parentheses `(4)` specifies the dimensionality of the vector. This means each vector in this column will have exactly 4 components.
  </Step>

  <Step title="Generate and insert embeddings">
    Once you generate embeddings for your data (via an LLM), you can insert them into your table:

    ```sql theme={null}
    INSERT INTO movies (title, year, embedding)
    VALUES
      ('Napoleon', 2023, vector32('[0.800, 0.579, 0.481, 0.229]')),
      ('Black Hawk Down', 2001, vector32('[0.406, 0.027, 0.378, 0.056]')),
      ('Gladiator', 2000, vector32('[0.698, 0.140, 0.073, 0.125]')),
      ('Blade Runner', 1982, vector32('[0.379, 0.637, 0.011, 0.647]'));
    ```

    Popular tools like [LangChain](https://www.langchain.com), [Hugging Face](https://huggingface.co) or [OpenAI](https://turso.tech/blog/how-to-generate-and-store-openai-vector-embeddings-with-turso) can be used to generate embeddings.
  </Step>

  <Step title="Perform a vector similarity search">
    You can now write queries combining vectors and standard SQLite data:

    ```sql theme={null}
    SELECT title,
           vector_extract(embedding),
           vector_distance_cos(embedding, vector32('[0.064, 0.777, 0.661, 0.687]')) AS distance
    FROM movies
    ORDER BY distance ASC;
    ```
  </Step>
</Steps>

### Understanding Distance Results

The `vector_distance_cos` function calculates the cosine distance, which is defined as:

* Cosine Distance = 1 — [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity)

The cosine distance ranges from 0 to 2, where:

* A distance close to 0 indicates that the vectors are nearly identical or exactly matching.
* A distance close to 1 indicates that the vectors are orthogonal (perpendicular).
* A distance close to 2 indicates that the vectors are pointing in opposite directions.

<Note>
  Very small negative numbers close to zero (for example, `-10^-14`) may
  occasionally appear due to floating-point arithmetic precision. These should
  be interpreted as effectively zero, indicating an exact or near-exact match
  between vectors.

  ```sql theme={null}
  SELECT vector_distance_cos('[1000]', '[1000]');
  -- Output: -2.0479999918166e-09
  ```
</Note>

### Vector Limitations

* Euclidean distance is **not supported** for 1-bit `FLOAT1BIT` vectors
* LibSQL can only operate on vectors with no more than 65536 dimensions

## Indexing

Nearest neighbors (NN) queries are popular for various AI-powered applications ([RAG](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) uses NN queries to extract relevant information, and recommendation engines can suggest items based on embedding similarity).

LibSQL implements [DiskANN](https://turso.tech/blog/approximate-nearest-neighbor-search-with-diskann-in-libsql) algorithm in order to speed up approximate nearest neighbors queries for tables with vector columns.

<Note>
  The DiskANN algorithm trades search accuracy for speed, so LibSQL queries may
  return slightly suboptimal neighbors for tables with a large number of rows.
</Note>

### Vector Index

LibSQL introduces a custom index type that helps speed up nearest neighbors queries against a fixed distance function (cosine similarity by default).

From a syntax perspective, the vector index differs from ordinary application-defined B-Tree indices in that it must wrap the vector column into a `libsql_vector_idx` marker function like this

```sql theme={null}
CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding));
```

<Note>
  Vector index works only for column with one of the vector types described
  above
</Note>

The vector index is fully integrated into the LibSQL core, so it inherits all operations and most features from ordinary indices:

* An index created for a table with existing data will be automatically populated with this data
* All updates to the base table will be **automatically** reflected in the index
* You can rebuild index from scratch using `REINDEX movies_idx` command
* You can drop index with `DROP INDEX movies_idx` command
* You can create [partial](https://www.sqlite.org/partialindex.html) vector index with a custom filtering rule:

```sql theme={null}
CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding))
WHERE year >= 2000;
```

### Query

At the moment vector index must be queried **explicitly** with special `vector_top_k(idx_name, q_vector, k)` [table-valued function](https://www.sqlite.org/vtab.html#table_valued_functions). The function accepts index name, query vector and amount of neighbors to return. This function searches for `k` approximate nearest neighbors and returns `ROWID` of these rows or `PRIMARY KEY` if base index [does not have ROWID](https://www.sqlite.org/withoutrowid.html).

In order for table-valued function to work query vector **must** have the same vector type and dimensionality.

### Settings

LibSQL vector index optionally can accept settings which must be specified as variadic parameters of the `libsql_vector_idx` function as strings in the format `key=value`:

```sql theme={null}
CREATE INDEX movies_idx
ON movies(libsql_vector_idx(embedding, 'metric=l2', 'compress_neighbors=float8'));
```

At the moment LibSQL supports the following settings:

| Setting key          | Value type                                                          | Description                                                                                                                                                                                                                                                                              |
| -------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `metric`             | `cosine` \| `l2`                                                    | Which distance function to use for building the index. <br /> Default: `cosine`                                                                                                                                                                                                          |
| `max_neighbors`      | positive integer                                                    | How many neighbors to store for every node in the DiskANN graph. The lower the setting -- the less storage index will use in exchange to search precision. <br /> Default: $3 \sqrt{D}$ where $D$ -- dimensionality of vector column                                                     |
| `compress_neighbors` | `float1bit`\|`float8`\|<br />`float16`\|`floatb16`\|<br />`float32` | Which vector type must be used to store neighbors for every node in the DiskANN graph. The more compact vector type is used for neighbors -- the less storage index will use in exchange to search precision. <br /> Default: **no compression** (neighbors has same type as base table) |
| `alpha`              | positive float $\geq 1$                                             | "Density" parameter of general sparse neighborhood graph build during DiskANN algorithm. The lower parameter -- the more sparse is DiskANN graph which can speed up query speed in exchange to lower search precision. <br />Default: `1.2`                                              |
| `search_l`           | positive integer                                                    | Setting which limits the amount of neighbors visited during vector search. The lower the setting -- the faster will be search query in exchange to search precision. <br />Default: `200`                                                                                                |
| `insert_l`           | positive integer                                                    | Setting which limits the amount of neighbors visited during vector insert. The lower the setting -- the faster will be insert query in exchange to DiskANN graph navigability properties. <br />Default: `70`                                                                            |

<Note>
  Vector index for column of type `T1` with `max_neighbors=M` and
  `compress_neighbors=T2` will approximately use $\texttt{N} (Storage(\texttt   {T1}) + \texttt{M} \cdot Storage(\texttt{T2}))$ storage bytes for `N` rows.
</Note>

### Index usage

<Steps>
  <Step title="Create a table">
    Begin by declaring a column used for storing vectors with the `F32_BLOB` datatype:

    ```sql theme={null}
    CREATE TABLE movies (
      title     TEXT,
      year      INT,
      embedding F32_BLOB(4) -- 4-dimensional f32 vector
    );
    ```

    The number in parentheses `(4)` specifies the dimensionality of the vector. This means each vector in this column will have exactly 4 components.
  </Step>

  <Step title="Generate and insert embeddings">
    Once you generate embeddings for your data (via an LLM), you can insert them into your table:

    ```sql theme={null}
    INSERT INTO movies (title, year, embedding)
    VALUES
      ('Napoleon', 2023, vector32('[0.800, 0.579, 0.481, 0.229]')),
      ('Black Hawk Down', 2001, vector32('[0.406, 0.027, 0.378, 0.056]')),
      ('Gladiator', 2000, vector32('[0.698, 0.140, 0.073, 0.125]')),
      ('Blade Runner', 1982, vector32('[0.379, 0.637, 0.011, 0.647]'));
    ```

    Popular tools like [LangChain](https://www.langchain.com), [Hugging Face](https://huggingface.co) or [OpenAI](https://turso.tech/blog/how-to-generate-and-store-openai-vector-embeddings-with-turso) can be used to generate embeddings.
  </Step>

  <Step title="Create an Index">
    Create an index using the `libsql_vector_idx` function:

    ```sql theme={null}
    CREATE INDEX movies_idx ON movies(libsql_vector_idx(embedding));
    ```

    This creates an index optimized for vector similarity searches on the `embedding` column.

    <Note>
      The `libsql_vector_idx` marker function is **required** and used by libSQL to
      distinguish `ANN`-indices from ordinary B-Tree indices.
    </Note>
  </Step>

  <Step title="Query the indexed table">
    ```sql theme={null}
    SELECT title, year
    FROM vector_top_k('movies_idx', vector32('[0.064, 0.777, 0.661, 0.687]'), 3)
    JOIN movies ON movies.rowid = id
    WHERE year >= 2020;
    ```

    This query uses the `vector_top_k` [table-valued function](https://www.sqlite.org/vtab.html#table_valued_functions) to efficiently find the top 3 most similar vectors to `[0.064, 0.777, 0.661, 0.687]` using the index.
  </Step>
</Steps>

### Index limitations

* Vector index works only for tables **with** `ROWID` or with singular `PRIMARY KEY`. Composite `PRIMARY KEY` without `ROWID` is not supported


# Attach Database (Deprecated)
Source: https://docs.turso.tech/features/attach-database

Attach and read data across multiple databases.

<Warning>
  This feature is now deprecated for all new users. Existing paid users can
  continue to use `ATTACH` — [read the
  announcement](https://turso.tech/blog/upcoming-changes-to-the-turso-platform-and-roadmap)
</Warning>

The `ATTACH` statement enables you to link multiple databases within a single transaction, which is ideal for:

* Organizing data in a modular way
* Streamlining data access and enhancing scalability
* Aggregating data

## How it works

1. You enable the `ATTACH` feature on the databases you want to connect to.
2. You retrieve the **Database ID** for the database you want to `ATTACH`.
3. You connect to the database
   * **CLI**: `--attach` flag to automatically create a token with the correct permissions.
   * **SDK**: Create a token with the `attach` permission for the database you want to attach.
4. You invoke `ATTACH` to connect to the other databases within the database shell or SDK.

## Usage

You can use the `ATTACH` statement to connect to other databases within a transaction using the CLI, or libSQL SDK. Once attached, you can query the attached databases as if they were part of the current database using the assigned alias.

### Turso CLI

Make sure you have the [Turso CLI](/cli/installation) installed, and [logged in](/cli/auth/login).

<Steps>
  <Step title="Enable attach on required databases">
    You will first need to enable the `ATTACH` feature on the database(s) you want to attach:

    ```bash theme={null}
    turso db config attach allow <database-name>
    ```
  </Step>

  <Step title="Retrieve Database ID">
    You now need to retrieve the **Database ID** for the database you want to `ATTACH`:

    ```bash theme={null}
    turso db show <database-name>
    ```
  </Step>

  <Step title="Connect to any database with attach">
    Now pass the names of the databases via the `--attach` flag when connecting to your database(s):

    ```bash theme={null}
    turso db shell <database-name> --attach <...database-name(s)>
    ```
  </Step>

  <Step title="ATTACH">
    Now once connected to the database you can invoke an `ATTACH` statement to connect the other database(s):

    ```sql theme={null}
    ATTACH "<database-id>" AS my_db;
    ```
  </Step>

  <Step title="Query">
    Execute a query using the alias for any attached database(s):

    ```sql theme={null}
    SELECT * FROM my_db.my_table;
    ```
  </Step>
</Steps>

### libSQL SDKs

You can use one of the libSQL client SDKs with [TypeScript](/sdk/ts), [Rust](/sdk/rust), [Go](/sdk/go), [Python](/sdk/python), or over [HTTP](/sdk/http).

<Steps>
  <Step title="Enable attach on required databases">
    You will first need to enable the `ATTACH` feature on the database(s) you want to attach:

    ```bash theme={null}
    turso db config attach allow <database-name>
    ```
  </Step>

  <Step title="Retrieve Database ID">
    You now need to retrieve the **Database ID** for the database you want to `ATTACH`:

    ```bash theme={null}
    turso db show <database-name>
    ```
  </Step>

  <Step title="Create token with ATTACH permissions">
    Now create a token for the libSQL client with the `attach` permission for the database you want to attach:

    ```bash theme={null}
    turso db tokens create <database-name> --attach <another-database-name>
    ```
  </Step>

  <Step title="Connect to any database with ATTACH">
    Use a [Client SDK](/sdk) to attach the desired database within a read transaction:

    <CodeGroup>
      ```ts @libsql/client theme={null}
      import { createClient } from "@libsql/client";

      const client = createClient({
        syncUrl: "libsql://...",
        authToken: "...",
      });

      const txn = await db.transaction("read");

      await txn.execute('ATTACH "<database-id>" AS my_db');

      const rs = await txn.execute("SELECT * FROM my_db.my_table");
      ```
    </CodeGroup>
  </Step>
</Steps>

## Things to know

* You can only attach databases that have the `attach` feature enabled.
* You can only attach databases belonging to a group, and in the same group.
* There is a maximum of 10 databases that can be attached to a single transaction.
* The attached databases are read only.
* `ATTACH` statement can be used only within transactions.
* `ATTACH` doesn't support [Embedded Replicas](/features/embedded-replicas)


# Branching
Source: https://docs.turso.tech/features/branching



A branch is a separate database instance that is created from an existing database. You can also create a branch from a [point-in-time](/features/point-in-time-recovery) snapshot of a database. Branches are useful for development and testing, because they allow you to make changes to the database without affecting the original database.

## How it works

1. You create a new database from an existing database using the CLI or API.
2. You connect to the new database using the group API token.
3. Make changes to the new schema using a migration tool (optional).
4. Apply the changes to the original database using a migration tool when merging using a GitHub Action (optional).
5. Delete the database when you no longer need it.

## Usage

You can create a new database from an existing database using the CLI or API:

<CodeGroup>
  ```bash CLI theme={null}
  turso db create my-new-database-branch --from-db my-existing-database
  ```

  ```bash Platform API theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "name": "new-database",
        "group": "default",
        "seed": {
          "type:": "database",
          "name": "my-existing-database"
        }
    }'
  ```
</CodeGroup>

Refer to the following references for more details about all arguments:

<CardGroup>
  <Card title="CLI Reference" icon="terminal" href="/cli/db/create" />

  <Card title="API Reference" icon="code" href="/api-reference/databases/create" />
</CardGroup>

## Things to know

* Database branches are completely separate from the original database. This means that you need to handle merging any schema changes or data manually using a migration tool.
* You will need to [create a new token](/cli/db/tokens/create) (or use a group token) to connect to the new database.
* You will need to manually delete the database branch when you no longer need it.
* Branches count towards your plan's database quota.

## CI/CD

Automating branching is useful for creating a new database for each pull request. This allows you to test changes without affecting the original database.

Here's an example of what that might look like using the [Platform API](/api-reference/databases/create):

```yml .github/workflows/create-database-branch.yml theme={null}
name: Create Database
on: create

jobs:
  triggerAPI:
    runs-on: ubuntu-latest
    steps:
      - name: Generate Branch Name
        id: branch_name
        run: |
          BRANCH_NAME=$(echo "${{ github.ref_name }}" | tr -cd '[:alnum:]' | sed 's/./\L&/g' | cut -c 1-32)
          echo "::set-output name=branch_name::$BRANCH_NAME"
      - name: Create Database
        run: |
          curl -X POST \
          -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" \
          -H "Content-Type: application/json" \
          -d '{"name": "${{ steps.branch_name.outputs.branch_name }}", "group": "default", "seed": {"type": "database", "name": "${{ secrets.DATABASE_NAME }}"} }' \
          "https://api.turso.tech/v1/organizations/${{ secrets.ORGANIZATION_NAME }}/databases"
```


# Data Edge (Deprecated)
Source: https://docs.turso.tech/features/data-edge

Allow your users to reach local replicas of your database, wherever they are.

<Warning>
  This feature is now deprecated for all new users. Existing users can continue
  to use Edge Replicas on Fly — [read the
  announcement](https://turso.tech/blog/upcoming-changes-to-the-turso-platform-and-roadmap)
</Warning>

For those seeking the ultimate in speed, Turso enables the [embedding of databases](/features/embedded-replicas) directly within your application on the same node. This configuration eliminates inter-regional request hopping, effectively bringing latency down to zero.

<iframe title="YouTube video player" />

## How it works

1. You create a database in a primary location
2. You add additional locations where data should be replicated
3. You query a single URL that automatically routes to the nearest edge

## Add replica location

You can add locations to your database group using the [Turso CLI](/cli/group/locations/add) or [Platform API](/api-reference/groups/add-location):

<CodeGroup>
  ```bash CLI theme={null}
  turso group locations add <group-name> <location-code>
  ```

  ```bash Platform API theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/locations/{location}' \
    -H 'Authorization: Bearer TOKEN'
  ```
</CodeGroup>

## Remove replica location

You can remove locations from your database group using the [Turso CLI](/cli/group/locations/remove) or [Platform API](/api-reference/groups/remove-location):

<CodeGroup>
  ```bash CLI theme={null}
  turso group locations remove <group-name> <...location-codes>
  ```

  ```bash Platform API theme={null}
  curl -L -X DELETE 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/locations/{location}' \
    -H 'Authorization: Bearer TOKEN'
  ```
</CodeGroup>


# Embedded Replicas
Source: https://docs.turso.tech/features/embedded-replicas/introduction



<Info>
  Embedded Replicas keep a local read replica of a Turso Cloud database: reads run locally from the file in microseconds, and writes are sent to the cloud primary and then reflected back to the replica. Embedded Replicas are fully supported in production.

  For new projects that need sync, we recommend [Turso Sync](/sync/usage). Sync is built on the Turso Database engine and uses logical change-data-capture, providing local-first writes, explicit `push()` / `pull()`, and [significantly less bandwidth and lower latency](https://turso.tech/blog/sync-benchmark) than the page-level replication Embedded Replicas use.
</Info>

Turso Cloud's embedded replicas let you replicate a database right within your application. This is especially useful for VMs or VPS deployments, and for mobile applications where stable connectivity is a challenge, since they allow uninterrupted access to the local database.

Embedded replicas provide a smooth switch between local and remote database operations, allowing the same database to adapt to various scenarios. They also ensure fast data access by syncing local copies with the remote database, enabling microsecond-level read operations.

## How it works

1. You configure a local file to be your main database.

   * The `url` parameter in the client configuration.

2. You configure a remote database to sync with.

   * The `syncUrl` parameter in the client configuration.

3. You read from a database:

   * Reads are always served from the local replica configured at `url`.

4. You write to a database:

   * **Writes are sent to the remote primary database** configured at `syncUrl` by default. They are NOT written to the local file first.
   * You can write locally if you set the `offline` config option to `true`.
   * Any write transactions with reads are also sent to the remote primary database.
   * Once the write is successful, the local database is updated with the changes automatically (read your own writes, can be disabled).

### Periodic sync

You can automatically sync data to your embedded replica using the periodic sync interval property. Simply pass the `syncInterval` parameter when instantiating the client:

```ts theme={null}
import { createClient } from "@libsql/client";

const client = createClient({
  url: "file:path/to/db-file.db",
  authToken: "...",
  syncUrl: "...",
  syncInterval: 60,
  offline: true, // Optional: Enable offline mode (default: false)
});
```

### Read your writes

Embedded Replicas also will guarantee read-your-writes semantics. What that means in practice is that after a write returns successfully, the replica that initiated the write will always be able to see the new data right away, even if it never calls `sync()`.

Other replicas will see the new data when they call `sync()`, or at the next sync period, if [Periodic Sync](#periodic-sync) is used.

<img alt="Read your writes" />

### Encryption at rest

Embedded Replicas support encryption at rest with one of the libSQL client SDKs. Simply pass the `encryptionKey` parameter when instantiating the client:

<CodeGroup>
  <Snippet />

  <Snippet />

  <Snippet />

  <Snippet />

  <Snippet />
</CodeGroup>

<Note>The encryption key used should be generated and managed by you.</Note>

## Usage

To use embedded replicas, you need to create a client with a `syncUrl` parameter. This parameter specifies the URL of the remote Turso Cloud database that the client will sync with:

<CodeGroup>
  ```ts TypeScript theme={null}
  import { createClient } from "@libsql/client";

  const client = createClient({
    url: "file:replica.db",
    syncUrl: "libsql://...",
    authToken: "...",
  });
  ```

  ```go Go theme={null}
  package main

  import (
     "database/sql"
     "fmt"
     "os"
     "path/filepath"

     "github.com/tursodatabase/go-libsql"
  )

  func main() {
     dbName := "local.db"
     primaryUrl := "libsql://[DATABASE].turso.io"
     authToken := "..."

     dir, err := os.MkdirTemp("", "libsql-*")
     if err != nil {
        fmt.Println("Error creating temporary directory:", err)
        os.Exit(1)
     }
     defer os.RemoveAll(dir)

     dbPath := filepath.Join(dir, dbName)

     connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl,
        libsql.WithAuthToken(authToken),
     )
     if err != nil {
        fmt.Println("Error creating connector:", err)
        os.Exit(1)
     }
     defer connector.Close()

     db := sql.OpenDB(connector)
     defer db.Close()
  }
  ```

  ```rust Rust theme={null}
  use libsql::{Builder};

  let build = Builder::new_remote_replica("file:replica.db", "libsql://...", "...")
     .build()
     .await?;
  let client = build.connect()?;
  ```

  ```php PHP theme={null}
  use Libsql\Database;

  $db = new Database(
      path: 'replica.db',
      url: getenv('TURSO_URL'),
      authToken: getenv('TURSO_AUTH_TOKEN'),
      syncInterval: 300 // Sync every 5 minutes
  );
  $conn = $db->connect();
  ```

  ```php Laravel theme={null}
  // config/database.php
  return [
      "default" => env("DB_CONNECTION", "libsql"),

      "connections" => [
          "libsql" => [
              "driver" => "libsql",
              "database" => database_path("database.db"),
              "url" => env("TURSO_DATABASE_URL"),
              "password" => env("TURSO_AUTH_TOKEN"),
              "sync_interval" => env("TURSO_SYNC_INTERVAL", 300),
          ],
          // ...
      ],
  ];

  // .env
  DB_CONNECTION=libsql
  TURSO_DATABASE_URL=libsql://...
  TURSO_AUTH_TOKEN=...
  TURSO_SYNC_INTERVAL=300
  ```
</CodeGroup>

You can sync changes from the remote database to the local replica manually:

<CodeGroup>
  ```ts TypeScript theme={null}
  await client.sync();
  ```

  ```go Go theme={null}
  if err := connector.Sync(); err != nil {
    fmt.Println("Error syncing database:", err)
  }
  ```

  ```rust Rust theme={null}
  client.sync().await?;
  ```

  ```php PHP theme={null}
  $db->sync();
  ```
</CodeGroup>

<br />

<Note>
  You should call `.sync()` in the background whenever your application wants to sync the remote and local embedded replica. For example, you can call it every 5 minutes or every time the application starts.
</Note>

## Things to know

* Do not open the local database while the embedded replica is syncing. This can lead to data corruption.
* In certain contexts, such as serverless environments without a filesystem, you can't use embedded replicas.
* There are a couple scenarios where you may sync more frames than you might
  expect.
  * A write that causes the internal btree to split at any node would cause many
    new frames to be written to the replication log.
  * A server restart that left the on-disk wal in dirty state would regenerate
    the replication log and sync additional frames.
  * Removing/invalidating the local files on disk could cause the embedded
    replica to re-sync from scratch.
* One frame equals 4kB of data (one on disk page frame), so if you write a 1
  byte row, it will always show up as a 4kB write since that is the unit in
  which libsql writes with.

## Deployment Guides

<CardGroup>
  <Card href="/features/embedded-replicas/with-fly" title="Turso + Fly">
    Deploy a JavaScript project with Embedded Replicas to Fly.io
  </Card>

  <Card href="/features/embedded-replicas/with-koyeb" title="Turso + Koyeb">
    Deploy a JavaScript/Rust project with Embedded Replicas to Koyeb
  </Card>

  <Card href="/features/embedded-replicas/with-railway" title="Turso + Railway">
    Deploy a JavaScript/Rust project with Embedded Replicas to Railway
  </Card>

  <Card href="/features/embedded-replicas/with-render" title="Turso + Render">
    Deploy a JavaScript project with Embedded Replicas to Render
  </Card>

  <Card href="/features/embedded-replicas/with-akamai" title="Turso + Linode by Akamai">
    Deploy a JavaScript/Rust project with Embedded Replicas to Akamai
  </Card>
</CardGroup>


# Turso + Linode by Akamai
Source: https://docs.turso.tech/features/embedded-replicas/with-akamai

Deploy a JavaScript/Rust app using [Turso Cloud embedded replicas](/features/embedded-replicas) to [Akamai](https://www.linode.com/).

<img alt="Akamai banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso Cloud](/cli/authentication#signup)
* Have an Akamai account - [create one](https://login.linode.com/signup)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step>
    Fork one of the following embedded replica projects from GitHub

    <CardGroup>
      <Card title="My Expenses Tracker - (Elysia + Bun)" icon="github" href="https://github.com/tursodatabase/embedded-replicas-with-js">
        See the full source code
      </Card>

      <Card title="Web Traffic API - (Rust)" icon="github" href="https://github.com/tursodatabase/embedded-replicas-with-rust">
        See the full source code
      </Card>
    </CardGroup>
  </Step>

  <Step title="Set up a Linode server">
    Configure and create a new linode. Then, [set up SSH authentication](https://www.linode.com/docs/guides/use-public-key-authentication-with-ssh/) to securely access the Linode server from your terminal.

    Prepare the newly created linode server environment by accessing and set it up for Rust/JavaScript development depending on the project you forked earlier. Install and set up [Git](https://git-scm.com/) too.
  </Step>

  <Step title="Transfer project to Linode server">
    SSH into your server, clone the project from GitHub, and follow its README instructions to set it up.
  </Step>

  <Step title="Deploy">
    Build, run the project, and set up load balancing for it.
    [pm2](https://www.npmjs.com/package/pm2) is one of the good candidates out there with built-in load balancing, log monitoring, and bug/exception alerts.

    <Info>
      You can go with your favorite options for where to buy domains, reverse proxy setup, and SSL certificates. [Caddy](https://caddyserver.com/) is another good option here.
    </Info>
  </Step>
</Steps>


# Embedded Replicas on Fly
Source: https://docs.turso.tech/features/embedded-replicas/with-fly

Deploy a JavaScript app using [Turso Cloud embedded replicas](/features/embedded-replicas) to [Fly.io](https://www.fly.io/).

<img alt="Koyeb banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso Cloud](/cli/authentication#signup)
* [Install the Fly.io CLI](https://fly.io/docs/hands-on/install-flyctl/)

<Steps>
  <Step title="Locate your application">
    You should have an application ready using your Turso Cloud database that you want to deploy to Fly.
  </Step>

  <Step title="Launch with Fly">
    Using the Fly CLI, launch it:

    ```bash theme={null}
    fly launch
    ```

    Your application will automatically deploy to Fly, but we're not ready yet.
  </Step>

  <Step title="Create a shared volume">
    Now create a volume that will be used to store the embedded replica(s):

    ```bash theme={null}
    fly volumes create libsql_data
    ```
  </Step>

  <Step title="Mount and configure volumes">
    The files `fly.toml` and `Dockerfile` created created when you launched previously.

    Update `fly.toml` this file to mount the new volume:

    ```toml theme={null}
    [[mounts]]
    source = "libsql_data"
    destination = "/app/data"
    ```

    Then inside `Dockerfile`, make sure you install and update `ca-certificates`:

    ```dockerfile theme={null}
    RUN apt-get update -qq && \
        apt-get install -y ca-certificates && \
        update-ca-certificates
    ```

    Make sure to also add the following line after any `COPY` commands to copy the certificates:

    ```dockerfile theme={null}
    COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
    ```
  </Step>

  <Step title="Configure the libSQL client">
    You will want to change the `url` to point to a local file, and set the `syncUrl` to be your Turso Cloud database URL:

    ```ts theme={null}
    import { createClient } from "@libsql/client";

    const client = createClient({
      url: "file:/app/data/local.db",
      syncUrl: process.env.TURSO_DATABASE_URL,
      authToken: process.env.TURSO_AUTH_TOKEN,
      syncInterval: 60,
    });
    ```
  </Step>

  <Step title="Deploy your updated app">
    ```bash theme={null}
    fly deploy
    ```
  </Step>
</Steps>


# Turso + Koyeb
Source: https://docs.turso.tech/features/embedded-replicas/with-koyeb

Deploy a JavaScript/Rust app using [Turso Cloud embedded replicas](/features/embedded-replicas) to [Koyeb](https://www.koyeb.com/).

<img alt="Koyeb banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso Cloud](/cli/authentication#signup)
* Have a Koyeb account - [create one](https://app.koyeb.com/)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step>
    Fork one of the following embedded replica project from GitHub

    <CardGroup>
      <Card title="My Expenses Tracker - (Elysia + Bun)" icon="github" href="https://github.com/tursodatabase/embedded-replicas-with-js">
        See the full source code
      </Card>

      <Card title="Web Traffic API - (Rust)" icon="github" href="https://github.com/tursodatabase/embedded-replicas-with-rust">
        See the full source code
      </Card>
    </CardGroup>

    <Note>
      Or, you can:

      <Card title="Deploy to Koyeb with a single-click" href="https://app.koyeb.com/deploy?name=er-with-js&type=git&repository=tursodatabase/embedded-replicas-with-js&branch=main&env[PORT]=8000&env[TURSO_DATABASE_URL]=REPLACE_ME&env[TURSO_AUTH_TOKEN]=REPLACE_ME&env[LOCAL_DB]=file:expenses.db" />
    </Note>
  </Step>

  <Step title="Add a new Koyeb app">
    1. Create a new app in the Koyeb control panel.

    2. Select GitHub as the deployment option.

    3. Import the GitHub project to Koyeb.
  </Step>

  <Step title="Fill in the environment variables on Koyeb's deploy page">
    <img alt="Koyeb deploy page - environment variables" />
  </Step>

  <Step title="Deploy">
    Click the **Deploy** button at the bottom to deploy your web service.
  </Step>
</Steps>


# Turso + Railway
Source: https://docs.turso.tech/features/embedded-replicas/with-railway

Deploy a JavaScript/Rust app using [Turso Cloud embedded replicas](/features/embedded-replicas) to [Railway](https://railway.app/).

<img alt="Koyeb banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso Cloud](/cli/authentication#signup)
* [Install the Railway CLI](https://docs.railway.app/guides/cli#installing-the-cli)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Get application code">
    Fork and clone the following embedded replica project from GitHub locally:

    <CardGroup>
      <Card title="My Expenses Tracker - (Elysia + Bun)" icon="github" href="https://github.com/tursodatabase/embedded-replicas-with-js">
        See the full source code
      </Card>

      <Card title="Web Traffic API - (Rust)" icon="github" href="https://github.com/tursodatabase/embedded-replicas-with-rust">
        See the full source code
      </Card>
    </CardGroup>
  </Step>

  <Step title="Create a new Railway project">
    Run the following command to create a new Railway project. Provide the project's name when prompted.

    ```sh theme={null}
    railway init
    ```
  </Step>

  <Step title="Add a service to the Railway project">
    [Create a new empty service](https://docs.railway.app/guides/services#creating-a-service) on your Railway project to act as your app's deployment target.
  </Step>

  <Step title="Link application to service">
    Run the following command to list and select the service to link to your application:

    ```sh theme={null}
    railway service
    ```
  </Step>

  <Step title="Add database credentials">
    Open the service on your Railway dashboard and add your Turso Cloud database credentials.

    ```sh theme={null}
    TURSO_DATABASE_URL=libsql://[db-name]-[github-username].turso.io
    TURSO_AUTH_TOKEN=...
    LOCAL_DB=file:local-db-name.db
    ```
  </Step>

  <Step title="Deploy">
    Run the following command to deploy your application:

    ```sh theme={null}
    railway up
    ```

    <Info>
      Make sure you [expose your application to the internet](https://docs.railway.app/guides/public-networking) to make it accessible from the public network.
    </Info>

    <Warning>
      If you are on a free plan, you'll need to connect your Railway account to GitHub to have access to code deployments.
    </Warning>
  </Step>
</Steps>


# Turso + Render
Source: https://docs.turso.tech/features/embedded-replicas/with-render

Deploy a JavaScript app using [Turso Cloud embedded replicas](/features/embedded-replicas) to [Render](https://render.com/).

<img alt="Render banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso Cloud](/cli/authentication#signup)
* Have a Render account - [create one](https://dashboard.render.com/)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Get application code">
    Fork the following embedded replica project from GitHub locally:

    <Card title="My Expenses Tracker - (Elysia + Bun)" icon="github" href="https://github.com/tursodatabase/embedded-replicas-with-js">
      See the full source code
    </Card>

    <Note>
      Or, you can:

      <Card title="Deploy to Render with a single-click" href="https://render.com/deploy?repo=https://github.com/tursodatabase/embedded-replicas-with-js" />
    </Note>
  </Step>

  <Step title="Create a web service">
    Create a new Render **Web Service** by clicking on the "New Web Service" button on the Web Services card inside you Render dashboard.
  </Step>

  <Step title="Connect to Git repository">
    1. Select "build and deploy from a Git repository" and proceed to the next page.

    2. Click on "Connect" for your target project repository
  </Step>

  <Step title="Set project's environment variables">
    On the web service configuration page, under "Advanced" add **a secret file** and fill it in with your database secret credentials:

    <img alt="Render secret credentials" />
  </Step>

  <Step title="Deploy project">
    Scroll to the bottom of the web service configuration page and click on "Create Web Service".
  </Step>
</Steps>


# Multi-DB Schemas (Deprecated)
Source: https://docs.turso.tech/features/multi-db-schemas

Create and share a single schema across multiple databases.

<Warning>
  This feature is now deprecated for all new users. Existing paid users can
  continue to use Multi-DB Schemas — [read the
  announcement](https://turso.tech/blog/upcoming-changes-to-the-turso-platform-and-roadmap)
</Warning>

Turso allows you to create a single schema and share it across multiple databases. This is useful for creating a multi-tenant application where each tenant has their own database.

<Info>
  **Using TypeScript?** To provision databases programmatically, use the [`@tursodatabase/api`](https://www.npmjs.com/package/@tursodatabase/api) client rather than calling the REST API directly. See the [SDK quickstart](/api-reference/introduction#quickstart-with-the-typescript-sdk).
</Info>

<Info>
  Try the [Turso Per User Starter](https://github.com/notrab/turso-per-user-starter) to get started with a multi-tenant application using Next.js, Drizzle, Clerk, libSQL, and Turso Multi-DB Schemas.
</Info>

## How it works

1. You create a database that is used as the parent schema database.
2. You create one or more databases that are used as the child databases.
3. You apply schema changes to the parent database, child databases are automatically updated with the new schema.

## Usage

You can create and manage parent or child databases using the [Turso CLI](/cli/db/create), and [Platform API](/api-reference/databases/create).

<iframe title="Multi database schema changes" />

### Turso CLI

Make sure you have the [Turso CLI](/cli/installation) installed, and [logged in](/cli/auth/login).

<Steps>
  <Step title="Create Parent Database">
    Create a single database using the `--type` flag set to `schema`:

    ```bash theme={null}
    turso db create parent-db --type schema
    ```
  </Step>

  <Step title="Create Schema">
    Connect to your database using the shell to `CREATE TABLE`, `CREATE TRIGGER`, `CREATE VIEW`, and anything else to setup your schema:

    ```bash theme={null}
    turso db shell parent-db
    ```
  </Step>

  <Step title="Create Child Database(s)">
    Create one or more child databases using the `--schema` flag with the name of the parent database:

    ```bash theme={null}
    turso db create child-db --schema parent-db
    ```
  </Step>

  <Step title="Apply Additional Schema Changes">
    You can now apply additional schema changes to the parent database, and the child databases will be automatically updated:

    ```bash theme={null}
    turso db shell parent-db "CREATE TABLE users (id INT PRIMARY KEY, name TEXT);"
    ```
  </Step>
</Steps>

### Platform API

Make sure you have an API Token, and know your Organization name:

<Steps>
  <Step title="Create Parent Database">
    Create a database and set the `is_schema` field to `true`:

    ```bash theme={null}
    curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \
      -H 'Authorization: Bearer TOKEN' \
      -H 'Content-Type: application/json' \
      -d '{
          "name": "parent-db",
          "group": "default",
          "is_schema": true
      }'
    ```
  </Step>

  <Step title="Create Schema">
    Connect to your database using the shell, [SDK](/sdk), or migration script to `CREATE TABLE`, `CREATE TRIGGER`, `CREATE VIEW`, and anything else to setup your schema:

    ```bash Turso CLI theme={null}
    turso db shell parent-db
    ```
  </Step>

  <Step title="Create Child Database(s)">
    Create one or more child databases and pass the `schema` field the name of your parent database:

    ```bash theme={null}
    curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \
      -H 'Authorization: Bearer TOKEN' \
      -H 'Content-Type: application/json' \
      -d '{
          "name": "child-db",
          "group": "default",
          "schema": "parent-db"
      }'
    ```
  </Step>

  <Step title="Apply Additional Schema Changes">
    You can now apply additional schema changes to the parent database, and the child databases will be automatically updated:

    ```bash theme={null}
    turso db shell parent-db "CREATE TABLE users (id INT PRIMARY KEY, name TEXT);"
    ```
  </Step>
</Steps>

## Things to know

* Schema databases cannot be shared across groups or globally via an organization/account.
* You can (but not recommended) `INSERT` rows into the parent database that can be queried by the child database(s).
  * Be aware of any constraints that may conflict with the child database(s).
* You can't delete a parent database if it has one or more child databases.
* When a migration is applied to the schema database:
  * It's first run as a dry-run on the schema and all other associated databases.
  * If successful, a migration job is created.
  * Tasks are created for each database that references this schema.
  * The migration is then applied to each referencing database.
  * Finally, if all tasks succeed, the migration is applied to the schema database itself.
  * You can't create or delete a database if there are any migrations running.
* **During a migration, all databases are locked to write operations.**
* Make sure any application querying the child databases handle any databases not yet updated with the schema.
* You cannot apply schema changes to a child database directly. You must use the parent (schema) database.
* You can check the status of a migration using the `/jobs` endpoint — [learn more](/sdk/http/reference#schema-migration-jobs).


# Point-in-Time Recovery
Source: https://docs.turso.tech/features/point-in-time-recovery



Turso supports point-in-time recovery (PITR) for databases. PITR allows you to restore a database to a specific point in time. This is useful for recovering from user errors, such as dropping a table by mistake.

<Info>
  Backups are created automatically at `COMMIT`. Free plan users can restore to any point in time within the last 24 hours. Developer, Scaler, and Pro users can restore to any point in time within the last 10, 30, or 90 days, respectively.
</Info>

## How it works

1. You create a new database from the existing database using the CLI or API.
2. You update your application to use the new database connection string.
3. You delete the old database when you no longer need it.

## Usage

<CodeGroup>
  ```bash CLI theme={null}
  turso db create my-new-database --from-db my-existing-database --timestamp 2024-01-01T00:00:00Z
  ```

  ```bash Platform API theme={null}
  curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \
    -H 'Authorization: Bearer TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
        "name": "new-database",
        "group": "default",
        "seed": {
          "type:": "database",
          "name": "my-existing-database",
          "timestamp": "2024-01-01T00:00:00Z"
        }
    }'
  ```
</CodeGroup>

Refer to the following references for more details about all arguments:

<CardGroup>
  <Card title="CLI Reference" icon="terminal" href="/cli/db/create" />

  <Card title="API Reference" icon="code" href="/api-reference/databases/create" />
</CardGroup>

## Things to know

* Restoring from a PITR creates a new database. You will need to update your application to use the new database connection string.
* You cannot restore from a PITR to a database that already exists.
* You will need to [create a new token](/cli/db/tokens/create) (or use a group token) to connect to the new database.
* You will need to manually delete the old database when you no longer need it.
* Restores count towards your plan's database quota.


# SQLite Extensions
Source: https://docs.turso.tech/features/sqlite-extensions

Learn how to use SQLite extensions with Turso.

Turso comes loaded with extensions that provide additional functionality to SQLite. Some plans will have more extensions preloaded.

<Info>
  If you're looking to enable vector extensions, you should instead consider
  using the native [libSQL vector datatype](/features/ai-and-embeddings).
</Info>

## Preloaded Extensions

The following extensions are always-on and available:

| Extension                                                                     | Version  | Description                                                                 |
| ----------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------- |
| [JSON](https://www.sqlite.org/json1.html)                                     | Built-in | Work with JSON data in SQLite.                                              |
| [FTS5](https://www.sqlite.org/fts5.html)                                      | Built-in | Full-text search and indexing.                                              |
| [R\*Tree](https://www.sqlite.org/rtree.html)                                  | Built-in | Indexing and querying spatial data.                                         |
| [SQLean Crypto](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/crypto.md) | 0.24.1   | Hashing, message digest, encoding, and decoding.                            |
| [SQLean Fuzzy](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/fuzzy.md)   | 0.24.1   | Fuzzy string matching and phonetics. A fork of Spellfix1 with improvements. |
| [SQLean Math](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/math.md)     | 0.24.1   | Advanced mathematical calculations.                                         |
| [SQLean Stats](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/stats.md)   | 0.24.1   | Common statistical functions with SQLite.                                   |
| [SQLean Text](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/text.md)     | 0.24.1   | String manipulation (reverse, split) with SQLite.                           |
| [SQLean UUID](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/uuid.md)     | 0.24.1   | Limited support for RFC 4122 compliant UUIDs.                               |

## Optional Extensions

The following extensions are also enabled in addition to the preloaded extensions on databases using the Fly Cloud Provider (Pro & Enterprise).

| Extension                                                                     | Version | Description                                  |
| ----------------------------------------------------------------------------- | ------- | -------------------------------------------- |
| [SQLean Regexp](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/regexp.md) | 0.24.1  | Regular expressions with SQLite.             |
| [sqlite-vec](https://github.com/asg017/sqlite-vec/tree/v0.1.3)                | 0.1.3   | The Vector Search successor to `sqlite-vss`. |


# Agent Databases
Source: https://docs.turso.tech/guides/agent-databases



<Info>
  Looking for a complete agent state management solution? Check out [AgentFS](/agentfs/introduction) - our specialized SDK designed specifically for AI agents, offering filesystem operations, key-value storage, and automatic tool call tracking in a single database file.
</Info>

AI agents need databases to store context, track state, and maintain memory across executions. Turso Database offers two powerful approaches:

* **Embedded Databases** for local-first agent processing, and

* **Turso Sync** for distributed agent coordination with cloud persistence.

## Embedded Databases (Local-First)

Use embedded databases for agents that process data locally within a single workflow, need offline-capable access, or work with temporary data without sharing state with other agents.

Local data pipelines, on-device processing of sensitive data, development/testing workflows, and single-agent analysis tasks.

```javascript theme={null}
import { connect } from "@tursodatabase/database";

// Create a local embedded database for this agent
const db = await connect("agent-workflow.db");

// Agent can now store and query data locally
const stmt = db.prepare(`
  CREATE TABLE IF NOT EXISTS steps (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    action TEXT NOT NULL,
    result TEXT,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);
stmt.run();

// Track agent workflow steps
db.prepare("INSERT INTO steps (action, result) VALUES (?, ?)").run(
  "fetch_data",
  "success",
);
```

Benefit from zero network latency, full offline capability, complete data privacy, and simple single-file deployment.

<Info>
  Data is isolated to this agent instance with no built-in multi-agent collaboration or cross-session persistence (unless you manage the file yourself).
</Info>

## Turso Sync (Cloud-Connected)

Use Turso Sync for agents that need persistent memory across sessions, coordinate with other agents, or require cloud-backed durability for long-running workflows.

💡 Conversational agents with session memory, multi-agent coordination systems, long-running workflows with recovery needs, and agents reporting to central systems.

```javascript theme={null}
import { connect } from "@tursodatabase/database";

// Connect to a synced database (embedded locally, synced to Turso Cloud)
const db = await connect({
  path: "agent-memory.db",
  url: "https://your-database.turso.io",
  authToken: "your-auth-token",
  sync: "full", // Bidirectional sync
});

// Agent can access data from previous sessions or other agents
const previousContext = db
  .prepare("SELECT * FROM conversation_history WHERE session_id = ?")
  .all("session-123");

// New data is automatically synced to cloud and other agent instances
db.prepare("INSERT INTO agent_actions (agent_id, action) VALUES (?, ?)").run(
  "agent-1",
  "processed_document",
);

// Manually trigger sync if needed
await db.sync();
```

💡 Persistent memory across restarts, multi-agent coordination via shared state, cloud backup and durability, with fast local reads and background sync.

<Info>
  **Sync modes:** `sync` (bidirectional), `pull` (cloud → agent), `push` (agent → cloud).
</Info>

## Multi-Agent Architectures

### Pattern 1: Isolated Agent Databases

Each agent gets its own embedded database. Best for independent agents with no shared state.

```javascript theme={null}
// Agent 1
const agent1DB = await connect("agent-1.db");

// Agent 2
const agent2DB = await connect("agent-2.db");
```

<Info>
  Independent agents performing separate tasks with no coordination needs and maximum isolation.
</Info>

### Pattern 2: Shared Database with Sync

Multiple agents connect to the same synced database. Best for coordinated agent systems.

```javascript theme={null}
// Agent 1 connects and writes tasks
const agent1DB = await connect({
  path: "agent-1-local.db",
  url: "https://shared-tasks.turso.io",
  authToken: process.env.TURSO_TOKEN,
  sync: "full",
});

agent1DB.prepare("INSERT INTO tasks (status) VALUES (?)").run("pending");

// Agent 2 connects to same database and reads tasks
const agent2DB = await connect({
  path: "agent-2-local.db",
  url: "https://shared-tasks.turso.io", // Same database
  authToken: process.env.TURSO_TOKEN,
  sync: "full",
});

const pendingTasks = agent2DB
  .prepare("SELECT * FROM tasks WHERE status = ?")
  .all("pending");
```

💡 Agents coordinating work through shared task queues or a common knowledge base.

### Pattern 3: Hub-and-Spoke

A central synced database with agents pushing results and pulling new tasks.

```javascript theme={null}
// Worker agents push results only
const workerDB = await connect({
  path: "worker-local.db",
  url: "https://hub.turso.io",
  authToken: process.env.WORKER_TOKEN,
  sync: "push", // Only push results to cloud
});

// Coordinator agent pulls from all workers
const coordinatorDB = await connect({
  path: "coordinator-local.db",
  url: "https://hub.turso.io",
  authToken: process.env.COORDINATOR_TOKEN,
  sync: "pull", // Only pull data from cloud
});
```

💡 Many worker agents feeding results to a central coordinator for monitoring or aggregation with unidirectional data flow.

## Database-Per-Agent Pattern

For systems managing many agents, you can create individual databases for each agent using the [Platform API](https://docs.turso.tech/api-reference):

```javascript theme={null}
import { createClient } from "@tursodatabase/api";

const turso = createClient({
  token: process.env.TURSO_PLATFORM_API_TOKEN,
  org: process.env.TURSO_ORG_NAME,
});

// Create a dedicated database for each agent
async function provisionAgentDatabase(agentId) {
  const database = await turso.databases.create(`agent-${agentId}`, {
    group: "default",
  });

  console.log(`Database created for agent ${agentId}: ${database.Name}`);
  return database;
}
```

<Info>
  Complete isolation between agents, independent scaling per agent, improved security (compromised agent only affects one database), and easy cleanup when agents are retired.
</Info>

## Examples

Explore these examples to see agent database patterns in action:

* [sync-node](https://github.com/tursodatabase/turso/blob/main/examples/javascript/sync-node) — Node.js with bidirectional sync to Turso Cloud
* [sync-wasm-vite](https://github.com/tursodatabase/turso/blob/main/examples/javascript/sync-wasm-vite) — Browser (WASM) with bidirectional sync to Turso Cloud
* [database-node](https://github.com/tursodatabase/turso/blob/main/examples/javascript/database-node) — Node.js, local file database (no sync)
* [database-wasm-vite](https://github.com/tursodatabase/turso/blob/main/examples/javascript/database-wasm-vite) — Browser (WASM), local database in the browser


# AI Memory
Source: https://docs.turso.tech/guides/ai-memory



AI agents start every session from scratch. A persistent memory system lets agents store lessons and retrieve relevant context via vector search. Turso's embedded database with built-in vector functions makes this possible in a single local file.

This guide covers the schema and queries for building a memory system with vector similarity search.

## Schema

At its simplest, a memory system needs just two tables: one for memories and one for tracking which memories were used in which tasks.

```sql theme={null}
CREATE TABLE IF NOT EXISTS memories (
    id              TEXT PRIMARY KEY,
    content         TEXT NOT NULL,
    embedding       F8_BLOB(384),
    category        TEXT NOT NULL,
    created_at      INTEGER NOT NULL,
    last_retrieved  INTEGER,
    retrieval_count INTEGER DEFAULT 0,
    source_task     TEXT
);

CREATE TABLE IF NOT EXISTS tasks (
    id               TEXT PRIMARY KEY,
    description      TEXT,
    embedding        F8_BLOB(384),
    started_at       INTEGER,
    finished_at      INTEGER
);
```

* **`memories`** stores each lesson with its vector embedding and a category (e.g. `correction`, `insight`, `user`, `discovery`). The embedding is a 384-dimensional int8-quantized vector (`F8_BLOB(384)`).
* **`tasks`** records each task the agent worked on, linking task descriptions to their embeddings for retrieval.

## Connecting

```javascript theme={null}
import { connect } from "@tursodatabase/database";

const db = await connect(".memory/agent.db");
await db.exec(SCHEMA);
```

## Storing a memory

When the agent learns something — a correction, a user preference, an insight — store it with an embedding. Use `vector8()` to convert a JSON float array into an int8-quantized vector:

```sql theme={null}
INSERT INTO memories (id, content, embedding, category, created_at, source_task)
VALUES (?, ?, vector8(?), ?, ?, ?);
```

The parameter for `vector8()` is a JSON-stringified float array (e.g. 384 dimensions from a model like `all-MiniLM-L6-v2`). Turso handles the quantization internally:

```javascript theme={null}
db.prepare(`
  INSERT INTO memories (id, content, embedding, category, created_at)
  VALUES (?, ?, vector8(?), ?, ?)
`).run(id, content, JSON.stringify(Array.from(embedding)), "correction", Date.now());
```

## Retrieving relevant memories

When a new task starts, find the most relevant memories using Turso's `vector_distance_cos()` function:

```sql theme={null}
SELECT id, content, category, created_at, retrieval_count,
       vector_distance_cos(embedding, vector8(?)) AS distance
FROM memories
WHERE embedding IS NOT NULL
ORDER BY distance ASC
LIMIT ?;
```

<Info>
  `vector_distance_cos()` returns cosine **distance** (0 = identical, 2 = opposite). Lower is better. To convert to similarity: `1.0 - distance`.
</Info>

After retrieval, update the memory's metadata:

```sql theme={null}
UPDATE memories SET last_retrieved = ?, retrieval_count = retrieval_count + 1
WHERE id = ?;
```

## Correcting wrong memories

When a memory turns out to be wrong, delete it and store a corrected version:

```sql theme={null}
DELETE FROM memories WHERE id = ?;

INSERT INTO memories (id, content, embedding, category, created_at)
VALUES (?, ?, vector8(?), 'correction', ?);
```

## Task tracking

Record tasks to understand what the agent has been working on:

```sql theme={null}
-- Start a task
INSERT INTO tasks (id, description, embedding, started_at)
VALUES (?, ?, vector8(?), ?);

-- Complete a task
UPDATE tasks SET finished_at = ? WHERE id = ?;
```

## Purging old memories

Remove memories that have been around a long time but are never retrieved:

```sql theme={null}
DELETE FROM memories
WHERE retrieval_count = 0 AND created_at < ?;
```

## Key design points

* **Vector search** uses Turso's `vector8()` and `vector_distance_cos()` for cosine similarity directly in SQL. Int8 quantization reduces storage by 75% compared to float32 with minimal impact on search quality.
* For small per-project datasets, a full table scan with `ORDER BY distance LIMIT k` is fast enough without a vector index.
* Everything runs in a single embedded database file with no external services.

## Going further

The schema above is deliberately minimal. In practice you will want to experiment with strategies for keeping the memory store useful as it grows. Some ideas:

* **Weighted memories** — add a `weight REAL` column and boost or penalize memories based on whether the agent found them useful after retrieval. Use an exponential moving average to update weights over time.
* **Time decay** — multiply a recency factor into the retrieval ranking so that stale memories rank lower. For example: `(1.0 - distance) * POWER(0.95, days_since_last_retrieved)`.
* **Garbage collection** — periodically remove memories with low weight and high retrieval count (retrieved often, never useful), or memories that have never been retrieved after a threshold period.
* **Task scoring** — track outcome metrics (tokens used, errors, corrections) per task and use them to compute a credit signal for the memories that were retrieved during that task.
* **Retrieval attribution** — add a join table `(memory_id, task_id, similarity, credit)` to record exactly which memories were used in each task and how they were rated.

<Warning>
  There is no single best approach — the right strategy depends on your agent's workload, how often memories are created, and how aggressively you want to prune. Start simple and iterate.
</Warning>

## Example

[memelord](https://github.com/glommer/memelord) is an MCP server and hooks system that implements this pattern with reinforcement learning and weight decay as a persistent memory layer for AI coding agents.


# Code Indexing
Source: https://docs.turso.tech/guides/code-indexing



AI coding agents work better when they can search a codebase semantically ("find the authentication logic") rather than just by filename or grep. A code index backed by Turso can combine full-text search over identifiers with vector similarity search over embeddings, all in a single embedded database.

This guide covers the schema and queries for building a code indexer with both FTS and vector search.

## Schema

```sql theme={null}
CREATE TABLE IF NOT EXISTS codebases (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    root_path   TEXT NOT NULL UNIQUE,
    name        TEXT NOT NULL DEFAULT '',
    indexed_at  INTEGER NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS chunks (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    codebase_id     INTEGER NOT NULL REFERENCES codebases(id),
    file_path       TEXT NOT NULL,
    chunk_key       TEXT NOT NULL UNIQUE,
    language        TEXT NOT NULL,
    kind            TEXT NOT NULL,
    name            TEXT NOT NULL DEFAULT '',
    signature       TEXT NOT NULL DEFAULT '',
    snippet         TEXT NOT NULL,
    start_line      INTEGER NOT NULL,
    end_line        INTEGER NOT NULL,
    file_hash       TEXT NOT NULL,
    indexed_at      INTEGER NOT NULL,
    embedding       F8_BLOB(384),
    embedding_model TEXT DEFAULT ''
);

CREATE TABLE IF NOT EXISTS indexed_files (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    codebase_id INTEGER NOT NULL REFERENCES codebases(id),
    file_path   TEXT NOT NULL,
    file_hash   TEXT NOT NULL,
    chunk_count INTEGER NOT NULL DEFAULT 0,
    indexed_at  INTEGER NOT NULL,
    UNIQUE(codebase_id, file_path)
);
```

### How it fits together

* **`codebases`** registers each project root. A single database can index multiple codebases.
* **`chunks`** stores individual semantic units extracted from source files — functions, structs, classes, impl blocks, etc. Each chunk has a `name`, `signature`, code `snippet`, line range, and optionally a vector `embedding`. The `chunk_key` is a unique identifier (e.g. `file_path::kind::name`) for upsert operations.
* **`indexed_files`** tracks which files have been indexed and their content hashes, enabling incremental re-indexing — only changed files are re-processed.

## Connecting

The FTS index features require an experimental flag at connection time:

```javascript theme={null}
import { connect } from "@tursodatabase/database";

const db = await connect(".index/code.db", {
  experimental: ["index_method"],
});
await db.exec(SCHEMA);
```

<Info>
  The `experimental: ["index_method"]` flag enables Turso's `USING fts` index syntax and the `fts_match()` / `fts_score()` functions.
</Info>

## Indexing files

### Upserting chunks

When a file is parsed, upsert its chunks. Setting `embedding = NULL` on conflict forces re-embedding when content changes:

```sql theme={null}
INSERT INTO chunks (codebase_id, file_path, chunk_key, language, kind, name,
                    signature, snippet, start_line, end_line, file_hash, indexed_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(chunk_key) DO UPDATE SET
  language = excluded.language,
  kind = excluded.kind,
  name = excluded.name,
  signature = excluded.signature,
  snippet = excluded.snippet,
  start_line = excluded.start_line,
  end_line = excluded.end_line,
  file_hash = excluded.file_hash,
  indexed_at = excluded.indexed_at,
  embedding = NULL,
  embedding_model = '';
```

### Tracking indexed files

```sql theme={null}
INSERT INTO indexed_files (codebase_id, file_path, file_hash, chunk_count, indexed_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(codebase_id, file_path) DO UPDATE SET
  file_hash = excluded.file_hash,
  chunk_count = excluded.chunk_count,
  indexed_at = excluded.indexed_at;
```

### Skipping unchanged files

Before parsing a file, check if it has changed:

```sql theme={null}
SELECT file_hash FROM indexed_files WHERE codebase_id = ? AND file_path = ?;
```

If the hash matches, skip it entirely.

## Storing embeddings

Embeddings can be stored as int8-quantized vectors using Turso's `vector8()` function, which reduces storage from 1,536 bytes (float32, 384 dims) to 395 bytes:

```sql theme={null}
UPDATE chunks SET embedding = vector8(?), embedding_model = ? WHERE chunk_key = ?;
```

The parameter for `vector8()` is a JSON-stringified float array. Turso handles the quantization internally.

Find chunks that need embedding:

```sql theme={null}
SELECT chunk_key, name, signature, file_path, kind, snippet
FROM chunks
WHERE codebase_id = ? AND (embedding IS NULL OR embedding_model != ?)
LIMIT ?;
```

## Full-text search

Turso provides an FTS index with weighted BM25 scoring that is separate from SQLite's `fts5` virtual tables.

### Creating the FTS index

Create an FTS table per codebase and populate it from the chunks:

```sql theme={null}
-- Create the FTS table
CREATE TABLE IF NOT EXISTS fts_1 (
    chunk_id    INTEGER NOT NULL REFERENCES chunks(id) ON DELETE CASCADE,
    name        TEXT NOT NULL DEFAULT '',
    signature   TEXT NOT NULL DEFAULT ''
);

-- Populate from chunks
INSERT INTO fts_1 (chunk_id, name, signature)
SELECT id, name, signature FROM chunks WHERE codebase_id = 1;

-- Create the FTS index with weighted columns
CREATE INDEX IF NOT EXISTS idx_fts_1 ON fts_1
    USING fts (name, signature)
    WITH (
        tokenizer = 'default',
        weights = 'name=5.0,signature=3.0'
    );
```

The `weights` parameter controls BM25 scoring — here, matches in the function/type `name` are weighted 5x and `signature` matches 3x.

### Searching with FTS

```sql theme={null}
SELECT chunk_id, fts_score(name, signature, ?1) AS score
FROM fts_1
WHERE fts_match(name, signature, ?1)
ORDER BY score DESC
LIMIT ?;
```

Then fetch the full chunk data:

```sql theme={null}
SELECT chunk_key, file_path, name, kind, signature, snippet, start_line, end_line
FROM chunks WHERE id = ?;
```

## Vector search

For semantic/natural language queries, use vector cosine distance:

```sql theme={null}
SELECT chunk_key, file_path, name, kind, signature, snippet, start_line, end_line,
       vector_distance_cos(embedding, vector8(?)) AS distance
FROM chunks
WHERE embedding IS NOT NULL
ORDER BY distance ASC
LIMIT ?;
```

The score is `1 - distance` (cosine similarity from cosine distance).

## Hybrid search

For the best results, combine both approaches using Reciprocal Rank Fusion (RRF). Run the FTS and vector queries separately, then merge results in application code:

```javascript theme={null}
// Run both searches
const ftsResults = await ftsSearch(query, limit);
const vecResults = await vectorSearch(queryEmbedding, limit);

// Reciprocal Rank Fusion
const k = 60; // RRF constant
const scores = new Map();

ftsResults.forEach((r, i) => {
  const key = r.chunk_key;
  scores.set(key, (scores.get(key) || 0) + 1 / (k + i + 1));
});

vecResults.forEach((r, i) => {
  const key = r.chunk_key;
  scores.set(key, (scores.get(key) || 0) + 1 / (k + i + 1));
});

// Sort by combined score
const merged = [...scores.entries()]
  .sort((a, b) => b[1] - a[1])
  .slice(0, limit);
```

## Removing stale files

When files are deleted from the codebase, clean up their chunks and records:

```sql theme={null}
DELETE FROM chunks WHERE codebase_id = ? AND file_path = ?;
DELETE FROM indexed_files WHERE codebase_id = ? AND file_path = ?;
```

After removing stale data, rebuild the FTS table to keep the index consistent:

```sql theme={null}
DROP TABLE IF EXISTS fts_1;
-- Then recreate and repopulate as shown above
```

## Key design points

* **Incremental indexing** via file hashes means only changed files are re-parsed and re-embedded, making updates fast even on large codebases.
* **Two search modes** — FTS for identifier/keyword lookup and vector search for semantic/natural language queries — cover different use cases. Hybrid search combines both.
* **Weighted FTS** (`name=5.0, signature=3.0`) biases results toward function and type names, which is what developers usually search for.
* **Int8 quantized vectors** via `vector8()` reduce storage by 75% compared to float32 with minimal impact on search quality.
* Everything runs in a single embedded database file with no external services.

## Example

[codemogger](https://github.com/glommer/codemogger) is an MCP server that implements this pattern as a code indexer for AI coding agents.


# Content Caching
Source: https://docs.turso.tech/guides/content-caching



Tools and agents frequently re-read files they have already seen, wasting tokens and time. A content-addressed cache backed by an embedded Turso database can detect unchanged files and return diffs instead of full content, dramatically reducing redundant I/O.

This guide walks through the schema and queries needed to build a file content cache with per-session tracking.

## Schema

The cache uses four tables: one for file versions, one for per-session read pointers, and two for statistics.

```sql theme={null}
CREATE TABLE IF NOT EXISTS file_versions (
  path        TEXT NOT NULL,
  hash        TEXT NOT NULL,
  content     TEXT NOT NULL,
  lines       INTEGER NOT NULL,
  created_at  INTEGER NOT NULL,
  PRIMARY KEY (path, hash)
);

CREATE TABLE IF NOT EXISTS session_reads (
  session_id  TEXT NOT NULL,
  path        TEXT NOT NULL,
  hash        TEXT NOT NULL,
  read_at     INTEGER NOT NULL,
  PRIMARY KEY (session_id, path)
);

CREATE TABLE IF NOT EXISTS stats (
  key   TEXT PRIMARY KEY,
  value INTEGER NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS session_stats (
  session_id  TEXT NOT NULL,
  key         TEXT NOT NULL,
  value       INTEGER NOT NULL DEFAULT 0,
  PRIMARY KEY (session_id, key)
);

INSERT OR IGNORE INTO stats (key, value) VALUES ('tokens_saved', 0);
```

### How it fits together

* **`file_versions`** is a content-addressed store. The composite primary key `(path, hash)` means the same file can have multiple versions stored simultaneously. When a file changes and then changes back (e.g. a branch switch), the old version is already cached.
* **`session_reads`** tracks which version of each file a given session last saw. This is keyed by `(session_id, path)` so each session independently tracks its own read state.
* **`stats`** and **`session_stats`** track cumulative token savings globally and per session.

## Connecting

```javascript theme={null}
import { connect } from "@tursodatabase/database";

const db = await connect(".cache/content.db");
await db.exec(SCHEMA); // The CREATE TABLE statements above
```

## Storing a file version

When a file is read for the first time (or has changed), store its content keyed by a hash. `INSERT OR IGNORE` makes this a no-op if the same version already exists:

```sql theme={null}
INSERT OR IGNORE INTO file_versions (path, hash, content, lines, created_at)
VALUES (?, ?, ?, ?, ?);
```

The hash should be computed from the file content (e.g. a truncated SHA-256). This makes storage content-addressed — identical content is never stored twice for the same path.

## Tracking reads per session

Each session needs to know what it last saw for a given file. On first read, record the session's read pointer:

```sql theme={null}
INSERT OR REPLACE INTO session_reads (session_id, path, hash, read_at)
VALUES (?, ?, ?, ?);
```

On subsequent reads, check what the session last saw:

```sql theme={null}
SELECT hash FROM session_reads WHERE session_id = ? AND path = ?;
```

If the stored hash matches the current file hash, the file is unchanged — return a short confirmation instead of the full content. If the hash differs, fetch the old content to compute a diff:

```sql theme={null}
SELECT content FROM file_versions WHERE path = ? AND hash = ?;
```

Then update the session's read pointer to the new hash:

```sql theme={null}
UPDATE session_reads SET hash = ?, read_at = ? WHERE session_id = ? AND path = ?;
```

## Tracking token savings

Use an atomic counter for global statistics and an upsert for per-session tracking:

```sql theme={null}
-- Global counter
UPDATE stats SET value = value + ? WHERE key = 'tokens_saved';

-- Per-session counter (upsert)
INSERT INTO session_stats (session_id, key, value)
VALUES (?, 'tokens_saved', ?)
ON CONFLICT(session_id, key) DO UPDATE SET value = value + ?;
```

## Querying statistics

```sql theme={null}
-- Number of distinct files cached
SELECT COUNT(DISTINCT path) as file_count FROM file_versions;

-- Global tokens saved
SELECT value FROM stats WHERE key = 'tokens_saved';

-- Tokens saved in the current session
SELECT value FROM session_stats WHERE session_id = ? AND key = 'tokens_saved';
```

## Cleanup

When a file is deleted from disk, remove all its versions and read pointers:

```sql theme={null}
DELETE FROM file_versions WHERE path = ?;
DELETE FROM session_reads WHERE path = ?;
```

To clear the entire cache:

```sql theme={null}
DELETE FROM file_versions;
DELETE FROM session_reads;
DELETE FROM session_stats;
UPDATE stats SET value = 0;
```

## Key design points

* **Content-addressed storage** means branch switches are handled naturally. If you switch from `main` to a feature branch and back, the original file versions are still in the cache.
* **Session isolation** ensures that each consumer independently tracks what it has seen. Session B gets full file content even if Session A already cached it.
* **Hash-based change detection** at read time means there is no polling or file watching required for correctness.
* An embedded Turso database keeps everything in a single file with zero network overhead.

## Example

[cachebro](https://github.com/glommer/cachebro) is an MCP server that implements this pattern as a drop-in file reading cache for AI coding agents.


# Secrets Vault
Source: https://docs.turso.tech/guides/secrets-vault



AI coding agents regularly need real credentials — an API token to call GitHub as you, a key to hit your billing provider, a token to deploy. The path of least resistance is to paste the secret into the chat, but then the value lives in the model's context, the transcript, and your provider's logs forever. A secrets vault backed by an encrypted Turso database lets an agent *use* a secret without ever seeing its value: the secret is injected into a child process, the command runs, and the output is scrubbed before the agent sees it.

This guide covers the schema, the encrypted connection, and the queries for a local secrets vault that an agent can use but never read.

## Schema

The vault uses two tables: one for the secrets themselves, one for an append-only audit log.

```sql theme={null}
CREATE TABLE IF NOT EXISTS secrets (
  name         TEXT PRIMARY KEY,
  value        TEXT NOT NULL,
  provider     TEXT,
  account      TEXT,
  environment  TEXT,
  access       TEXT,
  tags         TEXT NOT NULL DEFAULT '',
  description  TEXT,
  created_at   TEXT NOT NULL,
  last_used_at TEXT
);

CREATE TABLE IF NOT EXISTS audit_log (
  id         INTEGER PRIMARY KEY AUTOINCREMENT,
  ts         TEXT NOT NULL,
  secrets    TEXT NOT NULL,
  command    TEXT NOT NULL,
  cwd        TEXT NOT NULL,
  exit_code  INTEGER,
  outcome    TEXT NOT NULL
);
```

### How it fits together

* **`secrets`** stores each credential keyed by `name`. The `value` column holds the secret itself and is only ever read to inject into a child process, never returned to the agent. The metadata columns (`provider`, `account`, `environment`, `access`, `tags`) describe a secret without revealing it, so an agent can reason about "the production database token" by its attributes.
* **`audit_log`** is an append-only record of every use: which secrets were injected, the command, the working directory, the exit code, and the outcome (`ran` or `denied`).

## Connecting

The vault is a single encrypted database file. Turso encrypts every page at rest when you pass a key at connection time, and `multiprocess_wal` lets several processes (a couple of agent sessions plus a CLI) open the same file safely:

```javascript theme={null}
import { connect } from "@tursodatabase/database";

const db = await connect("vault.db", {
  encryption: { cipher: "aes256gcm", hexkey },
  experimental: ["multiprocess_wal"],
});
await db.exec(SCHEMA); // The CREATE TABLE statements above
```

<Info>
  `hexkey` is a 64-character hex string. Where it comes from is up to you. You can derive it from a passphrase with scrypt (and never store it), unwrap it from the OS keychain, or fetch it from a cloud KMS. Turso only needs the key at `connect()` time; the storage layer is identical regardless of the source.
</Info>

## Storing a secret

Upsert by `name` so rotating a value keeps the existing metadata:

```sql theme={null}
INSERT INTO secrets (name, value, provider, account, environment, access, tags, description, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(name) DO UPDATE SET
  value       = excluded.value,
  provider    = COALESCE(excluded.provider, secrets.provider),
  account     = COALESCE(excluded.account, secrets.account),
  environment = COALESCE(excluded.environment, secrets.environment),
  access      = COALESCE(excluded.access, secrets.access),
  description = COALESCE(excluded.description, secrets.description);
```

## Listing without exposing values

The query an agent is allowed to run simply never selects the `value` column:

```sql theme={null}
SELECT name, provider, account, environment, access, tags, description, created_at, last_used_at
FROM secrets ORDER BY name;
```

This returns secret names and attributes — enough for an agent to pick the right credential — while the value never leaves the database.

## Using a secret

To actually use a secret, read its value, inject it into a child process as an environment variable, run the command, and scrub the value out of the output before returning it:

```sql theme={null}
SELECT value FROM secrets WHERE name = ?;
```

The injection and scrubbing happen in application code, not the database:

```javascript theme={null}
const { value } = await db.prepare(
  "SELECT value FROM secrets WHERE name = ?"
).get([name]);

const result = await runCommand(command, {
  env: { ...process.env, [name]: value },
});

// Replace every occurrence of the value before the agent sees the output
const safe = result.stdout.replaceAll(value, "***");
```

<Warning>
  Scrubbing captured output is not a sandbox. A command that can read a secret can always leak it on purpose (write it to a file, send it over the network). What the vault guarantees is narrower and still useful: the *model* never receives the value, and a careless `echo $TOKEN` gets redacted. The secret only ever lives in the encrypted file and the short-lived process that used it.
</Warning>

## Auditing every use

Record each access as a row, and update the secret's `last_used_at`:

```sql theme={null}
INSERT INTO audit_log (ts, secrets, command, cwd, exit_code, outcome)
VALUES (?, ?, ?, ?, ?, ?);

UPDATE secrets SET last_used_at = ? WHERE name = ?;
```

"Who used the production key, when, and for what" is then an ordinary query:

```sql theme={null}
SELECT ts, secrets, command, cwd, exit_code, outcome
FROM audit_log ORDER BY id DESC LIMIT ?;
```

## Key design points

* **Encrypted at rest, for free.** Turso handles whole-database encryption (AES-256-GCM, or one of the faster AEGIS ciphers). The vault never touches a cipher or manages page encryption — it derives a key and hands it to `connect()`.
* **No coordinating daemon.** `multiprocess_wal` lets each agent session and CLI invocation open the same encrypted file directly, so the database is the shared state instead of a long-running server you have to keep alive.
* **The value never appears in a list.** Listing is a `SELECT` that omits the `value` column; the agent sees names and attributes only.
* **Fail closed.** An unknown secret name aborts before the command runs, rather than executing with a missing credential.
* **Auditing is just a table.** Every use is an `INSERT`; reporting on it is a `SELECT` you can export or alert on.

## Example

[keymaxxer](https://github.com/glommer/keymaxxer) is an MCP server that implements this pattern as an encrypted secrets vault for AI coding agents.


# Vector Search
Source: https://docs.turso.tech/guides/vector-search

Build semantic search, recommendation systems, and RAG workflows with native vector search in Turso.

Turso supports vector search as a native feature — no extensions required. Store vector embeddings alongside your relational data and query them using built-in distance functions for similarity search.

## Vector Types

Turso supports **dense**, **sparse**, **quantized**, and **binary** vector representations, each suited to different workloads.

### Dense Vectors

Dense vectors store a value for every dimension. Turso provides two precision levels:

| Function   | Precision    | Storage per dimension | Best for                                           |
| ---------- | ------------ | --------------------- | -------------------------------------------------- |
| `vector32` | 32-bit float | 4 bytes               | Most ML embeddings (OpenAI, sentence transformers) |
| `vector64` | 64-bit float | 8 bytes               | Applications requiring higher precision            |

### Sparse Vectors

Sparse vectors only store non-zero values and their indices, making them memory-efficient for high-dimensional data with many zero values.

| Function          | Storage                   | Best for                                           |
| ----------------- | ------------------------- | -------------------------------------------------- |
| `vector32_sparse` | Non-zero values + indices | TF-IDF, bag-of-words, high-dimensional sparse data |

### Quantized Vectors

| Function  | Storage per dimension      | Best for                                                                             |
| --------- | -------------------------- | ------------------------------------------------------------------------------------ |
| `vector8` | 1 byte (+8 bytes overhead) | Large-scale search where \~4x compression vs Float32 is worth minimal precision loss |

Values are linearly quantized to the 0-255 range using min/max scaling. Dequantization: `f_i = alpha * q_i + shift`.

### Binary Vectors

| Function     | Storage per dimension | Best for                                                                           |
| ------------ | --------------------- | ---------------------------------------------------------------------------------- |
| `vector1bit` | 1 bit                 | Binary hashing, approximate nearest neighbor search (\~32x compression vs Float32) |

Positive values become 1, non-positive values become 0. Extracted values are displayed as +1/-1.

<Note>
  For most applications, `vector32` is a good starting point. Explore more compact types if your table has a large number of rows.
</Note>

## Storing Vectors

Create a table with a BLOB column to store embeddings alongside your relational data:

```sql theme={null}
CREATE TABLE documents (
    id INTEGER PRIMARY KEY,
    title TEXT,
    content TEXT,
    embedding BLOB
);
```

Insert rows with vector embeddings using the appropriate conversion function:

```sql theme={null}
INSERT INTO documents (title, content, embedding) VALUES
    ('Machine learning basics', 'An introduction to ML concepts...', vector32('[0.2, 0.5, 0.1, 0.8]')),
    ('Database fundamentals', 'How relational databases work...', vector32('[0.1, 0.3, 0.9, 0.2]')),
    ('Neural networks guide', 'Deep learning architectures...', vector32('[0.3, 0.6, 0.2, 0.7]'));
```

For sparse vectors, zero values are automatically compressed:

```sql theme={null}
INSERT INTO documents (title, content, embedding) VALUES
    ('Sparse example', 'A document with sparse features...', vector32_sparse('[0.0, 1.5, 0.0, 2.3, 0.0]'));
```

## Similarity Search

Use distance functions to find the most similar vectors. All distance functions require both vectors to have the **same type and dimensionality**. Lower values indicate greater similarity.

### Cosine Distance

Measures the angle between vectors, ignoring magnitude. Returns a value between 0 (identical direction) and 2 (opposite direction).

```sql theme={null}
SELECT title,
       vector_distance_cos(embedding, vector32('[0.25, 0.55, 0.15, 0.75]')) AS distance
FROM documents
ORDER BY distance
LIMIT 5;
```

Best for text embeddings and document similarity where direction matters more than magnitude.

### Euclidean (L2) Distance

Measures straight-line distance in n-dimensional space. Not supported for `vector1bit` vectors.

```sql theme={null}
SELECT title,
       vector_distance_l2(embedding, vector32('[0.25, 0.55, 0.15, 0.75]')) AS distance
FROM documents
ORDER BY distance
LIMIT 5;
```

Best for image embeddings, spatial data, and unnormalized embeddings where absolute differences matter.

### Dot Product Distance

Computes the negative dot product: `-sum(v1[i] * v2[i])`. Lower (more negative) values indicate higher similarity.

```sql theme={null}
SELECT title,
       vector_distance_dot(embedding, vector32('[0.25, 0.55, 0.15, 0.75]')) AS distance
FROM documents
ORDER BY distance
LIMIT 5;
```

Best for normalized embeddings (equivalent to cosine distance when vectors are unit-length) and maximum inner product search (MIPS).

### Jaccard Distance

Computes weighted Jaccard distance based on the ratio of minimum to maximum values across dimensions. For `vector1bit` vectors, computes binary Jaccard distance.

```sql theme={null}
SELECT title,
       vector_distance_jaccard(embedding, vector32_sparse('[0.0, 1.0, 0.0, 2.0]')) AS distance
FROM documents
ORDER BY distance
LIMIT 5;
```

Best for sparse vectors, set-like comparisons, TF-IDF representations, and binary similarity with `vector1bit`.

## Utility Functions

### vector\_extract

Convert a vector BLOB back to a readable JSON representation:

```sql theme={null}
SELECT vector_extract(embedding) FROM documents WHERE id = 1;
-- [0.200000,0.500000,0.100000,0.800000]
```

### vector\_concat

Concatenate two vectors into one:

```sql theme={null}
SELECT vector_extract(
    vector_concat(vector32('[1.0, 2.0]'), vector32('[3.0, 4.0]'))
);
-- [1.000000,2.000000,3.000000,4.000000]
```

### vector\_slice

Extract a contiguous portion of a vector (zero-based, end exclusive):

```sql theme={null}
SELECT vector_extract(
    vector_slice(vector32('[1.0, 2.0, 3.0, 4.0, 5.0]'), 1, 4)
);
-- [2.000000,3.000000,4.000000]
```

## Limitations

* Euclidean distance is **not supported** for `vector1bit` vectors
* Maximum vector dimensionality is 65,536
* `vector1bit` cosine distance returns Hamming distance (number of differing bits) instead of standard cosine distance

## Example: Semantic Search

A complete end-to-end example combining vector storage and similarity search:

```sql theme={null}
-- Create a table with vector embeddings
CREATE TABLE articles (
    id INTEGER PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT,
    embedding BLOB
);

-- Insert articles with precomputed embeddings
INSERT INTO articles (title, content, embedding) VALUES
    ('Introduction to Machine Learning', 'ML is a subset of AI...', vector32('[0.12, -0.34, 0.56, 0.78]')),
    ('Database Design Patterns', 'Relational databases organize...', vector32('[0.82, 0.15, -0.44, 0.23]')),
    ('Neural Networks Explained', 'Neural networks are computing...', vector32('[0.14, -0.28, 0.62, 0.71]')),
    ('SQL Query Optimization', 'Efficient queries start with...', vector32('[0.75, 0.22, -0.38, 0.19]'));

-- Find the 3 most similar articles to a query vector
SELECT
    title,
    content,
    vector_distance_cos(embedding, vector32('[0.10, -0.30, 0.50, 0.80]')) AS distance
FROM articles
ORDER BY distance
LIMIT 3;
```

<Note>
  Similarity searches use a linear scan over the table. For large datasets, consider limiting the search to a subset of rows with a `WHERE` clause.
</Note>

## See Also

* [Vector Functions](/sql-reference/functions/vector) — full SQL reference for all vector functions
* [Data Types](/sql-reference/data-types) — how BLOBs are handled in Turso


# Usage & Billing
Source: https://docs.turso.tech/help/usage-and-billing



For the Starter and Scaler plans, Turso usage is limited monthly based on the following usage observations during each calendar month:

* Number of table [rows read](#rows-read)
* Number of table [rows written](#rows-written)
* Amount of [total storage](#total-storage)

<Info>
  In billing plans that include monthly quotas for row reads, row writes, and total storage, any query that exceeds these limits will result in a failure, indicated by the `BLOCKED` error code.
</Info>

## Rows Read

In SQLite, the term "row read" actually refers to a "row scan" during statement execution. Key points to remember with the Turso CLI metrics:

* **SQL Queries:** May scan more rows than returned.
* **SQL Updates:** Each updated row results in at least one row scan.

### Aggregate Function Impact

Using functions like `count`, `avg`, `min`, `max`, and `sum` results in a row scan for every row considered in the aggregation.

<Info>
  Improve query efficiency by storing aggregate values in a separate table and
  updating it transactionally with the base table changes.
</Info>

### Full Table Scans

Queries lacking index support perform a full table scan, incurring a row scan for each table row.

<Info>
  Seek strategies to minimize costly table scans.
</Info>

### Complex Query Costs

Table joins, subqueries, and compound queries incur a row scan for each considered row from all involved tables.

### Update Mechanics

SQL updates read (and write) each row they modify. Absent an index for row filtering, a full table scan is performed, adding a read for each table row, plus a write for each updated row.

### `ALTER TABLE` and Row Reads

`ALTER TABLE` operations, especially those rewriting row contents, necessitate a full table scan, incurring a read for each table row. However, not all `ALTER TABLE` actions, like `DROP COLUMN`, lead to full scans. Be mindful of potential row writes as well.

### Indexing Costs

Adding an index to an existing table triggers a full table scan, with one read per existing row.

### SQLite System Tables

Internal SQLite tables, like `dbstat` and those with `sqlite_` prefix, don't incur row reads in queries.

### Zero-Read Commands

Commands not involving row reads/writes, like `select 1`, default to one row read.

## Rows Written

In SQLite, a "row written" encompasses both the insertion of a new row and the update of an existing row.

### `ALTER TABLE` and Row Writes

`ALTER TABLE` operations can result in a row write for each existing row, especially if the row data is altered during the process. It's important to understand how different types of `ALTER TABLE` statements impact row writes.

### Implications of Aborted Transactions

Even if a transaction is not committed, any row that was inserted or updated during the transaction incurs a row write. This underscores the importance of transaction management in controlling database writes.

<Note>
  `ALTER TABLE` actions may also lead to row reads, adding another layer to consider when modifying table structures.
</Note>

## Total Storage

SQLite utilizes the virtual table [`dbstat`](https://www.sqlite.org/dbstat.html) to calculate the total space used by all tables and indexes. The base unit for this measurement is a database file page, which is 4KB.

<Note>
  In SQLite, the `VACUUM` command is a common tool for optimizing storage by compacting the database. However, it's important to note that this command is currently disabled in Turso. Future updates may introduce options for developers to efficiently manage and reduce the total storage footprint of their databases.
</Note>

## Reducing Usage

### Query Execution

Familiarizing yourself with the [SQLite query planner](https://www.sqlite.org/queryplanner.html) can significantly enhance your understanding of how your queries are executed. This knowledge is pivotal in optimizing query efficiency.

### Query Planning

Utilize the [`EXPLAIN QUERY PLAN`](https://www.sqlite.org/eqp.html) statement to gain insights into your query's execution plan. This tool is invaluable for identifying whether your query is performing a full table scan and if it's leveraging the most efficient index to reduce unnecessary reads.

### Indexing

Ensure that your queries are designed to take advantage of indexes for row filtering. The absence of suitable indexes forces SQLite to resort to full table scans, incrementally increasing the read count by one for each row in the table. Efficient indexing is key to minimizing this overhead.

Incorporating necessary indexes at the table creation stage is a best practice. Adding indexes to tables that already contain rows triggers a full table scan, with each existing row necessitating one read. Proactive index management is crucial for maintaining optimal database performance.


# MCP (AI agents)
Source: https://docs.turso.tech/integrations/mcp

Connect AI agents to Turso Cloud over the Model Context Protocol — manage databases and run SQL with OAuth, no API token to copy.

Turso runs a hosted [Model Context Protocol](https://modelcontextprotocol.io) (MCP)
server at **`https://mcp.turso.ai/mcp`**. Connect any MCP-capable AI agent and it
can manage your Turso Cloud account — organizations, databases, groups — and run
SQL, on your behalf.

Authentication is **OAuth 2.1** (the same model you use to log in to the
dashboard): you approve access in the browser and the agent receives a scoped
token. **There's no API token to copy or paste.**

## What the agent can do

Once connected, the agent has tools to:

* **Databases** — list, inspect, create, delete, and branch databases (including
  point-in-time branches), and update configuration (delete-protection, IP/VPC
  allow rules, size limits).
* **SQL** — run read-only queries, writes, deletes, and schema changes, each as a
  separate tool so the agent uses the least-powerful one for the task.
* **Insight** — per-database analytics (top queries by frequency and latency).

Read-only and destructive operations are labeled, so the agent can tell them
apart and confirm before doing anything that changes data.

## Connect

<Tabs>
  <Tab title="Claude Code">
    Install the plugin, then authenticate:

    ```bash theme={null}
    /plugin marketplace add tursodatabase/turso-mcp
    /plugin install turso@turso
    ```

    Run `/mcp`, select **turso**, and choose **Authenticate**. Your browser opens
    to approve access (see [Authorize](#authorize-access) below).
  </Tab>

  <Tab title="Codex">
    Install the plugin (it bundles the Turso skill too), then authenticate:

    ```bash theme={null}
    codex plugin marketplace add tursodatabase/turso-mcp
    codex plugin add turso@turso
    codex mcp login turso
    ```

    A browser opens to approve access (see [Authorize](#authorize-access) below).
    MCP-only alternative: add `[mcp_servers.turso]` with
    `url = "https://mcp.turso.ai/mcp"` to `~/.codex/config.toml`, then
    `codex mcp login turso`.
  </Tab>

  <Tab title="Claude (web & desktop)">
    Add a custom connector pointing at the Turso MCP server:

    1. Open **Settings → Connectors → Add custom connector**.
    2. Set the URL to `https://mcp.turso.ai/mcp` and connect.
    3. Approve access in the browser (see [Authorize](#authorize-access) below).
  </Tab>

  <Tab title="Other MCP clients">
    Point any MCP client that supports remote (Streamable HTTP) servers with OAuth
    at:

    ```
    https://mcp.turso.ai/mcp
    ```

    The client discovers the authorization server automatically (RFC 9728 / RFC
    8414\) and walks you through the same browser approval.
  </Tab>
</Tabs>

## Authorize access

When the browser opens, you'll log in to Turso (if you aren't already) and land
on a consent screen where you choose **what the agent can access**:

* the **organization**;
* optionally a single **group** to limit the token to — or the whole
  organization for full access;
* for a group, the **permissions** (read-only, full-access, or a custom set).

Approve, and the agent is connected. To re-scope or switch organizations,
disconnect and authenticate again (in Claude Code: `/mcp` → **turso** → **Clear
authentication**).

<Note>
  Picking a **group** scopes the token to that group's resources — the
  least-privilege choice. "Entire organization" grants full access. Either way the
  token is enforced at Turso's API: every tool call is checked for org-binding,
  role, and scope, and recorded in your audit log.
</Note>

## How it works

* The MCP server holds no privilege of its own. Each tool forwards your request
  to the regular Turso platform API carrying your token, so the same
  authorization and audit rules apply as any other API access.
* The token is **organization- or group-scoped**; unscoped tokens are rejected.
* Consent happens on the Turso dashboard — the only place that can see your login
  session — which mints the scoped token the agent then uses.

## Resources

* Plugin source and per-agent configs: [github.com/tursodatabase/turso-mcp](https://github.com/tursodatabase/turso-mcp)


# Vercel
Source: https://docs.turso.tech/integrations/vercel

Use Turso with Vercel serverless and edge functions.

Turso offers two approaches for database access from Vercel serverless and edge functions:

* **SQL over HTTP** — Every query is sent over the network to the database. Simple to set up and works in any runtime, including edge functions, without filesystem access. This is the model you already know from every other database.

* **In-Function SQLite** — Database pages are replicated into the serverless function. Reads execute locally with no network round-trips; writes go directly to the remote database for durability. If you've used Turso's embedded replicas or Cloudflare's Durable Objects with SQLite, this model will feel familiar. See [Bringing SQLite to Vercel Functions](https://turso.tech/blog/serverless) for a deep dive.

|                          | SQL over HTTP                                           | In-Function SQLite                                        |
| ------------------------ | ------------------------------------------------------- | --------------------------------------------------------- |
| **Packages**             | `@tursodatabase/serverless`, `@libsql/client`           | `@tursodatabase/vercel-experimental`                      |
| **Best for**             | Write-heavy workloads, edge runtimes without filesystem | Read-heavy workloads, per-tenant databases                |
| **Read latency**         | Network round-trip per query                            | In-process SQLite read (zero network hops)                |
| **Write latency**        | Network round-trip                                      | Network round-trip (same)                                 |
| **Multi-tenant**         | Shared database                                         | Database-per-tenant with automatic on-demand provisioning |
| **Runtime requirements** | `fetch` API only                                        | Ephemeral filesystem (`/tmp`)                             |
| **ORM support**          | Drizzle, Prisma, and others (`@libsql/client`)          | Not yet supported                                         |

## SQL over HTTP

Connect to Turso databases from Vercel serverless and edge environments. Configure your Vercel environment with the following variables:

* `TURSO_DATABASE_URL` — Your Turso database URL
* `TURSO_AUTH_TOKEN` — Your Turso auth token

|                       | `@tursodatabase/serverless`                  | `@libsql/client`                   |
| --------------------- | -------------------------------------------- | ---------------------------------- |
| **Use with**          | [Turso](/tursodb/quickstart) databases       | [libSQL](/libsql) databases        |
| **Dependencies**      | Uses only `fetch` — zero native dependencies | Requires Node.js or `/web` subpath |
| **Concurrent writes** | Yes — built into the Turso engine            | Not supported                      |
| **ORM support**       | Not yet supported                            | Drizzle, Prisma, and others        |

Match the driver to your database engine: **`@tursodatabase/serverless`** for [Turso](/tursodb/quickstart) databases, **`@libsql/client`** for [libSQL](/libsql) databases and ORM integration (Drizzle, Prisma).

### Quickstart

<Tabs>
  <Tab title="@tursodatabase/serverless">
    The `@tursodatabase/serverless` package connects to Turso using only the `fetch` API — no native dependencies, making it the smallest option for edge and serverless runtimes.

    ```bash theme={null}
    npm install @tursodatabase/serverless
    ```

    ```typescript theme={null}
    import { connect } from "@tursodatabase/serverless";

    const conn = connect({
      url: process.env.TURSO_DATABASE_URL,
      authToken: process.env.TURSO_AUTH_TOKEN,
    });

    const stmt = await conn.prepare("SELECT * FROM users WHERE id = ?");
    const row = await stmt.get([123]);
    ```

    For compatibility with the libSQL client API, use the compat module:

    ```typescript theme={null}
    import { createClient } from "@tursodatabase/serverless/compat";

    const client = createClient({
      url: process.env.TURSO_DATABASE_URL,
      authToken: process.env.TURSO_AUTH_TOKEN,
    });

    const result = await client.execute("SELECT * FROM users WHERE id = ?", [123]);
    ```
  </Tab>

  <Tab title="@libsql/client">
    The `@libsql/client` package is the production-ready libSQL driver used across the Turso ecosystem. Use the `/web` import for Vercel serverless and edge runtimes:

    ```bash theme={null}
    npm install @libsql/client
    ```

    ```typescript theme={null}
    import { createClient } from "@libsql/client/web";

    const client = createClient({
      url: process.env.TURSO_DATABASE_URL!,
      authToken: process.env.TURSO_AUTH_TOKEN!,
    });

    const result = await client.execute({
      sql: "SELECT * FROM users WHERE id = ?",
      args: [123],
    });
    ```

    <Note>
      Use `@libsql/client/web` instead of `@libsql/client` for Vercel deployments. The `/web` import uses the HTTP protocol, which is compatible with edge and serverless runtimes.
    </Note>
  </Tab>
</Tabs>

## In-Function SQLite

<Warning>
  The `@tursodatabase/vercel-experimental` package is in beta. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.
</Warning>

The `@tursodatabase/vercel-experimental` package replicates SQLite database pages into the serverless function itself. Reads execute against the local copy with no network round-trips. Writes go directly to the remote database for durability and consistency. This approach is ideal for read-heavy workloads where per-query network latency is the dominant cost.

* **Per-tenant isolation and sharding** — Each agent, workspace, or session can have its own database with independent storage limits
* **Colocation of compute and data** — Business logic runs next to the database, enabling zero-hop reads and many queries per request
* **Automatic on-demand provisioning** — Databases are created implicitly when `createDb()` is called, no redeploy or binding management needed

If you have used Turso's embedded replicas or Cloudflare’s Durable Objects with SQLite, this model will feel familiar.

### Quickstart

Install the package:

```bash theme={null}
npm install @tursodatabase/vercel-experimental
```

Get your Turso API token and organization slug:

```bash theme={null}
turso auth api-tokens mint my-vercel-token
turso org list
```

Add environment variables to your Vercel project:

```
TURSO_API_TOKEN=your-api-token
TURSO_ORG=your-org-slug
TURSO_GROUP=your-group-name
TURSO_DATABASE=your-database-name
```

<Note>
  The `TURSO_GROUP` environment variable scopes all database access to a single group. Even though the API token grants access to the entire organization, an attacker who controls the database name can only access databases within the configured group. The database name is still recommended to be stored as a secret environment variable when possible.
</Note>

Use `createDb()` to open a database and start querying:

```ts theme={null}
import { createDb } from "@tursodatabase/vercel-experimental";

const db = await createDb(process.env.TURSO_DATABASE!);
try {
  // Create tables
  await db.execute(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE
    )
  `);

  // Insert data
  await db.execute(
    "INSERT INTO users (name, email) VALUES (?, ?)",
    ["Alice", "alice@example.com"]
  );

  // Query data
  const result = await db.query("SELECT * FROM users");
  console.log(result.rows);
} finally {
  await db.close(); // Syncs and closes the connection
}
```

### Per-tenant databases

The auto-provisioning makes it straightforward to implement a database-per-tenant pattern. Instead of routing all tenants through a single shared database, each user or session gets its own isolated SQLite database. Simply derive the database name from a tenant identifier:

```ts theme={null}
// Per-user database
const db = await createDb(`user-${userId}`);

// Per-agent-session database
const db = await createDb(`session-${sessionId}`);
```

Because the driver creates databases on demand, there is no provisioning step, no migration to run, and no connection routing to configure. Each tenant's data is fully isolated at the SQLite level, and every database independently benefits from partial-sync reads.

### API Reference

**`createDb(name, options?)`**

Creates or retrieves a database instance. If the database does not exist, it is created automatically within the configured group (`TURSO_GROUP`). Calling `createDb()` with the same name within a single function invocation returns the existing instance.

```ts theme={null}
const db = await createDb(process.env.TURSO_DATABASE!, {
  remoteWrites: false,     // Batch writes locally and push on close() (optional, default: true)
});
```

When `remoteWrites` is `true` (the default), write statements (`INSERT`, `UPDATE`, `DELETE`) are sent directly to the remote Turso database for immediate durability. When set to `false`, writes are applied to the local SQLite copy and pushed in a single batch when `db.push()` or `db.close()` is called. This can be useful for bulk ingestion or background tasks where eventual visibility is acceptable.

**`db.query(sql, params?)`**

Execute a SELECT query and return results.

```ts theme={null}
const result = await db.query("SELECT * FROM users WHERE id = ?", [1]);

console.log(result.columns); // ["id", "name", "email"]
console.log(result.rows);    // [[1, "Alice", "alice@example.com"]]
```

**`db.execute(sql, params?)`**

Execute an INSERT, UPDATE, DELETE, or DDL statement. By default, writes are sent directly to the remote Turso database. If `remoteWrites: false` is set, writes are applied locally and pushed on `close()`.

```ts theme={null}
await db.execute("UPDATE users SET name = ? WHERE id = ?", ["Bob", 1]);
```

**`db.close()`**

Close the connection. If `remoteWrites: false` is set, this also pushes any pending local writes to the remote Turso database. Always call this in a `finally` block. As a safety net, the package registers a callback with Vercel's [`waitUntil()`](https://vercel.com/docs/functions/functions-api-reference#waituntil) API to push pending changes if `close()` is not called within 5 seconds of function completion.

```ts theme={null}
const db = await createDb(process.env.TURSO_DATABASE!);
try {
  await db.execute("INSERT INTO users (name) VALUES (?)", ["Charlie"]);
} finally {
  await db.close();
}
```

**`db.push()`**

Manually push local changes to the remote Turso database without closing the connection. Only applicable when using `remoteWrites: false`.

```ts theme={null}
await db.execute("INSERT INTO users (name) VALUES (?)", ["Charlie"]);
await db.push(); // Sync without closing
```

**`db.pull()`**

Pull latest changes from the remote Turso database.

```ts theme={null}
await db.pull();
```


# Welcome to Turso
Source: https://docs.turso.tech/introduction

The small database to power your big dreams in the age of AI.

<Frame>
  <img alt="Turso Quickstart" />
</Frame>

Turso is the small database to power your big dreams in the age of AI. The most efficient way to build for traditional applications, smart devices, embodied AI, agents, and anything in between.

## Get Started

Choose your path to get started with Turso:

<CardGroup>
  <Card title="Turso Database (Embedded)" icon="microchip" href="/tursodb/quickstart">
    An embedded database engine that goes anywhere. Offline, in the browser, or
    on-device.
  </Card>

  <Card title="AgentFS" icon="robot" href="/agentfs/introduction">
    Filesystem and state SDK for AI agents with built-in auditability.
  </Card>

  <Card title="Turso Cloud" icon="cloud" href="/turso-cloud">
    Fully managed platform for Turso and libSQL databases, with branching,
    analytics, backups, and more.
  </Card>

  <Card title="How Well You Know Turso?" icon="circle-question" href="https://www.howwellyouknow.com/play/turso">
    Test your knowledge of Turso and share your results.
  </Card>
</CardGroup>

<Info>
  **Need sync?** If you want local reads and writes with push/pull sync to the cloud, start with [Turso Database](/tursodb/quickstart) and [Turso Sync](/sync/usage).
</Info>

## Turso Database Features

The next evolution of SQLite, built for modern applications:

<CardGroup>
  <Card title="Vector Search" icon="magnifying-glass">
    Native similarity search for AI apps and RAG workflows, no extensions
    required
  </Card>

  <Card title="Async Design" icon="bolt">
    Modern async primitives such as Linux io\_uring to keep your apps responsive
  </Card>

  <Card title="SQLite Compatible" icon="check">
    Fully backwards compatible with SQLite, but built for the next generation
  </Card>

  <Card title="Concurrent Writes" icon="arrows-rotate">
    Multiple writers, zero conflicts, without locking
  </Card>

  <Card title="Richer Type System" icon="tags">
    Richer and stricter type system, including User-Defined Types (experimental)
  </Card>

  <Card title="Open Contribution" icon="code-branch">
    Take your seat at the table and contribute to the next evolution of SQLite
  </Card>
</CardGroup>

## Join Our Communities

Connect with developers and the Turso team:

<CardGroup>
  <Card title="Turso Database Core Developers" icon="discord" href="https://discord.gg/jgjmyYgHwB">
    Join the core development community for Turso Database discussions
  </Card>

  <Card title="Turso Community" icon="discord" href="https://tur.so/discord">
    Connect with users and get support for Turso Cloud
  </Card>
</CardGroup>

### Follow Us

<CardGroup>
  <Card title="GitHub" icon="star" href="https://github.com/tursodatabase" />

  <Card title="X (Twitter)" icon="x-twitter" href="https://twitter.com/tursodatabase" />

  <Card title="LinkedIn" icon="linkedin" href="https://www.linkedin.com/company/turso" />
</CardGroup>


# libSQL
Source: https://docs.turso.tech/libsql

libSQL is a production-ready fork of SQLite, maintained by Turso.

libSQL is a fork of SQLite, created because SQLite is [open-source but not open-contribution](https://www.sqlite.org/copyright.html). libSQL is production-ready, fully backwards compatible with SQLite, and adds features like native vector search.

<Note>
  libSQL represents where we started. Today, our focus is [Turso Database](/tursodb/quickstart), a full rewrite of SQLite built from scratch, designed for the highest density of databases with no need for servers or connectivity. If you're starting a new project, we recommend [Turso Database](/tursodb/quickstart). For mission-critical workloads that need a battle-tested foundation today, libSQL is the right choice.
</Note>

<CardGroup>
  <Card title="tursodatabase/libsql" icon="github" href="https://github.com/tursodatabase/libsql/">
    Browse the libSQL source code on GitHub, report issues, feature requests and contribute using pull requests.
  </Card>

  <Card title="Discord" href="https://tur.so/discord" icon="discord">
    Join the community on Discord to talk about the development of libSQL.
  </Card>
</CardGroup>

## libSQL vs. Turso Database

libSQL is a fork of SQLite. It maintains the same file format, the same API, and full backwards compatibility. It extends SQLite with features the ecosystem has long needed but couldn't contribute upstream.

[Turso Database](/tursodb/quickstart) is a different approach: a ground-up rewrite of SQLite. It reimplements SQLite's semantics with a modern architecture designed for concurrent writes, async I/O, and the highest database density in the industry.

|                      | libSQL                           | Turso Database                                              |
| -------------------- | -------------------------------- | ----------------------------------------------------------- |
| Approach             | Fork of SQLite                   | Full rewrite of SQLite                                      |
| Maturity             | Production-ready                 | Production-ready                                            |
| SQLite compatibility | Full (same file format and API)  | Backwards compatible                                        |
| Best for             | Mission-critical workloads today | New projects, agents, smart devices, high-density use cases |

Both are open-contribution and maintained by Turso, and both can be deployed on [Turso Cloud](/turso-cloud): libSQL has powered it for years, and Turso databases are now available on the platform as well.

## Extensions

<Info>
  If you're looking to enable vector extensions, you should instead consider
  using the native [libSQL vector datatype](/features/ai-and-embeddings).
</Info>

A full list of supported extensions can be found [here](/features/sqlite-extensions).


# Local Development
Source: https://docs.turso.tech/local-development

Build locally with a local Turso database, or a local libSQL server.

Developers can build locally with Turso using either of the following methods:

* [Local Turso database](#local-turso-database) — local database file, no server needed (recommended)
* [Turso CLI](#turso-cli) — managed libSQL server

## Using a dump locally

You can always dump your production database and use it locally for development:

<Steps>
  <Step title="Create a dump using the Turso CLI">
    ```bash theme={null}
    turso db shell your-database .dump > dump.sql
    ```
  </Step>

  <Step title="Create SQLite file from dump">
    ```bash theme={null}
    cat dump.sql | sqlite3 local.db
    ```
  </Step>

  <Step title="Connect to the file">
    You can use either of the methods below with the `local.db` file, or you can use a new file name if you prefer to create a database from scratch.
  </Step>
</Steps>

## Local Turso database

Whenever your application runs against a local database, we recommend the [Turso packages](/sdk). They are fully SQLite-compatible — they open existing SQLite files — and run entirely in your process, no server needed:

<CodeGroup>
  ```ts JavaScript theme={null}
  import { connect } from "@tursodatabase/database";

  const db = await connect("local.db");
  ```

  ```rust Rust theme={null}
  use turso::Builder;

  let db = Builder::new_local("local.db").build().await?;
  let conn = db.connect()?;
  ```

  ```go Go theme={null}
  package main

  import (
    "database/sql"

    _ "turso.tech/database/tursogo"
  )

  func main() {
    db, _ := sql.Open("turso", "local.db")
    defer db.Close()
  }
  ```

  ```python Python theme={null}
  import turso

  db = turso.connect("local.db")
  ```
</CodeGroup>

<br />

<Info>
  You don't need to provide an `authToken` in development.
</Info>

<Info>
  It's recommended to use environment variables for both `url` and `authToken` for a seamless developer experience.
</Info>

## Turso CLI

If you're developing against a [libSQL](/libsql) database and use libSQL-specific features like [extensions](/libsql#extensions), you should use the Turso CLI:

```bash theme={null}
turso dev
```

This will start a local libSQL server and create a database for you. You can then connect to it with your libSQL client using the `url` option:

```ts JavaScript theme={null}
import { createClient } from "@libsql/client";

const client = createClient({
  url: "http://127.0.0.1:8080",
});
```

The same URL works with the libSQL clients for [other languages](/sdk).

<br />

<Warning>
  Changes will be lost when you stop the server.
</Warning>

If you want to persist changes, or use a production dump, you can pass the `--db-file` flag with the name of the SQLite file:

```bash theme={null}
turso dev --db-file local.db
```

## Turso Cloud database

If you already have a database created on Turso Cloud, you can use that same one in development by passing the `url` option to your SDK.

<Warning>
  Keep in mind that using the Turso Cloud hosted database will incur platform costs and count towards your quota. Consider one of the local methods above to avoid platform costs.
</Warning>

## Connecting a GUI

During development you can easily connect to a SQLite, libSQL, or Turso database using one of the tools below:

* [Beekeeper Studio](https://www.beekeeperstudio.io/db/libsql-client/) — macOS, Linux, and Windows
* [Outerbase](https://www.outerbase.com) — Runs in the browser
* [TablePlus](https://tableplus.com) — macOS, Windows, and Linux
* [Dataflare](https://dataflare.app) — Paid (with limited free version) macOS, Windows, and Linux
* [Outerbase Studio](https://libsqlstudio.com) - Runs in the browser
* [DBeaver](https://dbeaver.io) - macOS, Windows, and Linux


# Quickstart
Source: https://docs.turso.tech/quickstart

Welcome to Turso Cloud! Get started in minutes.

**In this quickstart you will learn how to:**

1. Install the Turso CLI
2. Signup to Turso Cloud
3. Create your first Turso Cloud database
4. Connect to Database Shell

<Steps>
  <Step title="Install the Turso CLI">
    We'll be using the Turso CLI throughout this quickstart to manage databases and connect to the database shell.

    <CodeGroup>
      ```bash macOS theme={null}
      brew install tursodatabase/tap/turso
      ```

      ```bash Linux theme={null}
      curl -sSfL https://get.tur.so/install.sh | bash
      ```

      ```bash Windows (WSL) theme={null}
      curl -sSfL https://get.tur.so/install.sh | bash
      ```
    </CodeGroup>
  </Step>

  <Step title="Signup to Turso Cloud">
    The next command will open your browser to sign up:

    <CodeGroup>
      ```bash Signup theme={null}
      turso auth signup
      ```

      ```bash Login theme={null}
      turso auth login
      ```
    </CodeGroup>
  </Step>

  <Step title="Create a Database">
    Turso Cloud hosts two SQLite-compatible engines: [Turso](/tursodb/quickstart), with concurrent writes and multi-user access built in, and [libSQL](/libsql). Create your first Turso database with the name `my-db`:

    ```bash theme={null}
    turso db create my-db --tursodb
    ```

    To create a libSQL database instead, omit the `--tursodb` flag:

    ```bash theme={null}
    turso db create my-db
    ```

    You can inspect your new database using the following command:

    ```bash theme={null}
    turso db show my-db
    ```
  </Step>

  <Step title="Connect to Database Shell">
    Congratulations, you created a database! Now connect to it with the `shell` command:

    ```bash theme={null}
    turso db shell my-db
    ```

    <Info>
      Replace `my-db` with the name of your database if you named it differently.
    </Info>

    <AccordionGroup>
      <Accordion title="Create table">
        Now create a table for `users` using SQL:

        ```sql theme={null}
        CREATE TABLE users (
          ID INTEGER PRIMARY KEY AUTOINCREMENT,
          name TEXT
        );
        ```
      </Accordion>

      <Accordion title="Insert into new table">
        Then insert a row into the `users` table:

        ```sql theme={null}
        INSERT INTO users (name) VALUES ('Iku');
        ```
      </Accordion>

      <Accordion title="Select all rows from table">
        Finally, query for all `users`:

        ```sql theme={null}
        SELECT * FROM users;
        ```
      </Accordion>
    </AccordionGroup>

    When you're ready to move onto the next step, you'll want to to quit the shell:

    ```sql theme={null}
    .quit
    ```
  </Step>

  <Step title="Connect your application to your database">
    You're now ready to connect your application to your database. Pick from one of the SDKs below to continue:

    <Snippet />
  </Step>
</Steps>


# Ruby on Rails + Turso
Source: https://docs.turso.tech/sdk/activerecord/guides/rails

Set up Turso in your Ruby on Rails project in minutes.

<Snippet />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a Rails app — [learn more](https://guides.rubyonrails.org/getting_started.html)

<Steps>
  <Step title="Install the libsql_activerecord Rubygem">
    Add the following to your Gemfile:

    ```ruby theme={null}
    gem 'libsql_activerecord'
    ```

    Then run:

    ```bash theme={null}
    bundle install
    ```
  </Step>

  <Step title="Configure database credentials">
    <Snippet />

    Add your Turso credentials to your Rails credentials file or environment variables.
  </Step>

  <Step title="Use the libsql adapter">
    ```yml theme={null}
    default: &default
      adapter: libsql
      pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

    development:
      <<: *default
      url: <%= ENV['TURSO_DATABASE_URL'] %>
      auth_token: <%= ENV['TURSO_AUTH_TOKEN'] %>
      path: storage/development.sqlite3 # For embedded replica
    ```
  </Step>

  <Step title="Create and run migrations">
    Generate a migration:

    ```bash theme={null}
    rails generate migration CreateProducts name:string description:text
    ```

    Run the migration:

    ```bash theme={null}
    rails db:migrate
    ```
  </Step>

  <Step title="Define models">
    Create a model in `app/models/product.rb`:

    ```ruby theme={null}
    class Product < ApplicationRecord
      validates :name, presence: true
    end
    ```
  </Step>

  <Step title="Use in controllers">
    In your controllers, you can now use ActiveRecord methods to interact with your Turso database:

    ```ruby theme={null}
    class ProductsController < ApplicationController
      def index
        @products = Product.all
      end

      def create
        @product = Product.new(product_params)
        if @product.save
          redirect_to @product, notice: 'Product was successfully created.'
        else
          render :new
        end
      end

      private

      def product_params
        params.require(:product).permit(:name, :description)
      end
    end
    ```
  </Step>

  <Step title="Execute raw SQL (if needed)">
    You can also execute raw SQL queries:

    ```ruby theme={null}
    results = ActiveRecord::Base.connection.execute("SELECT * FROM products")
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Ruby on Rails App" icon="github" href="https://github.com/tursodatabase/libsql-activerecord/tree/main/examples/railsapp">
    See the full source code
  </Card>
</CardGroup>


# Turso Quickstart (ActiveRecord)
Source: https://docs.turso.tech/sdk/activerecord/quickstart

Get started with Turso and ActiveRecord in a few simple steps.

<Snippet />

In this Ruby quickstart we will learn how to:

* Retrieve database credentials
* Install the libSQL ActiveRecord gem
* Connect to a local or remote Turso database
* Define and create models
* Perform basic CRUD operations
* Execute raw SQL queries
* Work with migrations

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />
  </Step>

  <Step title="Install">
    In your Ruby project, add the following gems to your Gemfile:

    ```ruby theme={null}
    gem 'libsql_activerecord'
    gem 'activerecord'
    ```

    Then run:

    ```bash theme={null}
    bundle install
    ```
  </Step>

  <Step title="Connect">
    Create a Ruby file (e.g., `database.rb`) to set up the database connection:

    <AccordionGroup>
      <Accordion title="Embedded Replicas">
        ```rb theme={null}
        require 'libsql_activerecord'
        require 'active_record'

        ActiveRecord::Base.establish_connection(
          adapter: 'libsql',
          url: ENV['TURSO_DATABASE_URL'],
          auth_token: ENV['TURSO_AUTH_TOKEN'],
          path: 'path/to/local/replica.db'
        )
        ```
      </Accordion>

      <Accordion title="Local only">
        ```rb theme={null}
        require 'libsql_activerecord'
        require 'active_record'

        ActiveRecord::Base.establish_connection(
          adapter: 'libsql',
          path: 'path/to/local.db'
        )
        ```
      </Accordion>

      <Accordion title="Remote only">
        ```rb theme={null}
        require 'libsql_activerecord'
        require 'active_record'

        ActiveRecord::Base.establish_connection(
          adapter: 'libsql',
          url: ENV['TURSO_DATABASE_URL'],
          auth_token: ENV['TURSO_AUTH_TOKEN']
        )
        ```
      </Accordion>

      <Accordion title="In-memory">
        ```rb theme={null}
        require 'libsql_activerecord'
        require 'active_record'

        ActiveRecord::Base.establish_connection(adapter: 'libsql')
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Define models">
    Create model files for your database tables. For example, `product.rb`:

    ```rb theme={null}
    class Product < ActiveRecord::Base
      validates :name, presence: true
    end
    ```
  </Step>

  <Step title="Create and execute a migration">
    Create a migration file (e.g., `001_create_products.rb`):

    ```rb theme={null}
    class CreateProducts < ActiveRecord::Migration[8.0]
      def change
        create_table :products do |t|
          t.string :name
          t.text :description
          t.timestamps
        end
      end
    end
    ```

    Execute the migration:

    ```rb theme={null}
    require_relative 'database'
    require_relative '001_create_products'

    CreateProducts.migrate(:up)
    ```
  </Step>

  <Step title="Execute">
    Perform some basic CRUD operations:

    <CodeGroup>
      ```rb Create theme={null}
      product = Product.create(name: 'Book', description: 'A book about books')
      ```

      ```rb Read theme={null}
      all_products = Product.all
      first_product = Product.first
      found_product = Product.find_by(name: 'Book')
      ```

      ```rb Update theme={null}
      product.update(description: 'An updated description')
      ```

      ```rb Delete theme={null}
      product.destroy
      ```

      ```rb Raw SQL theme={null}
      results = ActiveRecord::Base.connection.execute("SELECT * FROM products WHERE name LIKE '%Book%'")

      results.each do |row|
        puts row.inspect
      end
      ```
    </CodeGroup>
  </Step>

  <Step title="Work with associations">
    Define associations in your models:

    ```ruby theme={null}
    class Author < ActiveRecord::Base
      has_many :books
    end

    class Book < ActiveRecord::Base
      belongs_to :author
    end
    ```

    Use associations in your code:

    ```ruby theme={null}
    author = Author.create(name: 'Jane Doe')
    book = author.books.create(title: 'My First Book')

    puts author.books.count # => 1
    puts book.author.name # => "Jane Doe"
    ```
  </Step>
</Steps>


# Authentication
Source: https://docs.turso.tech/sdk/authentication



SDKs connect using the `turso://` protocol for [Turso](/tursodb/quickstart) databases and the `libsql://` protocol for [libSQL](/libsql) databases, unless using the [HTTP API](/sdk/http).

## Database URL

You can find your database URL using the [Turso CLI](/cli/db/show) or [Platform API](/api-reference/databases/retrieve), it looks something like this:

<CodeGroup>
  ```bash Turso theme={null}
  turso://[DB-NAME]-[ORG-NAME].turso.io
  ```

  ```bash libSQL theme={null}
  libsql://[DB-NAME]-[ORG-NAME].turso.io
  ```

  ```bash HTTPS theme={null}
  https://[DB-NAME]-[ORG-NAME].turso.io
  ```
</CodeGroup>

<br />

<Info>
  WebSockets generally offer better performance when maintaining an open socket for multiple queries, whereas HTTP is typically more efficient for single queries due to reduced client-server round trips — it's advisable to benchmark both to ascertain the best option for your specific needs.
</Info>

## Auth Tokens

SDKs require an auth token, unless [working locally](/local-development). You can create tokens using the [Turso CLI](/cli) or [Platform API](/api-reference), or let your authentication provider issue tokens via [JWKS](/sdk/authorization#jwks-authentication-providers).

See [Authorization](/sdk/authorization) for full details on token creation, fine-grained permissions, and JWKS setup.


# Authorization
Source: https://docs.turso.tech/sdk/authorization



Turso uses scoped, JWT-based tokens to control access to your databases. Every token can be restricted by database, permission level, and expiration — giving you full control over what each client can and cannot do.

## Scoping Levels

Tokens are scoped at multiple levels, from broad to narrow:

| Level              | Scope                           | How to create                       |
| ------------------ | ------------------------------- | ----------------------------------- |
| **Group**          | Access all databases in a group | `turso group tokens create <group>` |
| **Database**       | Access a single database        | `turso db tokens create <database>` |
| **Read-only**      | Queries only, no writes         | Add `--read-only` flag              |
| **Table + Action** | Specific tables and operations  | Add `-p <table>:<actions>` flag     |
| **Time-limited**   | Auto-expires after a duration   | Add `--expiration 7d` flag          |

These can be combined. For example, a read-only token scoped to a single database that expires in 7 days:

```bash theme={null}
turso db tokens create mydb --read-only --expiration 7d
```

Or a token that only allows reading from all tables and inserting into `comments`:

```bash theme={null}
turso db tokens create mydb \
  -p all:data_read \
  -p comments:data_add
```

## Issuing Tokens

There are two ways to issue tokens:

* **[Platform Tokens](/sdk/authorization/tokens)** — Create tokens directly via the Turso CLI or Platform API.
* **[External Auth Providers](/sdk/authorization/jwks)** — Let your authentication provider (e.g., Clerk, Auth0) issue tokens using JWKS.

Both approaches support [fine-grained permissions](/sdk/authorization/fine-grained-permissions) to control access at the table and action level.

## Using Tokens

All tokens are passed as the `authToken` when creating a database client:

```javascript theme={null}
import { connect } from "@tursodatabase/serverless";

const db = connect({
  url: "<your-database-url>",
  authToken: "<your-token>",
});
```

<Note>
  You can get your database URL with `turso db show <database-name> --url`.
</Note>


# Fine-Grained Permissions
Source: https://docs.turso.tech/sdk/authorization/fine-grained-permissions



Fine-grained permissions let you control access at the table and action level. They work with both [CLI-generated tokens](/sdk/authorization/tokens) and [JWKS-issued tokens](/sdk/authorization/jwks).

## Permission Format

Permissions follow the format `<table-name|all>:<action1>,<action2>`:

```bash theme={null}
# Read-only access to all tables
turso db tokens create mydb -p all:data_read

# Specific actions on a specific table
turso db tokens create mydb -p users:data_read,data_update

# Multiple permission rules
turso db tokens create mydb \
  -p all:data_read \
  -p comments:data_add,data_update \
  -p posts:data_add,data_update,data_delete
```

Use `all` as the table name to apply permissions to every table.

## Available Actions

| Action          | Description           |
| --------------- | --------------------- |
| `data_read`     | Read data from tables |
| `data_add`      | Insert new rows       |
| `data_update`   | Update existing rows  |
| `data_delete`   | Delete rows           |
| `schema_add`    | Create new tables     |
| `schema_update` | Modify table schemas  |
| `schema_delete` | Drop tables           |

<Note>
  `data_read` is allowed on SQLite system tables (e.g., `sqlite_master`,
  `sqlite_schema`) by default, allowing users to query database metadata.
</Note>

## Role-Based Access Example

You can configure your JWT template to grant different permissions based on user roles:

| Role      | Permissions                                                                             |
| --------- | --------------------------------------------------------------------------------------- |
| Admin     | `all:data_read,data_add,data_update,data_delete,schema_add,schema_update,schema_delete` |
| Moderator | `all:data_read,data_update`                                                             |
| User      | `all:data_read`                                                                         |

With JWKS, these permissions are embedded in the JWT token based on user metadata (e.g., role, group membership) from your auth provider.


# External Auth Providers
Source: https://docs.turso.tech/sdk/authorization/jwks



Instead of managing tokens manually, you can let your authentication provider issue JWT tokens using JWKS. This allows you to leverage your existing auth infrastructure (e.g., Clerk, Auth0).

<Info>
  During the Turso Beta, we only support Clerk & Auth0 as OIDC providers.
</Info>

<Steps>
  <Step title="Generate JWT Template">
    Use the Turso CLI to generate a JWT claims template for your auth provider:

    ```bash theme={null}
    # Full access to a database
    turso org jwks template --database <database-name> --scope full-access

    # Read-only access to a group
    turso org jwks template --group <group-name> --scope read-only

    # Fine-grained permissions
    turso org jwks template \
      --database <database-name> \
      --permissions all:data_read \
      --permissions comments:data_add
    ```

    Copy the generated template into your auth provider's JWT configuration.
  </Step>

  <Step title="Add JWKS Endpoint to Turso">
    Register your auth provider's JWKS endpoint with your Turso organization:

    ```bash theme={null}
    turso org jwks save <name> <url>
    ```

    For example, with Clerk:

    ```bash theme={null}
    turso org jwks save clerk https://your-app.clerk.accounts.dev/.well-known/jwks.json
    ```

    You can also add JWKS endpoints in the [Turso Dashboard](https://turso.tech/app) under organization settings.
  </Step>

  <Step title="Use Tokens in Your Application">
    Get the JWT from your auth provider and pass it as the `authToken`:

    ```javascript theme={null}
    import { connect } from "@tursodatabase/serverless";

    const authToken = await getAuthToken(); // e.g., from Clerk, Auth0

    const db = connect({
      url: "<your-database-url>",
      authToken,
    });
    ```
  </Step>
</Steps>

## Managing JWKS Endpoints

```bash theme={null}
# List all JWKS endpoints
turso org jwks list

# Remove a JWKS endpoint
turso org jwks remove <name>
```

<Warning>
  If you don't setup a JWT template with specific permissions, the generated
  tokens will have access to **all databases in all groups** by default.
</Warning>


# Platform Tokens
Source: https://docs.turso.tech/sdk/authorization/tokens



Create tokens for a single database or all databases in a group using the CLI:

```bash theme={null}
# Token for a single database
turso db tokens create <database-name>

# Token for all databases in a group
turso group tokens create <group-name>
```

You can control token scope, permissions, and expiration:

| Flag                  | Description                                                                                      |
| --------------------- | ------------------------------------------------------------------------------------------------ |
| `-r`, `--read-only`   | Create a read-only token (queries only, no writes)                                               |
| `-p`, `--permissions` | Set [fine-grained permissions](/sdk/authorization/fine-grained-permissions) per table and action |
| `-e`, `--expiration`  | Set token expiration (`never`, `7d`, `30d`, etc.)                                                |

<CodeGroup>
  ```bash Read-only token theme={null}
  turso db tokens create mydb --read-only
  ```

  ```bash Token with 7-day expiration theme={null}
  turso db tokens create mydb --expiration 7d
  ```

  ```bash Fine-grained permissions theme={null}
  turso db tokens create mydb \
    -p all:data_read \
    -p comments:data_add,data_update
  ```

  ```bash Combined scoping theme={null}
  turso db tokens create mydb \
    --read-only \
    --expiration 7d
  ```
</CodeGroup>

You can also create tokens using the [Platform API](/api-reference/databases/create-token).

## Invalidating Tokens

You can invalidate all existing tokens for a database or group, which rotates the signing keys:

```bash theme={null}
# Invalidate all tokens for a database
turso db tokens invalidate <database-name>

# Invalidate all tokens for a group (and all its databases)
turso group tokens invalidate <group-name>
```


# Turso Examples (C)
Source: https://docs.turso.tech/sdk/c/examples



| Example                                                                                     | Description                                                                             |
| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| [local](https://github.com/tursodatabase/libsql-c/tree/master/examples/local)               | Uses libsql with a local SQLite file. Creates database, inserts data, and queries.      |
| [remote](https://github.com/tursodatabase/libsql-c/tree/master/examples/remote)             | Connects to a remote database. Requires environment variables for URL and auth token.   |
| [sync](https://github.com/tursodatabase/libsql-c/tree/master/examples/sync)                 | Demonstrates synchronization between local and remote databases.                        |
| [batch](https://github.com/tursodatabase/libsql-c/tree/master/examples/batch)               | Executes multiple SQL statements in a single batch operation.                           |
| [transactions](https://github.com/tursodatabase/libsql-c/tree/master/examples/transactions) | Shows transaction usage: starting, performing operations, and committing/rolling back.  |
| [memory](https://github.com/tursodatabase/libsql-c/tree/master/examples/memory)             | Uses an in-memory SQLite database for temporary storage or fast access.                 |
| [vector](https://github.com/tursodatabase/libsql-c/tree/master/examples/vector)             | Works with vector embeddings, storing and querying for similarity search.               |
| [encryption](https://github.com/tursodatabase/libsql-c/tree/master/examples/encryption)     | Creates and uses an encrypted SQLite database, demonstrating setup and data operations. |


# Turso Quickstart (C)
Source: https://docs.turso.tech/sdk/c/quickstart

Get started with Turso and C using the libSQL client in a few simple steps.

<Snippet />

In this C quickstart we will learn how to:

* Retrieve database credentials
* Install the libSQL package
* Connect to a local or remote Turso database
* Execute a query using SQL
* Sync changes to local database (optional)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Snippet />
  </Step>

  <Step title="Install">
    To use libSQL with C, you need to build the library from source:

    <Card title="libSQL C" href="https://github.com/tursodatabase/libsql">
      Build from source code
    </Card>

    After building, make sure to link against the library when compiling your C program:

    ```bash theme={null}
    gcc -o your_program your_program.c -L/path/to/libsql/lib -llibsql
    ```
  </Step>

  <Step title="Connect">
    You must first initialize libSQL, create a `libsql_database_t` object, and then open a `libsql_connection_t` to it:

    <AccordionGroup>
      <Accordion title="Embedded Replicas">
        ```c theme={null}
        #include "libsql.h"

        libsql_setup((libsql_config_t){0});

        libsql_database_t db = libsql_database_init((libsql_database_desc_t){
            .path = "local.db",
            .url = "TURSO_DATABASE_URL",
            .auth_token = "TURSO_AUTH_TOKEN",
            .sync_interval = 300
        });

        libsql_connection_t conn = libsql_database_connect(db);
        ```
      </Accordion>

      <Accordion title="Local only">
        ```c theme={null}
        #include "libsql.h"

        libsql_setup((libsql_config_t){0});

        libsql_database_t db = libsql_database_init((libsql_database_desc_t){
            .path = "local.db"
        });

        libsql_connection_t conn = libsql_database_connect(db);
        ```
      </Accordion>

      <Accordion title="Remote only">
        ```c theme={null}
        #include "libsql.h"

        libsql_setup((libsql_config_t){0});

        libsql_database_t db = libsql_database_init((libsql_database_desc_t){
            .url = "TURSO_DATABASE_URL",
            .auth_token = "TURSO_AUTH_TOKEN"
        });

        libsql_connection_t conn = libsql_database_connect(db);
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Execute">
    You can execute a SQL query against your existing database by preparing a statement and then executing it:

    ```c theme={null}
    libsql_statement_t stmt = libsql_connection_prepare(conn, "SELECT * FROM users");
    libsql_rows_t rows = libsql_statement_query(stmt);
    ```

    If you need to use placeholders for values, you can do that:

    <CodeGroup>
      ```c Positional Query theme={null}
      libsql_statement_t stmt = libsql_connection_prepare(conn, "SELECT * FROM users WHERE id = ?");
      libsql_statement_bind_value(stmt, libsql_integer(1));
      libsql_rows_t rows = libsql_statement_query(stmt);
      ```

      ```c Positional Insert theme={null}
      libsql_statement_t stmt = libsql_connection_prepare(conn, "INSERT INTO users (id) VALUES (?)");
      libsql_statement_bind_value(stmt, libsql_integer(1));
      libsql_execute_t result = libsql_statement_execute(stmt);
      ```
    </CodeGroup>
  </Step>

  <Step title="Sync (Embedded Replicas only)">
    When using embedded replicas, you should call `libsql_database_sync()` on the database to sync your local database with the primary database, unless you are using `sync_interval` (though there is no issue with calling `sync` with `sync_interval` enabled):

    ```c theme={null}
    libsql_sync_t sync = libsql_database_sync(db);
    if (sync.err) {
        fprintf(stderr, "Error syncing database: %s\n", libsql_error_message(sync.err));
    } else {
        printf("Sync completed. Frame number: %llu, Frames synced: %llu\n",
                (unsigned long long)sync.frame_no,
                (unsigned long long)sync.frames_synced);
    }
    ```
  </Step>
</Steps>


# C Reference
Source: https://docs.turso.tech/sdk/c/reference

libSQL C Reference

<Snippet />

## Installing

To use the libSQL C bindings, you need to include the `libsql.h` header file in your project and link against the `liblibsql.so` shared library.

<Card title="libSQL C" href="https://github.com/tursodatabase/libsql">
  Build from source code
</Card>

Before using libSQL, you need to call the setup function:

```c theme={null}
libsql_setup((libsql_config_t){0});
```

## In-Memory Databases

libSQL supports connecting to \[in-memory
databases]\([https://www.sqlite.org/inmemorydb.htm](https://www.sqlite.org/inmemorydb.htm) databases for cases where you don't require persistence:

```c theme={null}
libsql_database_t db = libsql_database_init((libsql_database_desc_t){.path = ":memory:"});
libsql_connection_t conn = libsql_database_connect(db);
```

## Local Development

You can work locally using an SQLite file:

```c theme={null}
libsql_database_t db = libsql_database_init((libsql_database_desc_t){.path = "local.db"});
libsql_connection_t conn = libsql_database_connect(db);
```

## Remote Databases

You can connect to remote databases using a URL and auth token:

```c theme={null}
libsql_database_t db = libsql_database_init((libsql_database_desc_t){
    .url = "TURSO_DATABASE_URL",
    .auth_token = "TURSO_AUTH_TOKEN"
});
libsql_connection_t conn = libsql_database_connect(db);
```

## Embedded Replicas

You can work with embedded replicas that can sync from the remote URL and delegate writes to the remote primary database:

```c theme={null}
libsql_database_t db = libsql_database_init((libsql_database_desc_t){
    .path = "local.db",
    .url = "TURSO_DATABASE_URL",
    .auth_token = "TURSO_AUTH_TOKEN"
});
libsql_connection_t conn = libsql_database_connect(db);
```

### Manual Sync

The `libsql_database_sync` function allows you to sync manually the local database with the remote counterpart:

```c theme={null}
libsql_sync_t sync = libsql_database_sync(db);
```

### Sync Interval

The `sync_interval` parameter in the database description allows you to set an interval for automatic synchronization of the database in the background:

```c theme={null}
libsql_database_t db = libsql_database_init((libsql_database_desc_t){
    .path = "local.db",
    .url = "TURSO_DATABASE_URL",
    .auth_token = "TURSO_AUTH_TOKEN",
    .sync_interval = 60000
});
```

### Read Your Own Writes

The `not_read_your_writes` parameter in the database description configures the database connection to ensure that writes are immediately visible to subsequent read operations initiated by the same connection. This is **enabled by default**, and you can disable it by setting `not_read_your_writes` to `true`:

```c theme={null}
libsql_database_t db = libsql_database_init((libsql_database_desc_t){
    .path = "local.db",
    .url = "TURSO_DATABASE_URL",
    .auth_token = "TURSO_AUTH_TOKEN",
    .not_read_your_writes = true
});
```

## Simple query

You can use `libsql_connection_batch` for simple queries without parameters:

```c theme={null}
libsql_batch_t batch = libsql_connection_batch(conn, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
```

For queries with results, use `libsql_connection_prepare` and `libsql_statement_query`:

```c theme={null}
libsql_statement_t stmt = libsql_connection_prepare(conn, "SELECT * FROM users");
libsql_rows_t rows = libsql_statement_query(stmt);
```

## Prepared Statements

You can prepare a statement using `libsql_connection_prepare` and then execute it with `libsql_statement_execute` or `libsql_statement_query`:

```c theme={null}
libsql_statement_t stmt = libsql_connection_prepare(conn, "INSERT INTO users (name) VALUES (?)");
libsql_statement_bind_value(stmt, libsql_text("John Doe", 8));
libsql_execute_t result = libsql_statement_execute(stmt);
```

## Placeholders

libSQL supports the use of positional and named placeholders within SQL statements:

<CodeGroup>
  ```c Positional theme={null}
  libsql_statement_t stmt = libsql_connection_prepare(conn, "SELECT * FROM users WHERE id = ?");
  libsql_statement_bind_value(stmt, libsql_integer(1));
  ```

  ```c Named theme={null}
  libsql_statement_t stmt = libsql_connection_prepare(conn, "SELECT * FROM users WHERE id = :id");
  libsql_statement_bind_named(stmt, ":id", libsql_integer(1));
  ```
</CodeGroup>

## Transactions

libSQL supports transactions:

```c theme={null}
libsql_transaction_t tx = libsql_connection_transaction(conn);
// Perform operations within the transaction
libsql_transaction_commit(tx); // or libsql_transaction_rollback(tx)
```

## Cleanup

Remember to clean up resources when you're done:

```c theme={null}
libsql_statement_deinit(stmt);
libsql_connection_deinit(conn);
libsql_database_deinit(db);
```


# Examples
Source: https://docs.turso.tech/sdk/flutter/examples





# Drift + Turso / LibSQL
Source: https://docs.turso.tech/sdk/flutter/integrations/drift

Configure `drift` to work with `libsql_dart`

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)

<Steps>
  <Step title="Add packages to your project">
    ```bash theme={null}
    flutter pub add drift_libsql drift drift_flutter dev:build_runner dev:drift_dev
    ```
  </Step>

  <Step title="Retrieve database credentials">
    <Snippet />
  </Step>

  <Step title="Create a table schema">
    ```dart theme={null}
    class TaskTable extends Table {
      IntColumn get id => integer().autoIncrement()();
      TextColumn get title => text()();
      TextColumn get description => text()();
      BoolColumn get completed => boolean()();
    }
    ```
  </Step>

  <Step title="Create db class">
    ```dart theme={null}
    @DriftDatabase(tables: [TaskTable])
    class AppDatabase extends _$AppDatabase {
      AppDatabase(super.e);

      @override
      int get schemaVersion => 1;
    }
    ```
  </Step>

  <Step title="Run build runner">
    ```bash theme={null}
    dart run build_runner build
    ```
  </Step>

  <Step title="Create the db">
    ```dart theme={null}
    final db = AppDatabase(DriftLibsqlDatabase(
        "${dir.path}/replica.db",
        syncUrl: url,
        authToken: token,
        readYourWrites: true,
        syncIntervalSeconds: 3,
      ));
    ```
  </Step>

  <Step title="Perform SQL operations">
    ```dart theme={null}
    await db.into(db.taskTable).insert(TaskTableCompanion.insert(
    	title: task.title,
    	description: task.description,
    	completed: task.completed));
    ```
  </Step>
</Steps>


# Turso Quickstart (Flutter / Dart)
Source: https://docs.turso.tech/sdk/flutter/quickstart

Get started with Flutter and Dart using the libSQL client in a few simple steps

<Note>
  This SDK is community maintained and may not be officially supported by Turso, or up to date with the latest features. Join the `#libsql-dart` channel [on Discord](https://tur.so/discord) for help and feedback.
</Note>

In this Flutter/Dart quickstart we will learn how to:

* Retrieve database credentials
* Install the Flutter/Dart libSQL client
* Connect to a local or remote Turso database
* Execute a query using SQL
* Sync changes to local database (optional)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Install libsql_dart">
    ```bash theme={null}
    flutter pub add libsql_dart
    ```
  </Step>

  <Step title="Initialize">
    Now connect to your local or remote database using the `LibsqlClient` constructor:

    <AccordionGroup>
      <Accordion title="Embedded Replicas">
        ```dart theme={null}
        final dir = await getApplicationCacheDirectory();
        final path = '${dir.path}/local.db';

        final client = LibsqlClient(path)
          ..authToken = '<TOKEN>'
          ..syncUrl = '<TURSO_OR_LIBSQL_URL>'
          ..syncIntervalSeconds = 5
          ..readYourWrites = true;
        ```
      </Accordion>

      <Accordion title="Local only">
        <CodeGroup>
          ```dart File theme={null}
          final dir = await getApplicationCacheDirectory();
          final path = '${dir.path}/local.db';

          final client = LibsqlClient(path);
          ```

          ```dart In-Memory theme={null}
          final client = LibsqlClient(":memory:");
          ```
        </CodeGroup>
      </Accordion>

      <Accordion title="Remote only">
        ```dart theme={null}
        final client = LibsqlClient('<TURSO_OR_LIBSQL_URL>')
          ..authToken = '<TOKEN>';
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Connect">
    ```dart theme={null}
    await client.connect();
    ```
  </Step>

  <Step title="Execute">
    <CodeGroup>
      ```dart Execute theme={null}
      await client.execute("create table if not exists customers (id integer primary key, name text);");
      ```

      ```dart Insert theme={null}
      await client.query("insert into customers(name) values ('John Doe')");
      ```

      ```dart Select theme={null}
      print(await client.query("select * from customers"));
      ```

      ```dart Batch theme={null}
      await client.batch("""insert into customers (name) values ('Jane Doe'); insert into customers (name) values ('Jake Doe');""");
      ```

      ```dart Prepared statement theme={null}
      final statement = await client
      	.prepare("select * from customers where id = ?");

      await statement.query(positional: [1])
      ```

      ```dart Interactive transaction theme={null}
      final tx = await client.transaction();

      await tx
      	.execute("update customers set name = 'John Noe' where id = 1");
      await tx
      	.execute("update customers set name = 'Jane Noe' where id = 2");
      print(await tx
      	.query("select * from customers where id = ?", positional: [1]));

      await tx.commit();
      ```
    </CodeGroup>

    If you need to use placeholders for values, you can do that:

    <CodeGroup>
      ```dart Positional theme={null}
      final statement = await client
        .prepare("select * from customers where id = ?");

      await statement.query(positional: [1])
      ```

      ```dart Named theme={null}
      final statement = await client
      	.prepare("select * from customers where id = :id");

      await statement.query(named: {"id": 1})
      ```
    </CodeGroup>
  </Step>

  <Step title="Sync (Embedded Replicas only)">
    When using embedded replicas you should call `sync()` on the connector to sync your local database with the primary database.

    ```dart theme={null}
    await client.sync();
    ```
  </Step>
</Steps>


# Reference
Source: https://docs.turso.tech/sdk/flutter/reference

libSQL Flutter / Dart Reference

The libSQL package for Flutter / Dart contains everything you need to work with Turso / libSQL and works flawlessly with all features, because it’s build on top libSQL Rust crate and `flutter_rust_bridge` package, allowing for seamless communication between Rust and Dart.

## Add the package to your project

```bash theme={null}
flutter pub add libsql_dart
```

Alternatively, manually add it to your project's `pubspec.yaml`

```yaml theme={null}
libsql_dart:
```

## Initializing

Call `LibsqlClient` constructor to create the database client. Different configurations are supported, allowing connection to in-memory database, local sqlite file, remote Turso / libSQL database, or embedded replica.

## In-Memory Databases

libSQL supports connecting to [in-memory databases](https://www.sqlite.org/inmemorydb.html) for cases where you don't require persistence:

```dart theme={null}
final client = LibsqlClient(":memory:");
```

## Local Development

You can work locally using an SQLite file and passing the path to `LibsqlClient`:

```dart theme={null}
final dir = await getApplicationCacheDirectory();
final path = '${dir.path}/local.db';

final client = LibsqlClient(path);
```

## Remote

You can work with remote database by passing your Turso Database URL:

```dart theme={null}
final client = LibsqlClient('<TURSO_OR_LIBSQL_URL>')
	..authToken = '<TOKEN>';
```

## Embedded Replicas

You can work with embedded replicas by passing your Turso Database URL to `syncUrl`:

```dart theme={null}
final dir = await getApplicationCacheDirectory();
final path = '${dir.path}/local.db';

final client = LibsqlClient(path)
	..authToken = '<TOKEN>'
	..syncUrl = '<TURSO_OR_LIBSQL_URL>'
	..readYourWrites = true;
```

<Snippet />

### Connect

```dart theme={null}
await client.connect();
```

### Manual Sync

The `sync()` function allows you to sync manually the local database with the remote counterpart:

```ts theme={null}
await client.sync();
```

### Periodic Sync

You can automatically sync at intervals by configuring the `syncIntervalSeconds` property when instantiating the client:

```dart theme={null}
final dir = await getApplicationCacheDirectory();
final path = '${dir.path}/local.db';

final client = LibsqlClient(path)
	..authToken = '<TOKEN>'
	..syncUrl = '<TURSO_OR_LIBSQL_URL>'
	..syncIntervalSeconds = 5
	..readYourWrites = true;
```

### Encryption at rest

To enable encryption on a SQLite file, pass the `encryptionKey`:

```dart theme={null}
final dir = await getApplicationCacheDirectory();
final path = '${dir.path}/local.db';

final client = LibsqlClient(path)..encryptionKey = '<KEY>';
```

Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — [learn more](/libsql#encryption-at-rest).

## Execute

Returns number of rows affected:

```dart theme={null}
await client.execute("create table if not exists customers (id integer primary key, name text);");
```

## Query

Returns rows as `List<Map<String, dynamic>>`. Will returns empty list when is not performing select query:

```dart theme={null}
await client.query("insert into customers(name) values ('John Doe')");

print(await client.query("select * from customers"));
```

## Placeholders

libSQL supports the use of positional and named placeholders within SQL statements:

<CodeGroup>
  ```dart Positional theme={null}
  final statement = await client
  	.prepare("select * from customers where id = ?");

  await statement.query(positional: [1])
  ```

  ```dart Named theme={null}
  final statement = await client
  	.prepare("select * from customers where id = :id");

  await statement.query(named: {"id": 1})
  ```
</CodeGroup>

<br />

<Info>
  libSQL supports the same named placeholder characters as SQLite — `:`, `@` and `$`.
</Info>

## Batch Transactions

A batch consists of multiple SQL statements executed sequentially within an implicit transaction. The backend handles the transaction: success commits all changes, while any failure results in a full rollback with no modifications.

```dart theme={null}
await client.batch("""insert into customers (name) values ('Jane Doe'); insert into customers (name) values ('Jake Doe');""");
```

## Transaction Modes

| Mode                                  | SQLite command               | Description                                                                                                                                                                                        |
| ------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LibsqlTransactionBehavior.immediate` | `BEGIN IMMEDIATE`            | The transaction may execute statements that read and write data. Write transactions executed on a replica are forwarded to the primary instance, and can't operate in parallel.                    |
| `LibsqlTransactionBehavior.readOnly`  | `BEGIN TRANSACTION READONLY` | The transaction may only execute statements that read data (select). Read transactions can occur on replicas, and can operate in parallel with other read transactions.                            |
| `LibsqlTransactionBehavior.deferred_` | `BEGIN DEFERRED`             | The transaction starts in read mode, then changes to write as soon as a write statement is executed. This mode change may fail if there is a write transaction currently executing on the primary. |

## Interactive Transactions

Interactive transactions in SQLite ensure the consistency of a series of read and write operations within a transaction's scope. These transactions give you control over when to commit or roll back changes, isolating them from other client activity.

| Method       | Description                                                         |
| ------------ | ------------------------------------------------------------------- |
| `execute()`  | Similar to `execute()` except within the context of the transaction |
| `query()`    | Similar to `query()` except within the context of the transaction   |
| `commit()`   | Commits all write statements in the transaction                     |
| `rollback()` | Rolls back the entire transaction                                   |

```dart theme={null}
final tx = await client.transaction();

await tx
	.execute("update customers set name = 'John Noe' where id = 1");
await tx
	.execute("update customers set name = 'Jane Noe' where id = 2");
print(await tx
	.query("select * from customers where id = ?", positional: [1]));

await tx.commit();
```

<br />

<Warning>
  Interactive transactions in libSQL lock the database for writing until
  committed or rolled back, with a 5-second timeout. They can impact performance
  on high-latency or busy databases.
</Warning>

## ATTACH

You can attach multiple databases to the current connection using the `ATTACH` attachment:

```dart theme={null}
final tx = await client.transaction(behavior: LibsqlTransactionBehavior.readOnly);

await tx.execute("ATTACH "<database-id>" AS attached");

print(await tx.execute("SELECT * FROM attached.customers"));

await tx.commit();
```

<Info>
  Make sure to [allow `ATTACH`](/cli/db/config/attach/allow) and create a token
  with the permission to attach a database — [learn
  more](/features/attach-database)
</Info>


# Examples
Source: https://docs.turso.tech/sdk/go/examples





# Turso Quickstart (Go)
Source: https://docs.turso.tech/sdk/go/quickstart

Get started with Turso and Go in a few simple steps.

In this Go quickstart we will learn how to:

* Install the Turso package
* Connect to a local or remote database
* Execute a query using SQL
* Sync changes to the cloud

## Recommended: tursogo (Local + Cloud Sync)

`tursogo` is the recommended package for running a local database, including synchronizing it to and from Turso Cloud. It is built on the Turso Database engine — a ground-up rewrite of SQLite with concurrent writes (MVCC) and async I/O. It implements the standard `database/sql` driver interface, so it works as a drop-in replacement for any Go SQLite driver. It ships prebuilt native libraries and uses [purego](https://github.com/ebitengine/purego) for FFI — no CGO required.

<Steps>
  <Step title="Install">
    ```bash theme={null}
    go get turso.tech/database/tursogo
    ```
  </Step>

  <Step title="Connect">
    ```go theme={null}
    package main

    import (
    	"database/sql"
    	"fmt"

    	_ "turso.tech/database/tursogo"
    )

    func main() {
    	db, _ := sql.Open("turso", "app.db")
    	defer db.Close()

    	db.Exec(`CREATE TABLE IF NOT EXISTS users (
    		id INTEGER PRIMARY KEY AUTOINCREMENT,
    		name TEXT NOT NULL
    	)`)

    	db.Exec("INSERT INTO users (name) VALUES (?)", "Alice")

    	rows, _ := db.Query("SELECT * FROM users")
    	defer rows.Close()
    	for rows.Next() {
    		var id int
    		var name string
    		rows.Scan(&id, &name)
    		fmt.Printf("User: %d %s\n", id, name)
    	}
    }
    ```
  </Step>

  <Step title="Sync (push and pull)">
    If you need to sync your local database with Turso Cloud, use the sync driver:

    ```go theme={null}
    package main

    import (
    	"context"
    	"os"

    	turso "turso.tech/database/tursogo"
    )

    func main() {
    	ctx := context.Background()

    	syncDb, _ := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{
    		Path:      "app.db",
    		RemoteUrl: os.Getenv("TURSO_DATABASE_URL"),
    		AuthToken: os.Getenv("TURSO_AUTH_TOKEN"),
    	})

    	db, _ := syncDb.Connect(ctx)
    	defer db.Close()

    	db.ExecContext(ctx, "INSERT INTO users (name) VALUES (?)", "Bob")

    	// Push local writes to Turso Cloud
    	syncDb.Push(ctx)

    	// Pull remote changes to local database
    	syncDb.Pull(ctx)
    }
    ```

    All reads and writes happen against the local database file — fast, offline-capable. `Push()` sends your changes to the cloud. `Pull()` brings remote changes down. See the [reference](/sdk/go/reference) for checkpoint, stats, and encryption. See [Turso Sync](/sync/usage) for details on conflict resolution and more.

    <Info>
      You can test sync locally without a Turso Cloud account by starting a local sync server:

      ```bash theme={null}
      tursodb :memory: --sync-server 127.0.0.1:8080
      ```

      Then set `RemoteUrl` to `http://127.0.0.1:8080` (no `AuthToken` needed). See [Turso Database quickstart](/tursodb/quickstart) for how to install `tursodb`.
    </Info>
  </Step>
</Steps>

## Remote Access (Over-the-Wire)

If your application needs to query a Turso Cloud database directly over the network (e.g., from a web server or serverless function), use the driver that matches your database engine: `tursogo-serverless` for [Turso databases](/tursodb/quickstart), or `libsql-client-go` for [libSQL](/libsql) databases.

<Info>
  For most applications, we recommend running a local database with sync (`NewTursoSyncDb`) instead — it gives you faster reads, offline support, and lower latency. Remote access is useful when you cannot store a local database file (e.g., stateless serverless environments).
</Info>

### Remote Turso database: tursogo-serverless

`tursogo-serverless` connects to a remote Turso database over HTTP — pure Go, no CGO, no native libraries. It implements the standard `database/sql` interface, compatible with the embedded `tursogo` driver.

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing Turso database to continue. If you don't have one, create one with `turso db create --tursodb` (see the [quickstart](/quickstart)).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Install">
    ```bash theme={null}
    go get turso.tech/database/tursogo-serverless
    ```
  </Step>

  <Step title="Connect and query">
    ```go theme={null}
    package main

    import (
    	"database/sql"
    	"fmt"
    	"os"

    	turso "turso.tech/database/tursogo-serverless"
    )

    func main() {
    	db := sql.OpenDB(turso.NewConnector(
    		os.Getenv("TURSO_DATABASE_URL"),
    		os.Getenv("TURSO_AUTH_TOKEN"),
    	))
    	defer db.Close()

    	rows, _ := db.Query("SELECT * FROM users")
    	defer rows.Close()
    	for rows.Next() {
    		var id int
    		var name string
    		rows.Scan(&id, &name)
    		fmt.Printf("User: %d %s\n", id, name)
    	}
    }
    ```
  </Step>
</Steps>

### Remote libSQL database: libsql-client-go

`libsql-client-go` connects to a remote libSQL database via the libSQL wire protocol — no local file needed, pure Go.

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing libSQL database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Install">
    ```bash theme={null}
    go get github.com/tursodatabase/libsql-client-go/libsql
    ```
  </Step>

  <Step title="Connect and query">
    ```go theme={null}
    package main

    import (
    	"database/sql"
    	"fmt"
    	"os"

    	_ "github.com/tursodatabase/libsql-client-go/libsql"
    )

    func main() {
    	url := os.Getenv("TURSO_DATABASE_URL") + "?authToken=" + os.Getenv("TURSO_AUTH_TOKEN")

    	db, _ := sql.Open("libsql", url)
    	defer db.Close()

    	rows, _ := db.Query("SELECT * FROM users")
    	defer rows.Close()
    	for rows.Next() {
    		var id int
    		var name string
    		rows.Scan(&id, &name)
    		fmt.Printf("User: %d %s\n", id, name)
    	}
    }
    ```
  </Step>
</Steps>

## Embedded Replicas (go-libsql)

<Info>
  Embedded Replicas give your Go app a local read copy of a Turso Cloud database. Reads are served locally; writes go to the cloud primary and are reflected back to the replica. Embedded Replicas are fully supported in production.

  For new projects that need sync, we recommend `tursogo` with `NewTursoSyncDb`: both reads and writes are local, you sync explicitly with `Push()` / `Pull()`, and the wire format is logical change-data-capture rather than page frames ([benchmark](https://turso.tech/blog/sync-benchmark)).
</Info>

See the [reference](/sdk/go/reference) for full documentation on Embedded Replicas with `go-libsql`.


# Reference
Source: https://docs.turso.tech/sdk/go/reference

Go Reference for Turso

Turso offers four Go packages:

|                       | `tursogo`                       | `tursogo-serverless`                  | `libsql-client-go`                     | `go-libsql`                                    |
| --------------------- | ------------------------------- | ------------------------------------- | -------------------------------------- | ---------------------------------------------- |
| **Use case**          | Local / embedded database, sync | Remote Turso database (over-the-wire) | Remote libSQL database (over-the-wire) | Existing libSQL codebases                      |
| **Engine**            | Turso (rewrite)                 | Turso (rewrite)                       | libSQL wire protocol                   | libSQL (SQLite fork)                           |
| **Concurrent writes** | Yes (MVCC)                      | Yes (MVCC)                            | N/A (remote)                           | Not supported                                  |
| **Sync**              | push/pull (local-first)         | —                                     | —                                      | Embedded Replicas (writes go to cloud primary) |
| **CGO**               | Not required                    | Not required                          | Not required                           | Required                                       |
| **API**               | `database/sql`                  | `database/sql`                        | `database/sql`                         | `database/sql`                                 |

**Starting a new project?** Use `tursogo` for local/embedded use or sync. For remote access, match the driver to the database engine: [`tursogo-serverless`](/sdk/go/quickstart) for Turso databases, `libsql-client-go` for libSQL databases. None of these require CGO.

## tursogo

For local and embedded use. Built on the Turso Database engine with concurrent writes (MVCC) and async I/O.

### Installing

```bash theme={null}
go get turso.tech/database/tursogo
```

### Connecting

```go theme={null}
import (
	"database/sql"
	_ "turso.tech/database/tursogo"
)

db, err := sql.Open("turso", "app.db")
```

In-memory databases are also supported:

```go theme={null}
db, err := sql.Open("turso", ":memory:")
```

### Querying

```go theme={null}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (
	id INTEGER PRIMARY KEY AUTOINCREMENT,
	name TEXT NOT NULL
)`)

_, err = db.Exec("INSERT INTO users (name) VALUES (?)", "Alice")

rows, err := db.Query("SELECT * FROM users")
defer rows.Close()

for rows.Next() {
	var id int
	var name string
	rows.Scan(&id, &name)
	fmt.Printf("User: %d %s\n", id, name)
}
```

### Encryption

Encrypt local databases at rest using DSN options:

```go theme={null}
import (
	"database/sql"
	"fmt"
	_ "turso.tech/database/tursogo"
)

hexkey := "b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327"
dsn := fmt.Sprintf("encrypted.db?experimental=encryption&encryption_cipher=aegis256&encryption_hexkey=%s", hexkey)

db, err := sql.Open("turso", dsn)
```

Supported ciphers: `aegis256`, `aegis256x2`, `aegis128l`, `aegis128x2`, `aegis128x4`, `aes256gcm`, `aes128gcm`.

Encrypted databases cannot be read as standard SQLite databases — you must use the Turso Database engine to open them.

<Info>
  Turso Cloud databases can also be encrypted with bring-your-own-key — [learn more](/cloud/encryption).
</Info>

### Sync (Push and Pull)

For local database with cloud sync. All reads and writes happen locally; use `Push()` to send changes to the cloud and `Pull()` to fetch remote changes.

```go theme={null}
import (
	"context"
	"os"
	turso "turso.tech/database/tursogo"
)

ctx := context.Background()

syncDb, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{
	Path:      "app.db",
	RemoteUrl: os.Getenv("TURSO_DATABASE_URL"),
	AuthToken: os.Getenv("TURSO_AUTH_TOKEN"),
})

db, err := syncDb.Connect(ctx)
```

On the first run, the local database is automatically bootstrapped from the remote. See [Turso Sync](/sync/usage) for full details.

#### Push and Pull

```go theme={null}
// Push local writes to Turso Cloud
err := syncDb.Push(ctx)

// Pull remote changes to local database
changed, err := syncDb.Pull(ctx)
```

#### Checkpoint

Compact the local WAL to bound disk usage while preserving sync state:

```go theme={null}
err := syncDb.Checkpoint(ctx)
```

#### Stats

```go theme={null}
stats, err := syncDb.Stats(ctx)
fmt.Printf("CDC operations: %d\n", stats.CdcOperations)
fmt.Printf("Main WAL size: %d\n", stats.MainWalSize)
fmt.Printf("Network sent: %d bytes\n", stats.NetworkSentBytes)
fmt.Printf("Network received: %d bytes\n", stats.NetworkReceivedBytes)
fmt.Printf("Last pull: %d\n", stats.LastPullUnixTime)
fmt.Printf("Last push: %d\n", stats.LastPushUnixTime)
fmt.Printf("Revision: %s\n", stats.Revision)
```

## libsql-client-go (Remote)

The recommended package for **any application that connects to a remote Turso Cloud database** over the network — web servers, Docker containers, serverless functions. Pure Go, no native dependencies.

### Installing

```bash theme={null}
go get github.com/tursodatabase/libsql-client-go/libsql
```

### Connecting

```go theme={null}
import (
	"database/sql"
	"os"
	_ "github.com/tursodatabase/libsql-client-go/libsql"
)

url := os.Getenv("TURSO_DATABASE_URL") + "?authToken=" + os.Getenv("TURSO_AUTH_TOKEN")
db, err := sql.Open("libsql", url)
```

### Querying

Uses the standard `database/sql` interface — same as `tursogo`:

```go theme={null}
rows, err := db.Query("SELECT * FROM users WHERE id = ?", 1)
```

## go-libsql (libSQL)

The `go-libsql` package is built on [libSQL](https://github.com/tursodatabase/libsql), the open-source fork of SQLite that powers Turso Cloud today. It is production-ready and battle-tested, and is the right choice when you are working with an existing `go-libsql`-based codebase.

<Info>
  With `go-libsql` Embedded Replicas, reads are local and writes are sent to the cloud primary, then reflected back to the replica. Embedded Replicas are fully supported. For new projects that need sync, we recommend `tursogo` with `NewTursoSyncDb` — see the [quickstart](/sdk/go/quickstart).
</Info>

### Embedded Replicas

<Info>
  For workloads that need **offline writes**, **bidirectional sync**, or **multi-writer convergence**, we recommend `tursogo` with `NewTursoSyncDb` — both reads and writes are local, and you sync explicitly with `Push()` / `Pull()`.
</Info>

You can work with [embedded replicas](/features/embedded-replicas) that can sync from the remote database to a local SQLite file, and delegate writes to the remote primary database:

```go theme={null}
package main

import (
  "database/sql"
  "fmt"
  "os"
  "path/filepath"

  "github.com/tursodatabase/go-libsql"
)

func main() {
    dbName := "local.db"
    primaryUrl := "libsql://[DATABASE].turso.io"
    authToken := "..."

    dir, err := os.MkdirTemp("", "libsql-*")
    if err != nil {
        fmt.Println("Error creating temporary directory:", err)
        os.Exit(1)
    }
    defer os.RemoveAll(dir)

    dbPath := filepath.Join(dir, dbName)

    connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl,
      libsql.WithAuthToken(authToken)
    )
    if err != nil {
        fmt.Println("Error creating connector:", err)
        os.Exit(1)
    }
    defer connector.Close()

    db := sql.OpenDB(connector)
    defer db.Close()
}
```

<Snippet />

#### Manual Sync

```go theme={null}
if err := connector.Sync(); err != nil {
    fmt.Println("Error syncing database:", err)
}
```

#### Periodic Sync

```go theme={null}
syncInterval := time.Minute

connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl,
    libsql.WithAuthToken(authToken),
    libsql.WithSyncInterval(syncInterval),
)
```

#### Read Your Writes

By default, after a sync the server must fully catch up with your changes before returning — guaranteeing you always read your own writes. This is safer but **much slower**, since the server must process all pending changes. If you can tolerate eventually-consistent reads, disable this for significantly faster syncs:

```go theme={null}
connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl,
    libsql.WithAuthToken(authToken),
    libsql.WithReadYourWrites(false),
)
```

### Encryption

<Warning>
  For new projects, we recommend [`tursogo`](#tursogo) for local encryption — it is built on the Turso Database engine with better performance and concurrent write support.
</Warning>

To enable encryption on a SQLite file, pass the encryption key value as an argument to the constructor:

```go theme={null}
encryptionKey := "SuperSecretKey"

connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl,
    libsql.WithAuthToken(authToken),
    libsql.WithEncryption(encryptionKey),
)
```

<Info>
  Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — [learn more](/libsql#encryption-at-rest).
</Info>


# Turso Quickstart (SQL over HTTP)
Source: https://docs.turso.tech/sdk/http/quickstart

Get started with SQL over HTTP for Turso in a few simple steps.

In this quickstart we will learn how to:

* Connect to a remote Turso database
* Execute SQL queries using [SQL over HTTP](https://github.com/tursodatabase/libsql/blob/main/docs/HRANA_3_SPEC.md#hrana-over-http)

|                  | `@tursodatabase/serverless`                  | `@libsql/client`                   | Raw HTTP               |
| ---------------- | -------------------------------------------- | ---------------------------------- | ---------------------- |
| **Use with**     | [Turso](/tursodb/quickstart) databases       | [libSQL](/libsql) databases        | Any database           |
| **Dependencies** | Uses only `fetch` — zero native dependencies | Requires Node.js or `/web` subpath | None (any HTTP client) |
| **ORM support**  | Not yet supported                            | Drizzle, Prisma, and others        | N/A                    |

Match the driver to your database engine: **`@tursodatabase/serverless`** for [Turso](/tursodb/quickstart) databases, **`@libsql/client`** for [libSQL](/libsql) databases.

<Tabs>
  <Tab title="@tursodatabase/serverless">
    The `@tursodatabase/serverless` package connects to Turso using only the `fetch` API — no native dependencies, making it the smallest option for edge and serverless runtimes.

    <Steps>
      <Step title="Install">
        ```bash theme={null}
        npm install @tursodatabase/serverless
        ```
      </Step>

      <Step title="Connect and query">
        ```typescript theme={null}
        import { connect } from "@tursodatabase/serverless";

        const conn = connect({
          url: process.env.TURSO_DATABASE_URL,
          authToken: process.env.TURSO_AUTH_TOKEN,
        });

        const stmt = await conn.prepare("SELECT * FROM users WHERE id = ?");
        const row = await stmt.get([123]);
        ```
      </Step>
    </Steps>

    For compatibility with the libSQL client API, use the compat module:

    ```typescript theme={null}
    import { createClient } from "@tursodatabase/serverless/compat";

    const client = createClient({
      url: process.env.TURSO_DATABASE_URL,
      authToken: process.env.TURSO_AUTH_TOKEN,
    });

    const result = await client.execute("SELECT * FROM users WHERE id = ?", [123]);
    ```
  </Tab>

  <Tab title="@libsql/client">
    The `@libsql/client` package is the production-ready libSQL driver used across the Turso ecosystem. Use the `/web` import for serverless and edge runtimes.

    <Steps>
      <Step title="Install">
        ```bash theme={null}
        npm install @libsql/client
        ```
      </Step>

      <Step title="Connect and query">
        ```typescript theme={null}
        import { createClient } from "@libsql/client/web";

        const client = createClient({
          url: process.env.TURSO_DATABASE_URL!,
          authToken: process.env.TURSO_AUTH_TOKEN!,
        });

        const result = await client.execute({
          sql: "SELECT * FROM users WHERE id = ?",
          args: [123],
        });
        ```
      </Step>
    </Steps>

    <Note>
      Use `@libsql/client/web` instead of `@libsql/client` for edge and serverless runtimes. The `/web` import uses the HTTP protocol.
    </Note>
  </Tab>

  <Tab title="Raw HTTP">
    You can also send queries directly over HTTP using any HTTP client.

    <Steps>
      <Step title="Create HTTP Database URL">
        Using the [Turso CLI](/cli) or [Platform API](/api-reference), fetch your database URL:

        ```bash theme={null}
        turso db show <database-name> --http-url
        ```

        <Info>
          Append `/v2/pipeline` to the URL and continue.
        </Info>
      </Step>

      <Step title="Create Database Auth Token">
        Using the Turso CLI or Platform API, create a new auth token for your database:

        ```bash theme={null}
        turso db tokens create <database-name>
        ```
      </Step>

      <Step title="Create JSON Request Payload">
        We'll be sending the query using JSON, so let's create a JSON payload that executes a SQL statement and closes the connection immediately:

        ```json theme={null}
        {
          "requests": [
            { "type": "execute", "stmt": { "sql": "SELECT * FROM users" } },
            { "type": "close" }
          ]
        }
        ```

        <Info>
          Make sure to update the `stmt.sql` to select from a table you already have.

          Bound parameter examples included on the [Reference Page](/sdk/http/reference#parameter-binding),
        </Info>
      </Step>

      <Step title="Execute HTTP Request">
        Depending on your language, you can use a HTTP client library to send the request to the URL you created as well as the `Authorization` header set to the token you created, and the request body as JSON with your SQL statement.

        You must append to the Base URL the actual pipeline URL that accepts requests — `/v2/pipeline`.

        <CodeGroup>
          ```ts JavaScript (Fetch) theme={null}
          const url = "https://[databaseName]-[organizationSlug].turso.io/v2/pipeline";
          const authToken = "...";

          fetch(url, {
            method: "POST",
            headers: {
              Authorization: `Bearer ${authToken}`,
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              requests: [
                { type: "execute", stmt: { sql: "SELECT * FROM users" } },
                { type: "close" },
              ],
            }),
          })
            .then((res) => res.json())
            .then((data) => console.log(data))
            .catch((err) => console.log(err));
          ```

          ```ruby Ruby theme={null}
          require 'net/http'
          require 'json'
          require 'uri'

          url = "https://[databaseName]-[organizationSlug].turso.io/v2/pipeline"
          auth_token = "..."

          uri = URI(url)
          request = Net::HTTP::Post.new(uri)
          request["Authorization"] = "Bearer #{auth_token}"
          request["Content-Type"] = "application/json"
          request.body = JSON.generate({
            "requests": [
              { "type": "execute", "stmt": { "sql": "SELECT * FROM users" } },
              { "type": "close" }
            ]
          })

          begin
            response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
              http.request(request)
            end

            puts JSON.parse(response.body)
          rescue => e
            puts e.message
          end
          ```
        </CodeGroup>

        <br />

        <Accordion title="Response">
          The response will be a JSON object with a `results` array containing the results of your query that looks something like this:

          ```json theme={null}
          {
            "baton": null,
            "base_url": null,
            "results": [
              {
                "type": "ok",
                "response": {
                  "type": "execute",
                  "result": {
                    "cols": [],
                    "rows": [],
                    "affected_row_count": 0,
                    "last_insert_rowid": null,
                    "replication_index": "1"
                  }
                }
              },
              {
                "type": "ok",
                "response": {
                  "type": "close"
                }
              }
            ]
          }
          ```
        </Accordion>

        If you need to use placeholders for values, you can do that:

        <CodeGroup>
          ```json Positional theme={null}
          {
            "type": "execute",
            "stmt": {
              "sql": "SELECT * FROM users WHERE id = ?",
              "args": [
                {
                  "type": "integer",
                  "value": "1"
                }
              ]
            }
          }
          ```

          ```json Named theme={null}
          {
            "type": "execute",
            "stmt": {
              "sql": "SELECT * FROM users WHERE name = :name OR name = $second_name OR name = @third_name",
              "named_args": [
                {
                  "name": "name",
                  "value": {
                    "type": "text",
                    "value": "Turso"
                  }
                },
                {
                  "name": "second_name",
                  "value": {
                    "type": "text",
                    "value": "Not Turso"
                  }
                },
                {
                  "name": "third_name",
                  "value": {
                    "type": "text",
                    "value": "Maybe Turso"
                  }
                }
              ]
            }
          }
          ```
        </CodeGroup>

        <Info>
          The `type` field within each arg corresponds to the column datatype and can be one of the following: `null`, `integer`, `float`, `text`, or `blob`.
        </Info>

        <Note>
          In JSON, the `value` is a `String` to avoid losing precision, because some
          JSON implementations treat all numbers as 64-bit floats.
        </Note>
      </Step>
    </Steps>
  </Tab>
</Tabs>


# SQL over HTTP Reference
Source: https://docs.turso.tech/sdk/http/reference



Turso Databases can be accessed via HTTP. The API enables developers to perform SQL operations using [SQL over HTTP](https://github.com/tursodatabase/libsql/blob/main/docs/HTTP_V2_SPEC.md), retrieve server version information, and monitor the health status.

<Info>
  It's recommended you use a [native SDK](/sdk).
</Info>

## Base URL

Simply replace your database URL protocol (`turso://` for Turso databases, `libsql://` for libSQL databases) with `https://`:

```bash theme={null}
https://[databaseName]-[organizationSlug].turso.io
```

You can obtain your database base URL from the Turso CLI:

<Accordion title="Obtain HTTP URL using Turso CLI">
  ```bash theme={null}
  turso db show <database-name> --http-url
  ```
</Accordion>

## Authentication

Turso uses Bearer authentication, and requires your API token to be passed with all protected requests in the `Authorization` header:

```bash theme={null}
Authorization: Bearer TOKEN
```

<Accordion title="Create token using Turso CLI">
  ```bash theme={null}
  turso db tokens create <database-name>
  ```
</Accordion>

## Endpoints

Your database has the endpoints available below:

### `POST /v2/pipeline`

You can use the `/v2/pipeline` endpoint to query a database. The endpoint accepts a series of operations to perform against a database connection. The supported operation types are:

* `execute`: execute a statement on the connection.
* `close`: close the connection.

#### Simple query

<CodeGroup>
  ```json Request theme={null}
  {
    "requests": [
      { "type": "execute", "stmt": { "sql": "CREATE TABLE users (name)" } },
      { "type": "close" }
    ]
  }
  ```

  ```json Response theme={null}
  {
    "baton": null,
    "base_url": null,
    "results": [
      {
        "type": "ok",
        "response": {
          "type": "execute",
          "result": {
            "cols": [],
            "rows": [],
            "affected_row_count": 0,
            "last_insert_rowid": null,
            "replication_index": "1"
          }
        }
      },
      {
        "type": "ok",
        "response": {
          "type": "close"
        }
      }
    ]
  }
  ```
</CodeGroup>

<br />

<Info>
  Connections are left open until they timeout, unless you close them explicitly in the request (as shown above). Every request made on a connection bumps the timeout. You should close the connection when it's no longer needed.
</Info>

#### Parameter binding

Queries with bound parameters come in two types:

1. Positional query parameters, bound by their position in the parameter list, and prefixed `?`. If the query uses positional parameters, the values should be provided as an array to the `args` field.

<CodeGroup>
  ```json Request theme={null}
  {
    "requests": [
      {
        "type": "execute",
        "stmt": {
          "sql": "SELECT * FROM users WHERE name = ?",
          "args": [
            {
              "type": "text",
              "value": "Turso"
            }
          ]
        }
      },
      {
        "type": "close"
      }
    ]
  }
  ```

  ```json Response theme={null}
  {
    "baton": null,
    "base_url": null,
    "results": [
      {
        "type": "ok",
        "response": {
          "type": "execute",
          "result": {
            "cols": [
              {
                "name": "name",
                "decltype": "TEXT"
              }
            ],
            "rows": [
              [
                {
                  "type": "text",
                  "value": "Turso"
                }
              ]
            ],
            "affected_row_count": 0,
            "last_insert_rowid": null
          }
        }
      },
      {
        "type": "ok",
        "response": {
          "type": "close"
        }
      }
    ]
  }
  ```
</CodeGroup>

<br />

2. Named bound parameters, where the parameter is referred to by a name and is prefixed with a `:`, a `@` or a `$`. If the query uses named parameters, then the `named_args` field of the query should be an array of objects mapping parameters to their values.

<CodeGroup>
  ```json Request theme={null}
  {
    "requests": [
      {
        "type": "execute",
        "stmt": {
          "sql": "SELECT * FROM users WHERE name = :name OR name = $second_name OR name = @third_name",
          "named_args": [
            {
              "name": "name",
              "value": {
                "type": "text",
                "value": "Turso"
              }
            },
            {
              "name": "second_name",
              "value": {
                "type": "text",
                "value": "Not Turso"
              }
            },
            {
              "name": "third_name",
              "value": {
                "type": "text",
                "value": "Maybe Turso"
              }
            }
          ]
        }
      },
      {
        "type": "close"
      }
    ]
  }
  ```

  ```json Response theme={null}
  {
    "baton": null,
    "base_url": null,
    "results": [
      {
        "type": "ok",
        "response": {
          "type": "execute",
          "result": {
            "cols": [
              {
                "name": "name",
                "decltype": "TEXT"
              }
            ],
            "rows": [
              [
                {
                  "type": "text",
                  "value": "Turso"
                }
              ]
            ],
            "affected_row_count": 0,
            "last_insert_rowid": null
          }
        }
      },
      {
        "type": "ok",
        "response": {
          "type": "close"
        }
      }
    ]
  }
  ```
</CodeGroup>

<br />

<Info>
  The `name` property of each `named_args` can match the prefix or omit the prefix. They were omitted in the example above, but both versions are valid.

  The `type` field within each arg corresponds to the column datatype and can be one of the following: `null`, `integer`, `float`, `text`, or `blob`.
</Info>

<Info>
  If using the `blob` type, replace the `value` property with `base64` and encode the argument into `base64` before sending the request.
</Info>

<Note>
  In JSON, the `value` is a `String` to avoid losing precision, because some
  JSON implementations treat all numbers as 64-bit floats.
</Note>

#### Interactive query

Sometimes, it may be desirable to perform multiple operation on the same connection, in multiple roundtrips. We can do this by not closing the connection right away:

<CodeGroup>
  ```json Body theme={null}
  {
    "requests": [{ "type": "execute", "stmt": { "sql": "BEGIN" } }]
  }
  ```

  ```json Response theme={null}
  {
    "baton": "m7lVVgEvknpf1P1irxHsHqrAqH7BLiwO4DQIAwr93PdZWGvdBNugLSokSsCZNkry",
    "base_url": null,
    "results": [
      {
        "type": "ok",
        "response": {
          "type": "execute",
          "result": {
            "cols": [],
            "rows": [],
            "affected_row_count": 0,
            "last_insert_rowid": null,
            "replication_index": "1"
          }
        }
      }
    ]
  }
  ```
</CodeGroup>

We can see that we have received a `baton` back. This is because we haven't closed the connection. We can now use this baton to perform more queries on the same connection:

```json Body theme={null}
{
  "baton": "m7lVVgEvknpf1P1irxHsHqrAqH7BLiwO4DQIAwr93PdZWGvdBNugLSokSsCZNkry",
  "requests": [
    { "type": "execute", "stmt": { "sql": "CREATE TABLE users (name)" } },
    {
      "type": "execute",
      "stmt": { "sql": "INSERT INTO users VALUES (\"iku\")" }
    },
    { "type": "execute", "stmt": { "sql": "COMMIT" } },
    { "type": "close" }
  ]
}
```

<Info>
  Note that both transactions and connections have timeouts. Transaction have a 5 seconds window to complete, while connections get closed after 10 seconds of idle time.
</Info>

#### Response types

The response contains the following fields:

| Field      | Type   | Description                                                                                                                          |
| ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `baton`    | string | The baton is used to identify a connection with the server so that it can be reused.                                                 |
| `base_url` | string | The base URL of the server that handled the request. This URL can be reused for subsequent requests to force routing to that server. |
| `results`  | array  | The results for each of the requests made in the pipeline.                                                                           |

The `results` array contains the results for each of the requests made in the pipeline. Each result has the following fields:

| Field                | Type    | Description                                                 |
| -------------------- | ------- | ----------------------------------------------------------- |
| `cols`               | array   | The list of columns for the returned rows.                  |
| `rows`               | array   | The rows returned for the query.                            |
| `affected_row_count` | integer | The number of rows affected by the query.                   |
| `last_insert_rowid`  | integer | The ID of the last inserted row.                            |
| `replication_index`  | string  | The replication timestamp at which this query was executed. |
| `rows_read`          | integer | The number of rows read by the query.                       |
| `rows_written`       | integer | The number of rows written by the query.                    |
| `query_duration_ms`  | float   | The duration of the query in milliseconds.                  |

### `GET /version`

To obtain the current version of the server running your database you can use the `/version` endpoint:

<CodeGroup>
  ```bash Request theme={null}
  curl -L -X GET 'https://[databaseName]-[organizationSlug].turso.io/version' \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```text 200 theme={null}
  sqld 0.21.9 (67f3ea5d 2023-10-26)
  ```
</CodeGroup>

### `GET /health`

To check the health of your database, you can use the `/health` endpoint which returns an empty body with an HTTP status:

<CodeGroup>
  ```bash Request theme={null}
  curl -L -X GET 'https://[databaseName]-[organizationSlug].turso.io/health' \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```text 200 theme={null}
  This response has no body data.
  ```
</CodeGroup>

### `GET /dump`

You can dump the database using the `/dump` endpoint:

<CodeGroup>
  ```bash Request theme={null}
  curl -L -X GET 'https://[databaseName]-[organizationSlug].turso.io/dump' \
    -H 'Authorization: Bearer TOKEN'
  ```

  ```text 200 theme={null}
  PRAGMA foreign_keys=OFF;
  BEGIN TRANSACTION;
  CREATE TABLE IF NOT EXISTS mytable (
  content TEXT,
  embedding FLOAT32(1536)
  );
  CREATE TABLE IF NOT EXISTS libsql_vector_index (type TEXT, name TEXT, vector_type TEXT, block_size INTEGER, dims INTEGER, distance_ops TEXT);
  INSERT INTO libsql_vector_index VALUES('diskann','mytable_idx','float32',128,1536,'cosine');
  CREATE INDEX mytable_idx USING diskann_cosine_ops ON mytable (embedding);
  COMMIT;
  ```
</CodeGroup>

If you're using [multi-db schemas](/features/multi-db-schemas), or vector databases, you will see statements that represent internally managed tables.

## Schema Migration Status

Turso provides a managed multi-tenant schema system — [Multi-DB Schemas](/features/multi-db-schemas). This feature allows you to create a single database that shares its schema automatically with any related child databases. Changes to the parent database automatically propogate to the related databases.

The `/v1/jobs` endpoint lets you monitor the status of schema migrations.

<Warning>
  Currently not available on AWS for Free, Developer and Scaler plans.
</Warning>

### `GET /v1/jobs`

Returns a summary of all migration jobs for a schema:

<CodeGroup>
  ```bash Request theme={null}
  curl -X GET 'https://[databaseName]-[organizationSlug].turso.io/v1/jobs' \
       -H 'Authorization: Bearer TOKEN'
  ```

  ```json Example Response theme={null}
  {
    "schema_version": 4,
    "migrations": [
      {
        "job_id": 43,
        "status": "RunSuccess"
      },
      {
        "job_id": 42,
        "status": "RunSuccess"
      },
      {
        "job_id": 39,
        "status": "RunSuccess"
      }
    ]
  }
  ```
</CodeGroup>

#### Response Fields

<ResponseField name="schema_version" type="number">
  Current version of the schema.
</ResponseField>

<ResponseField name="migrations" type="array">
  List of migration jobs

  <Expandable title="Migration Object">
    <ResponseField name="job_id" type="number">
      Unique ID for the migration job.
    </ResponseField>

    <ResponseField name="status" type="string">
      Current status of the job. Possible values: `WaitingDryRun`,
      `DryRunSuccess`, `DryRunFailure`, `WaitingRun`, `RunSuccess`, `RunFailure`
    </ResponseField>
  </Expandable>
</ResponseField>

### `GET /v1/jobs/:id`

Returns detailed information about a specific migration job.

<CodeGroup>
  ```bash Request theme={null}
  curl -X GET 'https://[databaseName]-[organizationSlug].turso.io/v1/jobs/:id' \
       -H 'Authorization: Bearer TOKEN'
  ```

  ```json Example Response theme={null}
  {
    "job_id": 1,
    "status": "RunSuccess",
    "error": null,
    "progress": [
      {
        "namespace": "db1",
        "status": "Success",
        "error": null
      },
      {
        "namespace": "db2",
        "status": "Failure",
        "error": "Connection lost"
      }
    ]
  }
  ```
</CodeGroup>

#### Path Parameters

<ParamField type="number">
  The ID of the migration job.
</ParamField>

#### Response Fields

<ResponseField name="job_id" type="number">
  Unique ID of the migration job.
</ResponseField>

<ResponseField name="status" type="string">
  Overall status of the job. Possible values are the same as in the `/v1/jobs`
  endpoint.
</ResponseField>

<ResponseField name="error" type="string">
  Error message if the job failed, null otherwise.
</ResponseField>

<ResponseField name="progress" type="array">
  List of migration statuses for the individual databases.

  <Expandable title="Progress Object">
    <ResponseField name="namespace" type="string">
      Name of the database.
    </ResponseField>

    <ResponseField name="status" type="string">
      Status of migration for this database. Possible values: `Enqueued`,
      `DryRunSuccess`, `DryRunFailure`, `Run`, `Success`, `Failure`
    </ResponseField>

    <ResponseField name="error" type="string">
      Error message if migration failed for this database, null otherwise.
    </ResponseField>
  </Expandable>
</ResponseField>

## Listen to changes

You can listen to changes committed to your database using the `/beta/listen` endpoint:

<Snippet />

<Warning>Currently not available on AWS for Free, Developer and Scaler plans.</Warning>

<Info>
  Your database group must be using the version `v0.24.18` or greater — `turso group update <group-name> --version latest`
</Info>

### `GET /beta/listen`

```bash Request theme={null}
curl -L 'https://[primary-instance-id]-[databaseName]-[organizationSlug].turso.io/beta/listen?table=TABLE_NAME' \
-H 'Authorization: Bearer TOKEN'
```

#### Query Parameters

<ParamField type="string">
  The name of the table to listen to.
</ParamField>

<ParamField type="string">
  The name of the action to listen to — `insert`, `update`, or `delete`.
</ParamField>


# Turso SDKs
Source: https://docs.turso.tech/sdk/introduction



## Which package should I use?

Two rules cover almost every case:

1. **If a local database is involved, use the Turso packages.** That means fully local databases (embedded, on-device, offline) and local databases that sync with the cloud — even when the database on the other side is libSQL.
2. **If your application only talks to a cloud database over the network, match the driver to the database engine.** [Turso databases](/tursodb/quickstart) use the serverless drivers; [libSQL](/libsql) databases use the libSQL clients.

| Use case                                          | TypeScript                  | Python                | Go                    | Rust                             |
| ------------------------------------------------- | --------------------------- | --------------------- | --------------------- | -------------------------------- |
| **Local database** (embedded, on-device, offline) | `@tursodatabase/database`   | `pyturso`             | `tursogo`             | `turso`                          |
| **Local database + cloud sync** (push/pull)       | `@tursodatabase/sync`       | `pyturso` (with sync) | `tursogo` (with sync) | `turso` (with `sync` feature)    |
| **Remote Turso database** (over-the-wire)         | `@tursodatabase/serverless` | `turso_serverless`    | `tursogo-serverless`  | `turso_serverless`               |
| **Remote libSQL database** (over-the-wire)        | `@libsql/client`            | `libsql`              | `libsql-client-go`    | `libsql` (with `remote` feature) |

**Starting a new project?** Use `@tursodatabase/database` (TypeScript), `pyturso` (Python), `tursogo` (Go), or `turso` (Rust). These are built on the Turso engine — the ground-up rewrite of SQLite with concurrent writes, async I/O, and local-first sync.

**Need sync?** Use [Turso Sync](/sync/usage) for local reads and writes with explicit `push()` / `pull()` to Turso Cloud.

**Using an ORM?**

* **TypeScript** — Drizzle and Prisma run on `@libsql/client` with production-ready support from all major ORMs in the ecosystem.
* **Rust** — [Toasty](/sdk/rust/orm/toasty), the async ORM from the Tokio project, has a native Turso driver built on the `turso` crate.
* **Python** — see the [SQLAlchemy guide](/sdk/python/orm/sqlalchemy).

**Migrating from SQLite?** Turso is a drop-in replacement. Your existing SQL, schema, and queries work unchanged.

**Already using `@libsql/client`, `libsql`, or `go-libsql`?** These packages are built on [libSQL](https://github.com/tursodatabase/libsql), the open-source fork of SQLite that has powered Turso Cloud for years. They are production-ready and battle-tested, and remain the recommended way to access libSQL databases remotely. If your workload needs concurrent writes, push/pull sync, or local-first writes (offline / multi-writer / bidirectional), consider a Turso database and its packages instead.

### The serverless drivers

The serverless drivers speak SQL over HTTP — no persistent connections, no native libraries. They work anywhere your language runs: servers, Docker containers, serverless functions, and edge runtimes. Each mirrors the API of its embedded counterpart, so code moves between local and remote with minimal changes.

<Info>
  **Need sync?** For new projects, use [Turso Sync](/sync/usage) — it gives you local reads and writes with explicit `push()` / `pull()` to the cloud. The libSQL-based SDKs below also support Embedded Replicas, where reads are local and writes are sent to the cloud primary.
</Info>

## Official SDKs

<Snippet />

## Community SDKs

These SDKs are community-driven and don't come with official Turso support.

<CardGroup>
  <Card
    title="Flutter / Dart"
    icon={
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
  <path
    fill="#1ebca1"
    d="M429.5 236.3L291.7 374.1 429.5 512H272l-59.1-59.1-78.8-78.8L272 236.3H429.5zM272 0L16 256l78.8 78.8L429.5 0H272z"
  />
</svg>
}
    href="/sdk/flutter/quickstart"
  />

  <Card
    title="Capacitor"
    icon={
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
  <path
    fill="#53b9ff"
    d="M19.93 27.059.156 46.859l30.496 30.59L0 108.191l19.715 19.813L50.43 97.25l30.547 30.531 19.777-19.8Zm0 0"
  />
  <path
    fill="#119eff"
    d="M70.258 77.45 50.43 97.25l30.547 30.531 19.777-19.8Zm0 0"
  />
  <path fill-opacity=".2" d="M70.258 77.45 50.43 97.25l7.633 7.59Zm0 0" />
  <path
    fill="#53b9ff"
    d="M97.285 50.492 128 19.738 108.215 0 77.512 30.691 46.957.156 27.184 19.957l80.82 80.922 19.777-19.8Zm0 0"
  />
  <path
    fill="#119eff"
    d="m57.68 50.492 19.828-19.8L46.957.155 27.184 19.957Zm0 0"
  />
  <path fill-opacity=".2" d="m57.68 50.492 19.828-19.8-7.633-7.594Zm0 0" />
</svg>
}
    href="https://capawesome.io/plugins/libsql"
  />

  <Card title="React Native / OP-SQLite" icon="react" href="https://op-engineering.github.io/op-sqlite/" />

  <Card
    title="Laravel"
    icon={
<svg
  xmlns="http://www.w3.org/2000/svg"
  preserveAspectRatio="xMidYMid"
  viewBox="0 0 256 264"
>
  <path
    d="m255.9 59.6.1 1.1v56.6c0 1.4-.8 2.8-2 3.5l-47.6 27.4v54.2c0 1.4-.7 2.8-2 3.5l-99.1 57-.7.4-.3.1c-.7.2-1.4.2-2.1 0l-.4-.1-.6-.3L2 206c-1.3-.8-2.1-2.2-2.1-3.6V32.7l.1-1.1.2-.4.3-.6.2-.4.4-.5.4-.3c.2 0 .3-.2.5-.3L51.6.6c1.3-.8 2.9-.8 4.1 0L105.3 29c.2 0 .3.2.4.3l.5.3c0 .2.2.4.3.5l.3.4.3.6.1.4.2 1v106l41.2-23.7V60.7c0-.4 0-.7.2-1l.1-.4.3-.7.3-.3.3-.5.5-.3.4-.4 49.6-28.5c1.2-.7 2.8-.7 4 0L254 57l.5.4.4.3.4.5.2.3c.2.2.2.5.3.7l.2.3Zm-8.2 55.3v-47l-17.3 10-24 13.7v47l41.3-23.7Zm-49.5 85v-47l-23.6 13.5-67.2 38.4v47.5l90.8-52.3ZM8.2 39.9V200l90.9 52.3v-47.5l-47.5-26.9-.4-.4c-.2 0-.3-.1-.4-.3l-.4-.4-.3-.4-.2-.5-.2-.5v-.6l-.2-.5V63.6L25.6 49.8l-17.3-10Zm45.5-31L12.4 32.8l41.3 23.7 41.2-23.7L53.7 8.9ZM75 157.3l24-13.8V39.8l-17.3 10-24 13.8v103.6l17.3-10ZM202.3 36.9 161 60.7l41.3 23.8 41.3-23.8-41.3-23.8Zm-4.1 54.7-24-13.8-17.3-10v47l24 13.9 17.3 10v-47Zm-95 106 60.6-34.5 30.2-17.3-41.2-23.8-47.5 27.4L62 174.3l41.2 23.3Z"
    fill="#1ebca1"
  />
</svg>
}
    href="https://github.com/richan-fongdasen/turso-laravel"
  />

  <Card title=".NET" icon="microsoft" href="https://github.com/tvandinther/libsql-client-dotnet" />

  <Card title="Java" icon="java" href="https://github.com/dbeaver/dbeaver-jdbc-libsql" />

  <Card title="Stateless libSQL" icon="github" href="https://github.com/DaBigBlob/libsql-stateless-easy" />
</CardGroup>

## SQL over HTTP

<CardGroup>
  <Snippet />
</CardGroup>


# Turso Quickstart (Android)
Source: https://docs.turso.tech/sdk/kotlin/quickstart

Get started with Turso and Android using the libSQL client in a few simple steps.

<Snippet />

<Note>
  This will only work with the Android Gradle Plugin for now. Fully Kotlin
  support is coming.
</Note>

In this Kotlin quickstart we will learn how to:

* Retrieve database credentials
* Add libSQL as a dependency in your Android Gradle project
* Connect to a local or remote Turso database
* Execute a query using SQL
* Sync changes to local database (optional)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Snippet />
  </Step>

  <Step title="Install">
    Add libsql as a implementation dependency in Gradle:

    <Note>
      This will only work with the Android Gradle Plugin for now.
    </Note>

    ```kotlin theme={null}
    dependencies {
        implementation("tech.turso.libsql:libsql:0.1.0")
    }
    ```
  </Step>

  <Step title="Connect">
    You must first create a `Database` object and then open a `Connection` to it:

    <AccordionGroup>
      <Accordion title="Embedded Replicas">
        ```kotlin theme={null}
        import tech.turso.libsql.Libsql

        val db = Libsql.open(
            path = "./local.db",
            url = "TURSO_DATABASE_URL",
            authToken = "TURSO_AUTH_TOKEN",
        )

        val conn = db.connect()
        ```
      </Accordion>

      <Accordion title="Local only">
        ```kotlin theme={null}
        import tech.turso.libsql.Libsql

        val db = Libsql.open(path = "./local.db")
        val conn = db.connect()
        ```
      </Accordion>

      <Accordion title="Remote only">
        ```kotlin theme={null}
        import tech.turso.libsql.Libsql

        val db = Libsql.open(
            url = "TURSO_DATABASE_URL",
            authToken = "TURSO_AUTH_TOKEN",
        )

        val conn = db.connect()
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Execute">
    You can execute a SQL query against your existing database by calling `execute()`:

    ```kotlin theme={null}
    db.connect().use {
        it.execute("INSERT INTO users (id) VALUES (1)")
    }
    ```

    If you need to use placeholders for values, you can do that:

    <CodeGroup>
      ```kotlin Positional theme={null}
      db.connect().use {
          it.query("SELECT * FROM users WHERE id = ?", 1)
      }
      ```

      ```kotlin Named theme={null}
      db.connect().use {
          it.query("INSERT INTO users (name) VALUES (:name)", mapOf(":name" to "Iku"))
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Sync (Embedded Replicas only)">
    When using embedded replicas you should call `sync()` on the database type to
    sync your local database with the primary database, unless you are using
    `syncInterval` (though there is no issue with calling `sync` with
    `syncInterval` enabled):

    ```kotlin theme={null}
    db.sync()
    ```
  </Step>
</Steps>


# Android Reference
Source: https://docs.turso.tech/sdk/kotlin/reference

libSQL Android Reference

<Snippet />

<Note>
  This will only work with the Android Gradle Plugin for now. Fully Kotlin
  support is coming.
</Note>

## Installing

Add libsql as a implementation dependency in Gradle:

```kotlin theme={null}
dependencies {
    implementation("tech.turso.libsql:libsql:0.1.0")
}
```

## In-Memory Databases

libSQL supports connecting to [in-memory
databases](https://www.sqlite.org/inmemorydb.html) for cases where you don't
require persistence:

```kotlin theme={null}
import tech.turso.libsql.Libsql

val db = Libsql.open(":memory:")
val conn = db.connect()
```

## Local Development

You can work locally using an SQLite file:

```kotlin theme={null}
import tech.turso.libsql.Libsql

val db = Libsql.open(path = "./local.db")
val conn = db.connect()
```

## Embedded Replicas

You can work with embedded replicas that can sync from the remote URL and
delegate writes to the remote primary database:

```kotlin theme={null}
import tech.turso.libsql.Libsql

val db = Libsql.open(
    path = "./local.db",
    url = "TURSO_DATABASE_URL",
    authToken = "TURSO_AUTH_TOKEN",
)

val conn = db.connect()
```

### Manual Sync

The `sync` function allows you to sync manually the local database with the
remote counterpart:

```kotlin theme={null}
db.sync() // Call sync manually to update local database (only for EmbeddedReplicaDatabase)
```

## Simple query

You can pass a string to `query()` to invoke a SQL statement, as well as
optional arguments:

<CodeGroup>
  ```kotlin Execute theme={null}
  db.connect().use {
      it.execute("INSERT INTO users VALUES (?)", 1)
  }
  ```

  ```kotlin Query theme={null}
  db.connect().use {
      it.query("INSERT INTO users VALUES (?)", 1)
  }
  ```

  ```kotlin Arguments theme={null}
  db.connect().use {
      it.query("SELECT * FROM users WHERE id = ?", 1)
  }
  ```
</CodeGroup>

## Placeholders

libSQL supports the use of positional and named placeholders within SQL statements:

```kotlin Positional theme={null}
db.connect().use {
    it.query("SELECT * FROM users WHERE id = ?", 1)
}
```

```kotlin Named theme={null}
db.connect().use {
    it.query("SELECT * FROM users WHERE id = :id", mapOf(":id" to 1))
}
```

## Batch Transactions

A batch consists of multiple SQL statements executed sequentially within an
implicit transaction. The backend handles the transaction: success commits all
changes, while any failure results in a full rollback with no modifications.

```kotlin theme={null}
db.connect().use {
    it.execute_batch("
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY,
        name TEXT NOT NULL
      );

      INSERT INTO users (name) VALUES ('Alice');
      INSERT INTO users (name) VALUES ('Bob');
    ")
}
```

## Transactions

Interactive transactions in SQLite ensure the consistency of a series of read
and write operations within a transaction's scope. These transactions give you
control over when to commit or roll back changes, isolating them from other
client activity.

```kotlin theme={null}
db.connect().use {
    val tx = it.transaction();

    tx.execute("INSERT INTO users (name) VALUES (?)", "Iku");
    tx.execute("INSERT INTO users (name) VALUES (?)", "Iku 2");

    tx.commit() // or tx.rollback()
}
```


# Examples
Source: https://docs.turso.tech/sdk/php/examples



[View examples on GitHub](https://github.com/tursodatabase/libsql-client-ts/tree/main/packages/libsql-client/examples)


# Turso Quickstart (Laravel)
Source: https://docs.turso.tech/sdk/php/guides/laravel



In this Laravel quickstart we will learn how to:

* Install the Turso Laravel adapter
* Configure Laravel to use Turso
* Create a model and migration
* Perform CRUD operations using Eloquent

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Install">
    Install the package to your Laravel project using Composer:

    ```console theme={null}
    composer require turso/libsql-laravel
    ```

    <Info>Make sure to enable FFI in your PHP configuration to use the Laravel adapter.</Info>
  </Step>

  <Step title="Configure">
    Choose your preferred setup and configure accordingly:

    <AccordionGroup>
      <Accordion title="Local only">
        Update your `config/database.php`:

        ```php theme={null}
        return [
            "default" => env("DB_CONNECTION", "libsql"),

            "connections" => [
                "libsql" => [
                    "driver" => "libsql",
                    "database" => database_path("database.db"),
                ],
                // ...
            ],
        ];
        ```

        This will use a local SQLite file with the libSQL adapter.
      </Accordion>

      <Accordion title="Remote only">
        Update your `config/database.php`:

        ```php theme={null}
        return [
            "default" => env("DB_CONNECTION", "libsql"),

            "connections" => [
                "libsql" => [
                    "driver" => "libsql",
                    "url" => env("TURSO_DATABASE_URL"),
                    "password" => env("TURSO_AUTH_TOKEN"),
                ],
                // ...
            ],
        ];
        ```

        Then add to your `.env` file:

        ```
        DB_CONNECTION=libsql
        TURSO_DATABASE_URL=libsql://...
        TURSO_AUTH_TOKEN=...
        ```
      </Accordion>

      <Accordion title="Embedded Replicas">
        Update your `config/database.php`:

        ```php theme={null}
        return [
            "default" => env("DB_CONNECTION", "libsql"),

            "connections" => [
                "libsql" => [
                    "driver" => "libsql",
                    "database" => database_path("database.db"),
                    "url" => env("TURSO_DATABASE_URL"),
                    "password" => env("TURSO_AUTH_TOKEN"),
                    "sync_interval" => env("TURSO_SYNC_INTERVAL", 300),
                ],
                // ...
            ],
        ];
        ```

        Then add to your `.env` file:

        ```
        DB_CONNECTION=libsql
        TURSO_DATABASE_URL=libsql://...
        TURSO_AUTH_TOKEN=...
        TURSO_SYNC_INTERVAL=300
        ```

        <Info>The `sync_interval` is optional and defaults to 300 seconds (5 minutes).</Info>
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Create a model and migration">
    Create a User model and migration:

    ```console theme={null}
    php artisan make:model User -m
    ```

    Update the migration file:

    ```php theme={null}
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamps();
        });
    }
    ```
  </Step>

  <Step title="Run migrations">
    Apply the migration to create the users table:

    ```console theme={null}
    php artisan migrate
    ```
  </Step>

  <Step title="Execute CRUD operations">
    Now you can perform CRUD operations using Eloquent:

    <CodeGroup>
      ```php Create theme={null}
      use App\Models\User;

      $user = User::create([
          'name' => 'John Doe',
          'email' => 'john@example.com',
      ]);
      ```

      ```php Read theme={null}
      use App\Models\User;

      // Fetch all users
      $allUsers = User::all();

      // Find a specific user by ID
      $user = User::find(1);

      // Filter users
      $filteredUsers = User::where('name', 'John Doe')->get();
      ```

      ```php Update theme={null}
      use App\Models\User;

      // Update a single record
      $user = User::find(1);
      $user->name = 'Jane Doe';
      $user->save();

      // Update multiple records
      User::where('name', 'John Doe')->update(['name' => 'Jane Doe']);
      ```

      ```php Delete theme={null}
      use App\Models\User;

      // Delete a single record
      $user = User::find(1);
      $user->delete();

      // Delete multiple records
      User::where('name', 'Jane Doe')->delete();
      ```
    </CodeGroup>
  </Step>
</Steps>


# Turso + Doctrine DBAL
Source: https://docs.turso.tech/sdk/php/orm/doctrine-dbal

Set up Turso in your PHP + Doctrine DBAL project in minutes

<img alt="Doctrine DBAL" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have an PHP Application with Doctrine DBAL

<Snippet />

<Steps>
  <Step title="Configure database credentials">
    Get the database URL:

    ```bash theme={null}
    turso db show --url <database-name>
    ```

    Get the database authentication token:

    ```bash theme={null}
    turso db tokens create <database-name>
    ```

    Assign credentials to the environment variables inside `.env`.

    ```bash theme={null}
    TURSO_DATABASE_URL=
    TURSO_AUTH_TOKEN=
    ```

    <Info>Save this later when you need to connect to **Remote** and **Embedded Replica**</Info>
  </Step>

  <Step title="Create PHP Project">
    Create new directory:

    ```bash theme={null}
    mkdir your-awesome-php-project
    cd your-awesome-php-project
    ```
  </Step>

  <Step title="Install Turso Doctrine DBAL">
    ```bash theme={null}
    composer require tursodatabase/turso-doctrine-dbal
    ```
  </Step>

  <Step title="Configure database connection">
    Setup the environment variable in your Laravel application, choose what the type connection you need.

    <CodeGroup>
      ```php In-Memory Only theme={null}
      $params = [
          "url"               => ":memory:",
          'driverClass'       => \Turso\Doctrine\DBAL\Driver::class,
      ];
      ```

      ```php Local Only theme={null}
      $params = [
          "url"               => "database.db",
          'driverClass'       => \Turso\Doctrine\DBAL\Driver::class,
      ];
      ```

      ```php Remote Only theme={null}
      $params = [
          "auth_token"        => getenv('TURSO_AUTH_TOKEN'),
          "sync_url"          => getenv('TURSO_DATABASE_URL'),
          'driverClass'       => \Turso\Doctrine\DBAL\Driver::class,
      ];
      ```

      ```php Embedded Replica theme={null}
      $params = [
          "url"               => "database.db",
          "auth_token"        => getenv('TURSO_AUTH_TOKEN'),
          "sync_url"          => getenv('TURSO_DATABASE_URL'),
          'driverClass'       => \Turso\Doctrine\DBAL\Driver::class,
      ];
      ```
    </CodeGroup>

    **Other environment variables (Embedded Replica Only)**

    <Expandable title="Other environment variables (Embedded Replica Only)">
      <ResponseField name="sync_interval" type="integer">
        Integer value representing synchronization interval in seconds (optional, default: `5`).
      </ResponseField>

      <ResponseField name="read_your_writes" type="boolean">
        Boolean value indicating whether to read your writes (optional, default: `true`).
      </ResponseField>

      <ResponseField name="encryption_key" type="string">
        String value for encryption purposes (optional, default: empty).
      </ResponseField>
    </Expandable>
  </Step>

  <Step title="Project Structure Example">
    ```bash theme={null}
    src/
    ├── helpers.php
    └── Todo.php
    vendor/
    composer.json
    composer.lock
    todo            # todo executable
    ```

    Here you simple php todo cli + Doctrine BDAL

    <CodeGroup>
      ```php todo theme={null}
      #!/usr/bin/env php
      <?php

      use Turso\Todo\CLI\Todo;

      require_once __DIR__ . '/vendor/autoload.php';

      $header = 'TODO CLI + TURSO';

      echo $header . PHP_EOL;
      function main()
      {
          echo str_repeat('-', WIDTH - 18) . "  Todo CLI - " . VERSION . PHP_EOL;

          while (true) {
              echo "[L] List | [A] Add | [E] Edit | [D] Delete | [X] Exit" . PHP_EOL;
              echo "command: ";

              $choice = trim(fgets(STDIN));

              $app = new Todo();

              switch (strtolower($choice)) {
                  case 'l':
                      $app->listTodos();
                      break;
                  case 'a':
                      $app->addTodo();
                      clearScreen();
                      break;
                  case 'e':
                      $app->editTodo();
                      clearScreen();
                      break;
                  case 'd':
                      $app->deleteTodo();
                      clearScreen();
                      break;
                  case 'x':
                      exit("Goodbye!" . PHP_EOL);
                  default:
                      echo "Invalid choice." . PHP_EOL;
                      clearScreen();
              }
          }

          echo PHP_EOL;
      }

      main();
      ```

      ```php src/helpers.php theme={null}
      <?php

      use Doctrine\DBAL\Connection;
      use Doctrine\DBAL\DriverManager;

      const WIDTH = 100;
      const VERSION = '1.0.0';

      function clearScreen()
      {
          if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
              system('cls');
          } else {
              system('clear');
          }
      }

      function createConnection(): Connection
      {
          $params = [
              "auth_token"        => getenv('TURSO_AUTH_TOKEN'),
              "sync_url"          => getenv('TURSO_DATABASE_URL'),
              'driverClass'       => \Turso\Doctrine\DBAL\Driver::class,
          ];

          $db = DriverManager::getConnection($params);
          
          return $db;
      }
      ```

      ```php src/Todo.php theme={null}
      <?php

      namespace Turso\Todo\CLI;

      use Doctrine\DBAL\Connection;

      class Todo
      {

          private Connection $db;

          public function __construct()
          {
              $this->db = createConnection();
          }

          public function loadTodos(): array
          {
              $sql = "SELECT * FROM todos";
              return $this->db->fetchAllAssociative($sql);
          }

          public function saveTodo(string $todo): void
          {
              $sql = "INSERT INTO todos (name) VALUES (?)";
              $this->db->executeStatement($sql, [$todo]);
          }

          public function updateTodo(int $id, string $todo): void
          {
              $sql = "UPDATE todos SET name = ? WHERE id = ?";
              $this->db->executeStatement($sql, [$todo, $id]);
          }

          public function removeTodo(int $id): void
          {
              $sql = "DELETE FROM todos WHERE id = ?";
              $this->db->executeStatement($sql, [$id]);
          }

          public function listTodos()
          {
              $todos = $this->loadTodos();
              echo "\n" . "TODO-List   " . str_repeat('-', WIDTH - 12) . "\n\n";
              if (!empty($todos)) {
                  foreach ($todos as $todo) {
                      echo sprintf("[%d] %s\n", $todo['id'], $todo['name']);
                  }
              } else {
                  echo "Nothing...\n";
              }
              echo "\n" . str_repeat('-', WIDTH) . "\n";
          }

          public function addTodo()
          {
              echo "Enter the new To-Do: ";
              $todo = trim(fgets(STDIN));
              $this->saveTodo($todo);
              echo "To-Do added.\n";
          }

          public function editTodo()
          {
              $this->listTodos();
              echo "Enter the number of the To-Do to edit: ";
              $index = intval(trim(fgets(STDIN)));

              $findTodo = $this->db->executeQuery("SELECT * FROM todos WHERE id = ?", [$index])->fetchAssociative();

              if (!empty($findTodo)) {
                  echo "Enter the new value: ";
                  $todo = trim(fgets(STDIN));
                  $this->updateTodo($findTodo['id'], $todo);
                  echo "To-Do updated.\n";
              } else {
                  echo "Invalid index.\n";
              }
          }

          public function deleteTodo()
          {
              $this->listTodos();
              echo "Enter the number of the To-Do to delete: ";
              $index = intval(trim(fgets(STDIN)));
              $findTodo = $this->db->executeQuery("SELECT * FROM todos WHERE id = ?", [$index])->fetchAssociative();
              if (!empty($findTodo)) {
                  $this->removeTodo($findTodo['id']);
                  echo "To-Do deleted.\n";
              } else {
                  echo "Invalid index.\n";
              }
          }
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Your PHP Application Ready!">
    ![PHP TODO CLI - TURSO](https://i.imgur.com/dmrPFr2.png)
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="PHP Todo CLI + Doctrine DBAL" icon="github" href="https://github.com/tursodatabase/example/php-todo-cli-doctrine-dbal/tree/master">
    See the full source code
  </Card>

  <Card title="Symfony Rest API CRUD" icon="github" href="https://github.com/tursodatabase/example/symfony7-rest-api-crud/tree/master">
    See the full source code
  </Card>
</CardGroup>


# Turso Quickstart (PHP)
Source: https://docs.turso.tech/sdk/php/quickstart

Get started with Turso and PHP using the libSQL client in a few simple steps.

In this PHP quickstart we will learn how to:

* Install libSQL with Composer
* Retrieve database credentials
* Connect to a Turso database
* Execute a query using SQL
* Sync changes to local database (optional)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create
    one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Install">
    Install the package to your project using composer:

    ```console theme={null}
    composer require turso/libsql
    ```
  </Step>

  <Step title="Connect">
    Now connect to your local or remote database using the libSQL connector:

    <AccordionGroup>
      <Accordion title="Embedded Replica">
        ```php theme={null}
        use Libsql\Database;

        $db = new Database(
            path: 'test.db',
            url: getenv('TURSO_URL'),
            authToken: getenv('TURSO_AUTH_TOKEN')
            syncInterval: 100 // Sync every second
        );
        $conn = $db->connect();
        ```
      </Accordion>

      <Accordion title="Local only">
        ```php theme={null}
        use Libsql\Database;

        $db = new Database(path: "database.db");
        ```

        Or just:

        ```php theme={null}
        use Libsql\Database;

        $db = new Database("database.db");
        ```
      </Accordion>

      <Accordion title="Local only">
        ```php theme={null}
        use Libsql\Database;

        $db = new Database(path: "database.db");
        ```

        Or just:

        ```php theme={null}
        use Libsql\Database;

        $db = new Database("database.db");
        ```
      </Accordion>

      <Accordion title="Remote only">
        ```php theme={null}
        use Libsql\Database;

        $db = new Database(
            url: getenv('TURSO_URL'),
            authToken: getenv('TURSO_AUTH_TOKEN'),
        )
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Execute">
    You can execute SQL queries against your existing database as follows:

    ```php theme={null}
    $createUsers = "
      CREATE TABLE IF NOT EXISTS users (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          name TEXT,
          age INTEGER
      );
      INSERT INTO users (name, age) VALUES ('Prof. Ir. Onno Widodo Purbo, M.Eng., Ph.D', 61);
      INSERT INTO users (name, age) VALUES ('Jim Geovedi', 41);
      INSERT INTO users (name, age) VALUES ('Nasirun', 59);
    ";

    $db->executeBatch($createUsers);
    $db->query("SELECT * FROM users")->fetchArray();
    ```

    If you need to use placeholders for values, you can do that:

    <CodeGroup>
      ```php Positional theme={null}
      $db->query("SELECT * FROM users WHERE id = ?", [1])->fetchArray();
      ```

      ```php Named theme={null}
      $db->query("SELECT * FROM users WHERE id = :id", [":id" => 1])->fetchArray();
      ```
    </CodeGroup>
  </Step>

  <Step title="Sync (Embedded Replicas only)">
    When using embedded replicas you should call `sync()` on the database type
    to sync your local database with the primary database:

    ```php theme={null}
    <?php

    $db->sync();
    ```
  </Step>
</Steps>


# Reference
Source: https://docs.turso.tech/sdk/php/reference

libSQL PHP Reference

## Installing

Install the package to your project using composer:

```console theme={null}
composer require turso/libsql
```

## Initializing

Make sure to add `use Libsql\Database` to access the `Database` object.

```php theme={null}
$db = new Database("local.db")
```

## In-Memory Databases

libSQL supports connecting to [in-memory
databases](https://www.sqlite.org/inmemorydb.html) for cases where you don't
require persistence:

```php theme={null}
$db = new Database(":memory:");
```

Or the simpler:

```php theme={null}
$db = new Database();
```

## Local Development

You can work locally by passing a path as the first parameter.

```php theme={null}
$db = new Database("local.db")
```

Or more explicitly:

```php theme={null}
$db = new Database(path: "local.db")
```

## Remote Only

You can use a remote only database by passing `url` and `authToken`.

```php theme={null}
$db = new Database(
    url: getenv('TURSO_URL'),
    authToken: getenv('TURSO_AUTH_TOKEN'),
);
```

## Embedded Replicas

You can work with embedded replicas by passing a `path`, `url` and `authToken`.
Embedded replicas can sync from the remote URL and delegate writes to the
remote primary database:

```php theme={null}
$db = new Database(
    path: 'test.db',
    url: getenv('TURSO_URL'),
    authToken: getenv('TURSO_AUTH_TOKEN'),
);
```

### Sync Interval

The `sync_interval` function allows you to set an interval for automatic synchronization of the database in the background:

```php theme={null}
$db = new Database(
    path: 'test.db',
    url: getenv('TURSO_URL'),
    authToken: getenv('TURSO_AUTH_TOKEN'),
    syncInterval: 300, // Sync every 3 seconds
);
```

### Manual Sync

The `sync` function allows you to sync manually the local database with the
remote counterpart:

```php theme={null}
$db->sync()
```

### Read Your Own Writes

The `readYourWrites` parameter configures the database connection to ensure
that writes are immediately visible to subsequent read operations initiated by
the same connection. This is **enabled by default**, and is particularly
important in distributed systems to ensure consistency from the perspective of
the writing process.

You can disable this behavior by passing `false`:

```php theme={null}
$db = new Database(
    path: 'test.db',
    url: getenv('TURSO_URL'),
    authToken: getenv('TURSO_AUTH_TOKEN'),
    readYourWrites: false,
);
```

## Simple Query

You can acquire a connection from a database and call `query()` to invoke a
SQL statement, as well as optional arguments:

<CodeGroup>
  ```php Query theme={null}
  $rows = $conn->query("SELECT * FROM users");
  ```

  ```php Arguments theme={null}
  $rows = $conn->query("SELECT * FROM users WHERE id = ?1", [1]);
  ```

  ```php Named Arguments theme={null}
  $rows = $conn->query("SELECT * FROM users WHERE id = :id", [":id" => 1]);
  ```
</CodeGroup>

## Prepared Statements

You can prepare a cached statement using `prepare()`, bind parameters, and then
query it:

```php theme={null}
$stmt = $conn->prepare("SELECT * FROM users where id = ?");
$rows = $stmt->bind([1])->query();
```

## Placeholders

libSQL supports the use of positional and named placeholders within SQL statements:

<CodeGroup>
  ```rust Positional theme={null}
  conn->query("SELECT * FROM users WHERE id = ?", [1]);
  ```

  ```rust Named theme={null}
  conn->execute("INSERT INTO users (name) VALUES (:name)", [ ":name" => "Iku" ]);
  ```
</CodeGroup>

## Batch Transactions

A batch consists of multiple SQL statements executed sequentially within an
implicit transaction. The backend handles the transaction: success commits all
changes, while any failure results in a full rollback with no modifications.

```php theme={null}
$conn->execute_batch("
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
  );

  INSERT INTO users (name) VALUES ('Alice');
  INSERT INTO users (name) VALUES ('Bob');
");
```

## Interactive Transactions

Interactive transactions in SQLite ensure the consistency of a series of read
and write operations within a transaction's scope. These transactions give you
control over when to commit or roll back changes, isolating them from other
client activity.

```php theme={null}
$tx = conn->transaction();

$tx->execute("INSERT INTO users (name) VALUES (?1)", ["Iku"]);
$tx->execute("INSERT INTO users (name) VALUES (?1)", ["Iku 2"]);

tx->commit(); // or, $tx->rollback()
```


# Examples
Source: https://docs.turso.tech/sdk/python/examples





# Flask + Turso
Source: https://docs.turso.tech/sdk/python/guides/flask

Set up Turso in your Flask project in minutes

<img alt="Flask banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a Flask app — [learn more](https://flask.palletsprojects.com/en/3.0.x/installation/#create-an-environment)

<Steps>
  <Step title="Install the libSQL dialect">
    ```bash theme={null}
    pip install sqlalchemy-libsql python-dotenv
    ```
  </Step>

  <Step title="Retrieve database credentials">
    <Snippet />
  </Step>

  <Step title="Create database models">
    ```python models.py theme={null}
    from sqlalchemy import String
    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column

    class Base(DeclarativeBase):
        pass

    class Item(Base):
        __tablename__ = "items"
        id: Mapped[str] = mapped_column(primary_key=True)
        foo: Mapped[str] = mapped_column(String(255))
        bar: Mapped[str] = mapped_column(String(100))
        def __repr__(self) -> str:
            return f"Item(id={self.id!r}, foo={self.foo!r}, bar={self.bar!r})"
    ```
  </Step>

  <Step title="Query">
    ```python theme={null}
    from dotenv import load_dotenv
    from sqlalchemy import create_engine
    from sqlalchemy.orm import Session
    from sqlalchemy import select
    from models import Item

    load_dotenv()

    # Get environment variables
    TURSO_DATABASE_URL = os.environ.get("TURSO_DATABASE_URL")
    TURSO_AUTH_TOKEN = os.environ.get("TURSO_AUTH_TOKEN")

    # construct special SQLAlchemy URL
    dbUrl = f"sqlite+{TURSO_DATABASE_URL}/?authToken={TURSO_AUTH_TOKEN}&secure=true"

    engine = create_engine(dbUrl, connect_args={'check_same_thread': False}, echo=True)

    @app.route("/", methods=(["GET"]))
    def home():
        session = Session(engine)

        # get & print items
        stmt = select(Item)

        for item in session.scalars(stmt):
            print(item)
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Social App" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-find-me-on-python-htmx">
    See the full source code
  </Card>
</CardGroup>


# SQLAlchemy + Turso
Source: https://docs.turso.tech/sdk/python/orm/sqlalchemy

Configure SQLAlchemy to work with your Turso database

<img alt="SQLAlchemy Quickstart" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)

<Steps>
  <Step title="Install the libSQL dialect for SQLAlchemy">
    ```bash theme={null}
    pip install sqlalchemy-libsql
    ```
  </Step>

  <Step title="Retrieve database credentials">
    <Snippet />
  </Step>

  <Step title="Create database models">
    ```python models.py theme={null}
    from sqlalchemy import String
    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column

    class Base(DeclarativeBase):
        pass

    class Foo(Base):
        __tablename__ = "foo"
        id: Mapped[str] = mapped_column(primary_key=True)
        bar: Mapped[str] = mapped_column(String(100))
        def __repr__(self) -> str:
            return f"Item(id={self.id!r}, bar={self.bar!r})"

    ```
  </Step>

  <Step title="Create engine">
    <AccordionGroup>
      <Accordion title="Embedded Replicas">
        ```python theme={null}
        from dotenv import load_dotenv
        from sqlalchemy import create_engine

        TURSO_DATABASE_URL = os.environ.get("TURSO_DATABASE_URL")
        TURSO_AUTH_TOKEN = os.environ.get("TURSO_AUTH_TOKEN")

        engine = create_engine(
             "sqlite+libsql:///embedded.db",
             connect_args={
                 "auth_token": TURSO_AUTH_TOKEN,
                 "sync_url": TURSO_DATABASE_URL,
             },
        )
        ```
      </Accordion>

      <Accordion title="Remote only">
        ```python theme={null}
        from dotenv import load_dotenv
        from sqlalchemy import create_engine

        TURSO_DATABASE_URL = os.environ.get("TURSO_DATABASE_URL")
        TURSO_AUTH_TOKEN = os.environ.get("TURSO_AUTH_TOKEN")

        engine = create_engine(
            f"sqlite+{TURSO_DATABASE_URL}?secure=true",
            connect_args={
                "auth_token": TURSO_AUTH_TOKEN,
            },
        )
        ```
      </Accordion>

      <Accordion title="Memory Only">
        ```python theme={null}
        from sqlalchemy import create_engine

        engine = create_engine("sqlite+libsql://")
        ```
      </Accordion>

      <Accordion title="Local only">
        ```python theme={null}
        from sqlalchemy import create_engine

        engine = create_engine("sqlite+libsql:///local.db")
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Query">
    ```py app.py theme={null}
    from sqlalchemy.orm import Session
    from sqlalchemy import select
    from models import Foo

    @app.route("/", methods=(["GET"]))
    def home():
        session = Session(engine)

        # get & print foos
        stmt = select(Foo)

        for item in session.scalars(stmt):
            print(item)

    ```
  </Step>
</Steps>


# Turso Quickstart (Python)
Source: https://docs.turso.tech/sdk/python/quickstart

Get started with Turso and Python in a few simple steps.

In this Python quickstart we will learn how to:

* Install the Turso package
* Connect to a local or remote database
* Execute a query using SQL
* Sync changes to local database

## Recommended: pyturso (Local / Embedded)

`pyturso` is the recommended package for local and embedded use cases. It is built on the Turso Database engine — a ground-up rewrite of SQLite with concurrent writes (MVCC) and async I/O. The API follows the standard Python `sqlite3` interface, so it works as a drop-in replacement.

<Steps>
  <Step title="Install">
    ```bash theme={null}
    uv add pyturso
    ```

    Or with pip:

    ```bash theme={null}
    pip install pyturso
    ```
  </Step>

  <Step title="Connect">
    ```py theme={null}
    import turso

    db = turso.connect("app.db")
    ```

    In-memory databases are also supported:

    ```py theme={null}
    db = turso.connect(":memory:")
    ```
  </Step>

  <Step title="Execute">
    ```py theme={null}
    db.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
    db.execute("INSERT INTO users (name) VALUES (?)", ("Alice",))
    db.commit()

    for row in db.execute("SELECT * FROM users"):
        print(row)
    ```
  </Step>

  <Step title="Sync (push and pull)">
    If you need to sync your local database with Turso Cloud, use `turso.sync`:

    ```py theme={null}
    import os
    import turso.sync

    db = turso.sync.connect(
        "app.db",
        remote_url=os.environ["TURSO_DATABASE_URL"],
        auth_token=os.environ["TURSO_AUTH_TOKEN"],
    )

    db.execute("INSERT INTO users (name) VALUES (?)", ("Bob",))
    db.commit()

    # Push local writes to Turso Cloud
    db.push()

    # Pull remote changes to local database
    db.pull()
    ```

    All reads and writes happen against the local database file — fast, offline-capable. `push()` sends your changes to the cloud. `pull()` brings remote changes down. See [Turso Sync](/sync/usage) for details on conflict resolution, checkpointing, and more.
  </Step>
</Steps>

## Remote Access (Over-the-Wire)

If your application needs to query a Turso Cloud database directly over the network (e.g., from a web server or serverless function), use the driver that matches your database engine: `turso_serverless` for [Turso databases](/tursodb/quickstart), or `libsql` for [libSQL](/libsql) databases.

<Info>
  For most applications, we recommend running a local database with [Turso Sync](/sync/usage) (`turso.sync`) instead — it gives you faster reads, offline support, and lower latency. Remote access is useful when you cannot store a local database file (e.g., stateless serverless environments).
</Info>

### Remote Turso database: turso\_serverless

`turso_serverless` connects to a remote Turso database over HTTP — no persistent connections, no native extensions, just the standard library. Like `pyturso`, it implements the standard DB-API interface.

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing Turso database to continue. If you don't have one, create one with `turso db create --tursodb` (see the [quickstart](/quickstart)).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Install">
    ```bash theme={null}
    uv add turso_serverless
    ```

    Or with pip:

    ```bash theme={null}
    pip install turso_serverless
    ```
  </Step>

  <Step title="Connect and query">
    ```py theme={null}
    import os
    import turso_serverless

    conn = turso_serverless.connect(
        os.environ["TURSO_DATABASE_URL"],
        auth_token=os.environ["TURSO_AUTH_TOKEN"],
    )

    conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
    conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",))
    conn.commit()

    for row in conn.execute("SELECT * FROM users"):
        print(row)

    conn.close()
    ```
  </Step>
</Steps>

### Remote libSQL database: libsql

`libsql` connects to a remote libSQL database via HTTP — no local file needed.

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing libSQL database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Install">
    ```bash theme={null}
    uv add libsql
    ```

    Or with pip:

    ```bash theme={null}
    pip install libsql
    ```
  </Step>

  <Step title="Connect and query">
    ```py theme={null}
    import os
    import libsql

    conn = libsql.connect(
        database=os.environ["TURSO_DATABASE_URL"],
        auth_token=os.environ["TURSO_AUTH_TOKEN"],
    )

    conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
    conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",))
    conn.commit()

    rows = conn.execute("SELECT * FROM users").fetchall()
    print(rows)
    ```
  </Step>
</Steps>

## Embedded Replicas (libsql)

The `libsql` package also supports Embedded Replicas — local reads from a file, with writes sent to the cloud primary and reflected back to the replica. Embedded Replicas are fully supported in production. For new projects that need sync, we recommend `turso.sync` instead: both reads and writes are local, and you sync explicitly with `push()` / `pull()`.

See the [reference](/sdk/python/reference) for full documentation on Embedded Replicas, encryption, and periodic sync.


# Reference
Source: https://docs.turso.tech/sdk/python/reference

Python Reference for Turso

Turso offers three Python packages:

|                       | `pyturso`                       | `turso_serverless`                    | `libsql`                                          |
| --------------------- | ------------------------------- | ------------------------------------- | ------------------------------------------------- |
| **Use case**          | Local / embedded database, sync | Remote Turso database (over-the-wire) | Remote libSQL database, existing libSQL codebases |
| **Engine**            | Turso (rewrite)                 | Turso (rewrite)                       | libSQL (SQLite fork)                              |
| **Concurrent writes** | Yes (MVCC)                      | Yes (MVCC)                            | Not supported                                     |
| **Sync**              | push/pull (local-first)         | —                                     | Embedded Replicas (writes go to cloud primary)    |
| **API**               | Python `sqlite3`-compatible     | Python `sqlite3`-compatible           | Python `sqlite3`-compatible                       |

**Starting a new project?** Use `pyturso` — it is built on the Turso engine with concurrent writes and local-first sync. For remote access over the network, match the driver to the database engine: [`turso_serverless`](/sdk/python/quickstart) for Turso databases, `libsql` for libSQL databases.

## pyturso

For local and embedded use. Built on the Turso Database engine with concurrent writes (MVCC) and async I/O.

### Installing

```bash theme={null}
pip install pyturso
```

### Connecting

```py theme={null}
import turso

conn = turso.connect("app.db")
```

In-memory databases are also supported:

```py theme={null}
conn = turso.connect(":memory:")
```

### Querying

```py theme={null}
conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",))
conn.commit()

for row in conn.execute("SELECT * FROM users"):
    print(row)
```

### Encryption

Encrypt local databases at rest using the `encryption` option:

```py theme={null}
from turso import connect, EncryptionOpts

conn = connect("encrypted.db",
               experimental_features="encryption",
               encryption=EncryptionOpts(cipher="aegis256",
                                         hexkey="b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327"))
```

Supported ciphers: `aegis256`, `aegis256x2`, `aegis128l`, `aegis128x2`, `aegis128x4`, `aes256gcm`, `aes128gcm`.

Encrypted databases cannot be read as standard SQLite databases — you must use the Turso Database engine to open them.

<Info>
  Turso Cloud databases can also be encrypted with bring-your-own-key — [learn more](/cloud/encryption).
</Info>

## libsql (libSQL)

The `libsql` package is built on [libSQL](https://github.com/tursodatabase/libsql), the open-source fork of SQLite that powers Turso Cloud today. It is production-ready and battle-tested, and is the right choice when you are working with an existing `libsql`-based codebase.

<Info>
  With `libsql` Embedded Replicas, reads are local and writes are sent to the cloud primary, then reflected back to the replica. Embedded Replicas are fully supported. For new projects that need sync, we recommend `turso.sync` — see the [quickstart](/sdk/python/quickstart).
</Info>

## Embedded Replicas

<Info>
  For workloads that need **offline writes**, **bidirectional sync**, or **multi-writer convergence**, we recommend `turso.sync` — both reads and writes are local, and you sync explicitly with `push()` / `pull()`. See the [quickstart](/sdk/python/quickstart).
</Info>

You can work with [embedded replicas](/features/embedded-replicas) that can sync from the remote database to a local SQLite file, and delegate writes to the remote primary database:

```py theme={null}
import os

import libsql

conn = libsql.connect("local.db", sync_url=os.getenv("LIBSQL_URL"),
                      auth_token=os.getenv("LIBSQL_AUTH_TOKEN"))
conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER);")
conn.execute("INSERT INTO users(id) VALUES (1);")
conn.commit()

print(conn.execute("select * from users").fetchall())
```

<Snippet />

### Periodic Sync

You can automatically sync at intervals by passing time in seconds to the `sync_interval` option. For example, to sync every minute, you can use the following code:

```py theme={null}
conn = libsql.connect("local.db", sync_interval=60, sync_url=os.getenv("LIBSQL_URL"),
                      auth_token=os.getenv("LIBSQL_AUTH_TOKEN"))
```

### Manual Sync

The `Sync` function allows you to sync manually the local database with the remote counterpart:

```py theme={null}
conn.execute("INSERT INTO users(id) VALUES (2);")
conn.commit()
conn.sync()
```

## Encryption

<Warning>
  For new projects, we recommend [`pyturso`](#pyturso) for local encryption — it is built on the Turso Database engine with better performance and concurrent write support.
</Warning>

To enable encryption on a SQLite file, pass the encryption secret to the `encryption_key` option:

```py theme={null}
conn = libsql.connect("encrypted.db", sync_url=os.getenv("LIBSQL_URL"),
                      auth_token=os.getenv("LIBSQL_AUTH_TOKEN"),
                      encryption_key=os.getenv("ENCRYPTION_KEY"))
```

<Info>
  Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — [learn more](/libsql#encryption-at-rest).
</Info>


# Turso Examples (Ruby)
Source: https://docs.turso.tech/sdk/ruby/examples



| Example                                                                                        | Description                                                                             |
| ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| [local](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/local)               | Uses libsql with a local SQLite file. Creates database, inserts data, and queries.      |
| [remote](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/remote)             | Connects to a remote database. Requires environment variables for URL and auth token.   |
| [sync](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/sync)                 | Demonstrates synchronization between local and remote databases.                        |
| [transactions](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/transactions) | Shows transaction usage: starting, performing operations, and committing/rolling back.  |
| [memory](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/memory)             | Uses an in-memory SQLite database for temporary storage or fast access.                 |
| [vector](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/vector)             | Works with vector embeddings, storing and querying for similarity search.               |
| [encryption](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/encryption)     | Creates and uses an encrypted SQLite database, demonstrating setup and data operations. |


# Turso Quickstart (Ruby)
Source: https://docs.turso.tech/sdk/ruby/quickstart

Get started with Turso and Ruby using the libSQL client in a few simple steps.

<Snippet />

In this Ruby quickstart we will learn how to:

* Retrieve database credentials
* Install the libSQL package
* Connect to a local or remote Turso database
* Execute a query using SQL
* Sync changes to local database (optional)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Snippet />
  </Step>

  <Step title="Install">
    Inside your Ruby project, install the following Rubygem:

    ```bash theme={null}
    bundle add turso_libsql
    ```
  </Step>

  <Step title="Connect">
    <AccordionGroup>
      <Accordion title="Embedded Replicas">
        ```rb theme={null}
        require 'turso_libsql'

        db =Libsql::Database.new(
          path: 'local.db',
          url: ENV['TURSO_DATABASE_URL'],
          auth_token: ENV['TURSO_AUTH_TOKEN'],
          sync_interval: 100
        )
        ```
      </Accordion>

      <Accordion title="Local only">
        ```rb theme={null}
        require 'turso_libsql'

        db = Libsql::Database.new(path: 'local.db')
        ```
      </Accordion>

      <Accordion title="Remote only">
        ```rb theme={null}
        require 'turso_libsql'

        db = Libsql::Database.new(
          url: ENV['TURSO_DATABASE_URL'],
          auth_token: ENV['TURSO_AUTH_TOKEN']
        )
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Execute">
    You can execute a SQL query against your existing database by preparing a statement and then executing it:

    ```c theme={null}
    db.connect do |conn|
      rows = conn.query 'SELECT * FROM users'
      rows.close
    end
    ```

    If you need to use placeholders for values, you can do that:

    <CodeGroup>
      ```c Positional theme={null}
      name = 'Iku'
      rows = conn.execute 'INSERT INTO users (id) VALUES (?)', [name]
      ```
    </CodeGroup>
  </Step>

  <Step title="Sync (Embedded Replicas only)">
    When using embedded replicas, you should call `sync` on the database to sync your local database with the primary database, unless you are using `sync_interval` (though there is no issue with calling `sync` with `sync_interval` enabled):

    ```rb theme={null}
    db.sync
    ```
  </Step>
</Steps>


# Examples
Source: https://docs.turso.tech/sdk/rust/examples





# Turso + Actix
Source: https://docs.turso.tech/sdk/rust/guides/actix

Set up Turso in your Actix project in minutes

<img alt="Actix banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have an Actix app — [learn more](https://actix.rs/docs/getting-started)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Add the libsql crate to the project">
    ```sh theme={null}
    cargo add libsql
    ```

    <Note>
      Optionally, you can add a package such as [`dotenvy`](https://docs.rs/dotenvy/latest/dotenvy) to help you work with `.env` files:

      ```sh theme={null}
      cargo add dotenvy
      ```
    </Note>
  </Step>

  <Step title="Execute SQL">
    ```rust theme={null}
    #[tokio::main]
    #[actix_web::main]
    async fn main() -> std::io::Result<()> {
        HttpServer::new(|| App::new().route("/", web::get().to(index)).route("/items", web::get().to(get_items)))
            .bind(("127.0.0.1", 8080))?
            .run()
            .await
    }

    async fn get_items() -> Result<HttpResponse, Error> {
        dotenv().expect(".env file not found");

        let db_url = env::var("TURSO_DATABASE_URL").unwrap();
        let auth_token = env::var("TURSO_AUTH_TOKEN").unwrap();
        let db_file = env::var("LOCAL_DB").unwrap();

        let db = Builder::new_remote_replica(db_file, url, auth_token)
        .read_your_writes(true)
        .build()
        .await
        .unwrap();

        let conn = db.connect().unwrap();

        let mut results = conn.query("SELECT * FROM items", ()).await.unwrap();

        let mut items: Vec<T> = Vec::new();

        while let Some(row) = results.next().await.unwrap() {
            let item: Item = Item {
                task: row.get(0).unwrap(),
            };
            items.push(item);
        }

        Ok(HttpResponse::Ok().json(items))
    }
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Turso + Actix Web Traffic Tracker" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-web-traffic-tracker-actix">
    See the full source code
  </Card>
</CardGroup>


# Turso + Axum
Source: https://docs.turso.tech/sdk/rust/guides/axum

Set up Turso in your Axum project in minutes

<img alt="Axum banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have an Axum app — [learn more](https://github.com/tokio-rs/axum)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Add the libsql crate to the project">
    ```sh theme={null}
    cargo add libsql
    ```

    <Note>
      Optionally, you can add a package such as [`dotenvy`](https://docs.rs/dotenvy/latest/dotenvy) to help you work with `.env` files:

      ```sh theme={null}
      cargo add dotenvy
      ```
    </Note>
  </Step>

  <Step title="Execute SQL">
    ```rust theme={null}
    use libsql::{Builder, Connection, Database, Result};

    #[tokio::main]
    async fn main() -> Result<()> {
        dotenv().ok();

        let db_url = std::env::var("TURSO_DATABASE_URL").expect("TURSO_DATABASE_URL must be set");
        let auth_token = std::env::var("TURSO_AUTH_TOKEN").expect("TURSO_AUTH_TOKEN must be set");

        let db = Builder::new_remote(db_url, auth_token)
            .build()
            .await?;

        let conn = db.connect()?;

        // Execute a query
        let mut rows = conn.query("SELECT * FROM users", ()).await?;

        while let Some(row) = rows.next().await? {
            let id: i64 = row.get(0)?;
            let name: String = row.get(1)?;
            println!("User: {} - {}", id, name);
        }

        Ok(())
    }
    ```
  </Step>

  <Step title="Use in an Axum handler">
    ```rust theme={null}
    use axum::{
        extract::State,
        routing::get,
        Json, Router,
    };
    use libsql::{Builder, Connection, Database};
    use serde::Serialize;

    #[derive(Clone)]
    struct AppState {
        db: Database,
    }

    #[derive(Serialize)]
    struct User {
        id: i64,
        name: String,
    }

    async fn get_users(State(state): State<AppState>) -> Json<Vec<User>> {
        let conn = state.db.connect().unwrap();
        let mut rows = conn.query("SELECT id, name FROM users", ()).await.unwrap();

        let mut users = Vec::new();
        while let Some(row) = rows.next().await.unwrap() {
            users.push(User {
                id: row.get(0).unwrap(),
                name: row.get(1).unwrap(),
            });
        }

        Json(users)
    }

    #[tokio::main]
    async fn main() {
        let db_url = std::env::var("TURSO_DATABASE_URL").expect("TURSO_DATABASE_URL must be set");
        let auth_token = std::env::var("TURSO_AUTH_TOKEN").expect("TURSO_AUTH_TOKEN must be set");

        let db = Builder::new_remote(db_url, auth_token)
            .build()
            .await
            .unwrap();

        let app_state = AppState { db };

        let app = Router::new()
            .route("/users", get(get_users))
            .with_state(app_state);

        axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
            .serve(app.into_make_service())
            .await
            .unwrap();
    }
    ```

    This example creates a shared `Database` instance in the application state, which is then used in the handler to execute queries.
  </Step>
</Steps>


# Turso + Rocket
Source: https://docs.turso.tech/sdk/rust/guides/rocket

Set up Turso in your Rocket project in minutes

<img alt="Rocket banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a Rocket app — [learn more](https://rocket.rs/v0.5/guide/getting-started/)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Add the libsql crate to the project">
    ```sh theme={null}
    cargo add libsql
    ```

    <Note>
      Optionally, you can add a package such as [`dotenvy`](https://docs.rs/dotenvy/latest/dotenvy) to help you work with `.env` files:

      ```sh theme={null}
      cargo add dotenvy
      ```
    </Note>
  </Step>

  <Step title="Execute SQL">
    ```rust theme={null}
    #[get("/todos")]
    async fn get_todos() -> Json<Vec<Todo>> {
        dotenv().expect(".env file not found");

        let url = env::var("TURSO_DATABASE_URL").expect("TURSO_DATABASE_URL not found!");
        let token = env::var("TURSO_AUTH_TOKEN").expect("TURSO_AUTH_TOKEN not found!");

        let db = Database::open_remote(url, token).unwrap();
        let conn = db.connect().unwrap();

        let mut response = conn.query("select * from todos", ()).await.unwrap();

        let mut todos: Vec<Todo> = Vec::new();
        while let Some(row) = response.next().unwrap() {
            let todo: Todo = Todo {
                task: row.get(0).unwrap(),
            };
            todos.push(todo);
        }

        Json(todos)
    }

    #[launch]
    fn rocket() -> _ {
        dotenv().ok();
        rocket::build().mount("/", routes![get_todos])
    }
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Turso + Rocket Todo List" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-todo-rocket">
    See the full source code
  </Card>
</CardGroup>


# Tauri + Turso
Source: https://docs.turso.tech/sdk/rust/guides/tauri

Set up Turso in your Tauri project in minutes

<img alt="Tauri banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a Tauri app — [learn more](https://tauri.app/v1/guides/getting-started/setup)

<Steps>
  <Step title="Add the following crates to the Tauri project's dependencies">
    ```toml src-tauri/Cargo.toml theme={null}
    [dependencies]
    serde_json = "1.0"
    serde = { version = "1.0", features = ["derive"] }
    reqwest = { version = "0.11.22", features = ["json", "blocking"] }
    dotenvy = "0.15.7"
    tokio = { version = "1", features = ["full"] }
    libsql = { git = "https://github.com/tursodatabase/libsql" }
    tracing-subscriber = "0.3"
    tracing = "0.1.40"

    [patch.crates-io]
    sqlite3-parser = { git = "https://github.com/LucioFranco/lemon-rs" }
    ```
  </Step>

  <Step title="Fetch Rust dependencies inside the `src-tauri` directory.">
    ```bash theme={null}
    cargo fetch
    ```
  </Step>

  <Step title="Configure database credentials">
    Get the database URL.

    ```bash theme={null}
    turso db show --url <database-name>
    ```

    Get the database authentication token.

    ```bash theme={null}
    turso db tokens create <database-name>
    ```

    Assign acquired credentials to the environment variables inside `src-tauri/.env`.

    ```bash src-tauri/.env theme={null}
    TURSO_SYNC_URL="..."
    TURSO_AUTH_TOKEN="..."
    DB_PATH=./my-data.db # embedded replica
    ```
  </Step>

  <Step title="Execute SQL">
    ```rust src-tauri/src/main.rs theme={null}
    use dotenvy::dotenv;
    use libsql::{params, Database};
    use serde::{Deserialize, Serialize};
    use std::env;

    use tracing::info;

    #[derive(Serialize, Debug)]
    struct Error {
        msg: String,
    }

    type Result<T> = std::result::Result<T, Error>;

    impl<T> From<T> for Error
    where
        T: std::error::Error,
    {
        fn from(value: T) -> Self {
            Self {
                msg: value.to_string(),
            }
        }
    }

    #[derive(Deserialize, Serialize, Debug)]
    pub struct Item {
        id: String,
        foo: String,
        bar: String,
    }

    #[tauri::command]
    async fn get_all_items() -> Result<Vec<Item>> {
        dotenv().expect(".env file not found");

        let db_path = env::var("DB_PATH").unwrap();
        let sync_url = env::var("TURSO_SYNC_URL").unwrap();
        let auth_token = env::var("TURSO_AUTH_TOKEN").unwrap();

        let db = Database::open_with_remote_sync(db_path, sync_url, auth_token).await?;

        let conn = db.connect()?;

        let mut results = conn
            .query("SELECT * FROM table_name", ())
            .await?;

        let mut items: Vec<Item> = Vec::new();
        while let Some(row) = results.next()? {
            let note: Item = Item {
                id: row.get(0)?,
                foo: row.get(1)?,
                bar: row.get(2)?,
            };
            items.push(item);
        }

        Ok(items)
    }

    fn main() {
        tracing_subscriber::fmt::init();

        tauri::Builder::default()
            .invoke_handler(tauri::generate_handler![
                get_all_items,
            ])
            .run(tauri::generate_context!())
            .expect("error while running tauri application");
    }
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Personal Notes App" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-turso-notes">
    See the full source code
  </Card>
</CardGroup>


# Toasty + Turso
Source: https://docs.turso.tech/sdk/rust/orm/toasty

Use Toasty, the async ORM from the Tokio project, with Turso Database.

[Toasty](https://github.com/tokio-rs/toasty) is an async ORM for Rust maintained by the [Tokio](https://tokio.rs/) project. It ships a native Turso driver (`toasty-driver-turso`) that connects to a local Turso Database file or an in-memory database — the same engine you get from the [`turso` crate](/sdk/rust/quickstart).

<Info>
  The Toasty Turso driver currently targets local and in-memory databases. For sync against Turso Cloud, drop down to the [`turso` crate directly](/sdk/rust/quickstart#sync-push-and-pull).
</Info>

## Prerequisites

* Rust 1.95 or newer (Toasty's minimum supported version)
* A Rust project (`cargo init`)
* Tokio as your async runtime

<Steps>
  <Step title="Install Toasty with the Turso driver">
    ```bash theme={null}
    cargo add toasty --features turso
    cargo add tokio --features full
    cargo add uuid
    ```
  </Step>

  <Step title="Define your models">
    Toasty derives schema, queries, and relations from plain Rust structs:

    ```rust models.rs theme={null}
    #[derive(Debug, toasty::Model)]
    struct User {
        #[key]
        #[auto]
        id: uuid::Uuid,

        name: String,

        #[unique]
        email: String,

        #[has_many]
        todos: toasty::Deferred<Vec<Todo>>,
    }

    #[derive(Debug, toasty::Model)]
    struct Todo {
        #[key]
        #[auto]
        id: uuid::Uuid,

        #[index]
        user_id: uuid::Uuid,

        #[belongs_to(key = user_id, references = id)]
        user: toasty::Deferred<User>,

        title: String,
    }
    ```
  </Step>

  <Step title="Connect to Turso">
    Pass a `turso:` URL to `Db::builder` — either an in-memory database or a file path:

    <AccordionGroup>
      <Accordion title="In-memory">
        ```rust theme={null}
        let db = toasty::Db::builder()
            .models(toasty::models!(crate::*))
            .connect("turso::memory:")
            .await?;
        ```
      </Accordion>

      <Accordion title="File-backed">
        ```rust theme={null}
        let db = toasty::Db::builder()
            .models(toasty::models!(crate::*))
            .connect("turso:./app.db")
            .await?;
        ```
      </Accordion>

      <Accordion title="With concurrent writes (MVCC)">
        To enable Turso's MVCC journal so multiple writers can run concurrently, build the driver explicitly and pass it to `build()`:

        ```rust theme={null}
        use toasty_driver_turso::Turso;

        let driver = Turso::file("app.db").concurrent_writes();

        let db = toasty::Db::builder()
            .models(toasty::models!(crate::*))
            .build(driver)
            .await?;
        ```

        When `concurrent_writes()` is enabled, write-write conflicts surface as a serialization failure — check `err.is_serialization_failure()` and retry the transaction.
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Create the schema and query">
    ```rust theme={null}
    #[tokio::main]
    async fn main() -> toasty::Result<()> {
        let mut db = toasty::Db::builder()
            .models(toasty::models!(crate::*))
            .connect("turso:./app.db")
            .await?;

        db.push_schema().await?;

        let user = toasty::create!(User {
            name: "Ada Lovelace",
            email: "ada@example.com",
        })
        .exec(&mut db)
        .await?;

        toasty::create!(in user.todos() { title: "ship Toasty + Turso" })
            .exec(&mut db)
            .await?;

        let reloaded = User::get_by_email(&mut db, &user.email).await?;
        println!("{reloaded:#?}");

        Ok(())
    }
    ```
  </Step>
</Steps>

## Where to go next

* The [Toasty guide](https://tokio-rs.github.io/toasty/) covers models, relations, transactions, and migrations.
* The [Turso chapter](https://tokio-rs.github.io/toasty/nightly/guide/turso.html) of the Toasty guide documents the connection URL forms, the `concurrent_writes()` flag, and the `experimental_*` toggles for Turso's engine features.
* The [`turso` crate quickstart](/sdk/rust/quickstart) shows how to use Turso Database directly without an ORM, including sync against Turso Cloud.


# Turso Quickstart (Rust)
Source: https://docs.turso.tech/sdk/rust/quickstart

Get started with Turso and Rust in a few simple steps.

In this Rust quickstart we will learn how to:

* Install the Turso crate
* Connect to a local or remote database
* Execute a query using SQL
* Sync changes to the cloud

## Recommended: turso (Local + Cloud Sync)

`turso` is the recommended crate for running a local database, including synchronizing it to and from Turso Cloud. It is built on the Turso Database engine — a ground-up rewrite of SQLite with concurrent writes (MVCC), async I/O, and native Rust async/await support.

<Steps>
  <Step title="Install">
    ```bash theme={null}
    cargo add turso tokio --features tokio/full
    ```
  </Step>

  <Step title="Connect">
    ```rust theme={null}
    use turso::Builder;

    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let db = Builder::new_local("app.db").build().await?;
        let conn = db.connect()?;

        conn.execute(
            "CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL
            )",
            (),
        ).await?;

        conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)).await?;

        let mut rows = conn.query("SELECT * FROM users", ()).await?;
        while let Some(row) = rows.next().await? {
            let id: i64 = row.get(0)?;
            let name: String = row.get(1)?;
            println!("User: {} {}", id, name);
        }

        Ok(())
    }
    ```
  </Step>

  <Step title="Sync (push and pull)">
    If you need to sync your local database with Turso Cloud, enable the `sync` feature:

    ```bash theme={null}
    cargo add turso --features sync
    ```

    ```rust theme={null}
    use turso::sync::Builder;

    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let db = Builder::new_remote("app.db")
            .with_remote_url(&std::env::var("TURSO_DATABASE_URL")?)
            .with_auth_token(&std::env::var("TURSO_AUTH_TOKEN")?)
            .build()
            .await?;

        let conn = db.connect().await?;

        conn.execute("INSERT INTO users (name) VALUES (?)", ("Bob",)).await?;

        // Push local writes to Turso Cloud
        db.push().await?;

        // Pull remote changes to local database
        db.pull().await?;

        Ok(())
    }
    ```

    All reads and writes happen against the local database file — fast, offline-capable. `push()` sends your changes to the cloud. `pull()` brings remote changes down. See the [reference](/sdk/rust/reference) for checkpoint, stats, and encryption. See [Turso Sync](/sync/usage) for details on conflict resolution and more.

    <Info>
      You can test sync locally without a Turso Cloud account by starting a local sync server:

      ```bash theme={null}
      tursodb :memory: --sync-server 127.0.0.1:8080
      ```

      Then use `http://127.0.0.1:8080` as the remote URL (no auth token needed). See [Turso Database quickstart](/tursodb/quickstart) for how to install `tursodb`.
    </Info>
  </Step>
</Steps>

## Remote Access (Over-the-Wire)

If your application needs to query a Turso Cloud database directly over the network (e.g., from a web server or serverless function), use the crate that matches your database engine: `turso_serverless` for [Turso databases](/tursodb/quickstart), or `libsql` (with the `remote` feature) for [libSQL](/libsql) databases.

<Info>
  For most applications, we recommend running a local database with sync (`turso::sync`) instead — it gives you faster reads, offline support, and lower latency. Remote access is useful when you cannot store a local database file (e.g., stateless serverless environments).
</Info>

### Remote Turso database: turso\_serverless

`turso_serverless` connects to a remote Turso database over HTTP — no persistent connections, no C compiler required. Its API mirrors the embedded `turso` crate, so code moves between local and remote with minimal changes.

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing Turso database to continue. If you don't have one, create one with `turso db create --tursodb` (see the [quickstart](/quickstart)).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Install">
    ```bash theme={null}
    cargo add turso_serverless tokio --features tokio/full
    ```
  </Step>

  <Step title="Connect and query">
    ```rust theme={null}
    use turso_serverless::Builder;

    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let db = Builder::new_remote(std::env::var("TURSO_DATABASE_URL")?)
            .with_auth_token(std::env::var("TURSO_AUTH_TOKEN")?)
            .build()
            .await?;
        let conn = db.connect()?;

        let mut rows = conn.query("SELECT * FROM users", ()).await?;
        while let Some(row) = rows.next().await? {
            let id: i64 = row.get(0)?;
            let name: String = row.get(1)?;
            println!("User: {} {}", id, name);
        }

        Ok(())
    }
    ```
  </Step>
</Steps>

### Remote libSQL database: libsql

The `libsql` crate with the `remote` feature connects to a remote libSQL database over HTTP — no local file needed, no C compiler required.

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing libSQL database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Install">
    ```bash theme={null}
    cargo add libsql --features remote
    ```
  </Step>

  <Step title="Connect and query">
    ```rust theme={null}
    use libsql::Builder;

    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let url = std::env::var("TURSO_DATABASE_URL")?;
        let token = std::env::var("TURSO_AUTH_TOKEN")?;

        let db = Builder::new_remote(url, token).build().await?;
        let conn = db.connect()?;

        let mut rows = conn.query("SELECT * FROM users", ()).await?;
        while let Some(row) = rows.next().await? {
            let id: i64 = row.get(0)?;
            let name: String = row.get(1)?;
            println!("User: {} {}", id, name);
        }

        Ok(())
    }
    ```
  </Step>
</Steps>

## Using an ORM

[Toasty](/sdk/rust/orm/toasty), the async ORM from the Tokio project, has a native Turso driver. It speaks to the `turso` crate directly, so you get a typed model layer (`#[derive(toasty::Model)]`, derived queries, relations) over the same Turso Database engine used in the quickstart above.

```bash theme={null}
cargo add toasty --features turso
```

See the [Toasty + Turso guide](/sdk/rust/orm/toasty) for the full walkthrough.

## Embedded Replicas (libsql)

<Info>
  Embedded Replicas give your Rust app a local read copy of a Turso Cloud database. Reads are served locally; writes go to the cloud primary and are reflected back to the replica. Embedded Replicas are fully supported in production.

  For new projects that need sync, we recommend the `turso` crate with `turso::sync`: both reads and writes are local, you sync explicitly with `push()` / `pull()`, and the wire format is logical change-data-capture rather than page frames ([benchmark](https://turso.tech/blog/sync-benchmark)).
</Info>

See the [reference](/sdk/rust/reference) for full documentation on Embedded Replicas with `libsql`.


# Reference
Source: https://docs.turso.tech/sdk/rust/reference

Rust Reference for Turso

Turso offers three Rust crates:

|                       | `turso`                         | `turso_serverless`                    | `libsql`                                                  |
| --------------------- | ------------------------------- | ------------------------------------- | --------------------------------------------------------- |
| **Use case**          | Local / embedded database, sync | Remote Turso database (over-the-wire) | Remote libSQL database, existing libSQL codebases         |
| **Engine**            | Turso (rewrite)                 | Turso (rewrite)                       | libSQL (SQLite fork)                                      |
| **Concurrent writes** | Yes (MVCC)                      | Yes (MVCC)                            | Not supported                                             |
| **Sync**              | push/pull (local-first)         | —                                     | Embedded Replicas (writes go to cloud primary)            |
| **C compiler**        | Not required                    | Not required                          | Required for `core`, `replication`, `encryption` features |

**Starting a new project?** Use `turso` for local/embedded use or sync. For remote access, match the crate to the database engine: [`turso_serverless`](/sdk/rust/quickstart) for Turso databases, `libsql` with the `remote` feature for libSQL databases (no C compiler needed).

## turso

For local and embedded use. Built on the Turso Database engine with concurrent writes (MVCC) and async I/O.

### Installing

```bash theme={null}
cargo add turso tokio --features tokio/full
```

### Connecting

```rust theme={null}
use turso::Builder;

let db = Builder::new_local("app.db").build().await?;
let conn = db.connect()?;
```

In-memory databases are also supported:

```rust theme={null}
let db = Builder::new_local(":memory:").build().await?;
```

### Querying

```rust theme={null}
conn.execute(
    "CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL
    )",
    (),
).await?;

conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)).await?;

let mut rows = conn.query("SELECT * FROM users", ()).await?;
while let Some(row) = rows.next().await? {
    let id: i64 = row.get(0)?;
    let name: String = row.get(1)?;
    println!("User: {} {}", id, name);
}
```

### Prepared Statements

```rust theme={null}
let mut stmt = conn.prepare("SELECT * FROM users WHERE id = ?1").await?;
let mut rows = stmt.query([42]).await?;
```

### Transactions

```rust theme={null}
let tx = conn.transaction().await?;

tx.execute("INSERT INTO users (name) VALUES (?1)", ["Alice"]).await?;
tx.execute("INSERT INTO users (name) VALUES (?1)", ["Bob"]).await?;

tx.commit().await?;
```

### Encryption

Encrypt local databases at rest:

```rust theme={null}
use turso::{Builder, EncryptionOpts};

let db = Builder::new_local("encrypted.db")
    .experimental_encryption(true)
    .with_encryption(EncryptionOpts {
        cipher: "aegis256".to_string(),
        hexkey: "b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327".to_string(),
    })
    .build()
    .await?;
```

Supported ciphers: `aegis256`, `aegis256x2`, `aegis128l`, `aegis128x2`, `aegis128x4`, `aes256gcm`, `aes128gcm`.

Encrypted databases cannot be read as standard SQLite databases — you must use the Turso Database engine to open them.

<Info>
  Turso Cloud databases can also be encrypted with bring-your-own-key — [learn more](/cloud/encryption).
</Info>

### Sync (Push and Pull)

For local database with cloud sync. All reads and writes happen locally; use `push()` to send changes to the cloud and `pull()` to fetch remote changes.

Enable the `sync` feature:

```bash theme={null}
cargo add turso --features sync
```

```rust theme={null}
use turso::sync::Builder;

let db = Builder::new_remote("app.db")
    .with_remote_url(&std::env::var("TURSO_DATABASE_URL")?)
    .with_auth_token(&std::env::var("TURSO_AUTH_TOKEN")?)
    .bootstrap_if_empty(true)  // Download schema on first sync (default)
    .build()
    .await?;

let conn = db.connect().await?;
```

On the first run, the local database is automatically bootstrapped from the remote. See [Turso Sync](/sync/usage) for full details.

#### Push and Pull

```rust theme={null}
// Push local writes to Turso Cloud
db.push().await?;

// Pull remote changes (returns true if changes were applied)
let changed = db.pull().await?;
```

#### Checkpoint

Compact the local WAL to bound disk usage while preserving sync state:

```rust theme={null}
db.checkpoint().await?;
```

#### Stats

```rust theme={null}
let stats = db.stats().await?;
println!("Network received: {} bytes", stats.network_received_bytes);
println!("Network sent: {} bytes", stats.network_sent_bytes);
println!("WAL size: {} bytes", stats.main_wal_size);
```

## libsql (Remote)

Use the `libsql` crate with the `remote` feature for over-the-wire access to Turso Cloud. This uses pure Rust HTTP — no C compiler needed.

### Installing

```bash theme={null}
cargo add libsql --features remote
```

### Connecting

```rust theme={null}
use libsql::Builder;

let url = std::env::var("TURSO_DATABASE_URL")?;
let token = std::env::var("TURSO_AUTH_TOKEN")?;

let db = Builder::new_remote(url, token).build().await?;
let conn = db.connect()?;
```

### Querying

```rust theme={null}
let mut rows = conn.query("SELECT * FROM users WHERE id = ?1", [1]).await?;
```

## libsql (libSQL)

The `libsql` crate is built on [libSQL](https://github.com/tursodatabase/libsql), the open-source fork of SQLite that powers Turso Cloud today. It is production-ready and battle-tested, and is the right choice when you are working with an existing `libsql`-based codebase.

<Info>
  With `libsql` Embedded Replicas, reads are local and writes are sent to the cloud primary, then reflected back to the replica. Embedded Replicas are fully supported. For new projects that need sync, we recommend the `turso` crate with `turso::sync` — see the [quickstart](/sdk/rust/quickstart).
</Info>

### Embedded Replicas

You can work with [Embedded Replicas](/features/embedded-replicas) that sync from a Turso Cloud database to a local SQLite file. Reads run locally; writes are sent to the cloud primary and then reflected back to the replica:

```rust theme={null}
use libsql::Builder;

let url = std::env::var("TURSO_DATABASE_URL")?;
let token = std::env::var("TURSO_AUTH_TOKEN")?;

let db = Builder::new_remote_replica("local.db", url, token)
    .build()
    .await?;
let conn = db.connect()?;
```

<Snippet />

#### Manual Sync

```rust theme={null}
db.sync().await?;
```

#### Sync Interval

```rust theme={null}
use std::time::Duration;

let db = Builder::new_remote_replica("local.db", url, token)
    .sync_interval(Duration::from_secs(300))
    .build()
    .await?;
```

#### Read Your Own Writes

By default, after a `push()`, the next `pull()` waits for the server to fully catch up with your changes before returning — guaranteeing you always read your own writes. This is safer but **much slower**, since the server must process all pending changes. If you can tolerate eventually-consistent reads, disable this for significantly faster pulls:

```rust theme={null}
let db = Builder::new_remote_replica("local.db", url, token)
    .read_your_writes(false)
    .build()
    .await?;
```

### Encryption

<Warning>
  For new projects, we recommend the [`turso`](#turso) crate for local encryption — it is built on the Turso Database engine with better performance and concurrent write support.
</Warning>

To enable encryption on a SQLite file, pass the encryption key value as an argument to the constructor:

<Snippet />

<Info>
  Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — [learn more](/libsql#encryption-at-rest).
</Info>

### Conditional compilation

The `libsql` crate supports [conditionally compiling](https://doc.rust-lang.org/cargo/reference/features.html#dependency-features) features:

| Feature       | Description                                                           |
| ------------- | --------------------------------------------------------------------- |
| `remote`      | HTTP-only client, pure Rust. No C compiler needed.                    |
| `core`        | Local database only. Requires a C compiler.                           |
| `replication` | Combines `core` with embedded replica support. Requires a C compiler. |
| `encryption`  | Encryption at rest. Requires `cmake`. Not enabled by default.         |

### Simple query

```rust theme={null}
conn.execute("SELECT * FROM users", ()).await?;
conn.execute("SELECT * FROM users WHERE id = ?1", [1]).await?;
```

### Placeholders

<CodeGroup>
  ```rust Positional theme={null}
  conn.execute("SELECT * FROM users WHERE id = ?1", libsql::params![1]).await?;
  ```

  ```rust Named theme={null}
  conn.execute("INSERT INTO users (name) VALUES (:name)", libsql::named_params! { ":name": "Iku" }).await?;
  ```
</CodeGroup>

### Deserialization

```rust theme={null}
use libsql::{de, Builder};

#[derive(Debug, serde::Deserialize)]
struct User {
    name: String,
    age: i64,
}

let mut stmt = conn.prepare("SELECT * FROM users WHERE id = ?1").await?;
let row = stmt.query([1]).await?.next().await?.unwrap();
let user = de::from_row::<User>(&row)?;
```

### Batch Transactions

```rust theme={null}
conn.execute_batch(r#"
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
  );

  INSERT INTO users (name) VALUES ('Alice');
  INSERT INTO users (name) VALUES ('Bob');
"#).await?;
```

### Interactive Transactions

<CodeGroup>
  ```rust Default theme={null}
  let tx = conn.transaction().await?;

  tx.execute("INSERT INTO users (name) VALUES (?1)", ["Alice"]).await?;
  tx.execute("INSERT INTO users (name) VALUES (?1)", ["Bob"]).await?;

  tx.commit().await?;
  ```

  ```rust Advanced control theme={null}
  use libsql::TransactionBehavior;

  let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).await?;

  tx.execute("UPDATE users SET age = age + 1 WHERE id = ?1", [1]).await?;

  tx.commit().await?;
  ```
</CodeGroup>


# Turso Quickstart (Swift)
Source: https://docs.turso.tech/sdk/swift/quickstart

Get started with Turso and Swift using the libSQL client in a few simple steps.

<Snippet />

In this Swift quickstart we will learn how to:

* Retrieve database credentials
* Install the libSQL package
* Connect to a local or remote Turso database
* Execute a query using SQL
* Sync changes to local database (optional)

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing database to continue. If you don't have one, [create one](/quickstart).

    <Snippet />

    <Snippet />
  </Step>

  <Step title="Install">
    First begin by adding `libsql` as a package dependency in XCode using this repo:

    <Card title="libSQL Swift" href="https://github.com/tursodatabase/libsql-swift">
      Build from source code
    </Card>

    Or add it to your SwiftPM dependencies:

    ```swift theme={null}
    import PackageDescription

    let package = Package(
        // ...
        dependencies: [
            .package(url: "https://github.com/tursodatabase/libsql-swift", from: "0.1.1"),
        ],
        // ...
    )
    ```
  </Step>

  <Step title="Connect">
    You must first create a `Database` object and then open a `Connection` to it:

    <AccordionGroup>
      <Accordion title="Embedded Replicas">
        ```swift theme={null}
        import Libsql

        let db = try Database(
            path: "./local.db",
            url: "TURSO_DATABASE_URL",
            authToken: "TURSO_AUTH_TOKEN",
            syncInterval: 300
        )

        let conn = try db.connect()
        ```
      </Accordion>

      <Accordion title="Local only">
        ```swift theme={null}
        import Libsql

        let db = try Database(
            path: "./local.db",
        )

        let conn = try db.connect()
        ```
      </Accordion>

      <Accordion title="Remote only">
        ```swift theme={null}
        import Libsql

        let db = try Database(
            url: "TURSO_DATABASE_URL",
            authToken: "TURSO_AUTH_TOKEN"
        )

        let conn = try db.connect()
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Execute">
    You can execute a SQL query against your existing database by calling `query()`,
    or `execute()` when you expect the query to not yield any rows:

    ```swift theme={null}
    let rows = try conn.query("SELECT * FROM users")
    ```

    If you need to use placeholders for values, you can do that:

    <CodeGroup>
      ```swift Positional Query theme={null}
      try conn.query("SELECT * FROM users WHERE id = ?", 1)
      ```

      ```swift Positional Insert theme={null}
      try conn.execute("INSERT INTO users (id) VALUES (?)", 1)
      ```

      <Note>Named arguments are not supported yet.</Note>
    </CodeGroup>
  </Step>

  <Step title="Sync (Embedded Replicas only)">
    When using embedded replicas you should call `sync()` on the database type to
    sync your local database with the primary database, unless you are using
    `sync_interval` (though there is no issue with calling `sync` with
    `sync_interval` enabled):

    ```swift theme={null}
    try db.sync()
    ```
  </Step>
</Steps>


# Swift Reference
Source: https://docs.turso.tech/sdk/swift/reference

libSQL Swift Reference

<Snippet />

## Installing

First begin by adding `libsql` as a package dependency in XCode using this repo:

<Card title="libSQL Swift" href="https://github.com/tursodatabase/libsql-swift">
  Build from source code
</Card>

Or add it to your SwiftPM dependencies:

```swift theme={null}
import PackageDescription

let package = Package(
    // ...
    dependencies: [
        .package(url: "https://github.com/tursodatabase/libsql-swift", from: "0.1.1"),
    ],
    // ...
)
```

## In-Memory Databases

libSQL supports connecting to [in-memory
databases](https://www.sqlite.org/inmemorydb.html) for cases where you don't
require persistence:

```swift theme={null}
import Libsql

let db = Database(":memory:")
let conn = try db.connect()
```

## Local Development

You can work locally using an SQLite file:

```swift theme={null}
import Libsql

let db = Database("local.db")
let conn = try db.connect()
```

## Embedded Replicas

You can work with embedded replicas that can sync from the remote URL and
delegate writes to the remote primary database:

```swift theme={null}
import Libsql

let db = try Database(
    path: "./local.db",
    url: "TURSO_DATABASE_URL",
    authToken: "TURSO_AUTH_TOKEN"
)

let conn = try db.connect()
```

### Manual Sync

The `sync` function allows you to sync manually the local database with the remote counterpart:

```swift theme={null}
try db.sync() // Call sync manually to update local database
```

### Sync Interval

The `syncInterval` parameter allows you to set an interval for automatic
synchronization of the database in the background:

```swift theme={null}
import Libsql

let db = try Database(
    path: "./local.db",
    url: "TURSO_DATABASE_URL",
    authToken: "TURSO_AUTH_TOKEN"
    syncInterval: 300 // Sync every 3 seconds
)

let conn = try db.connect()
```

### Read Your Own Writes

The `readYourWrites` parameter configures the database connection to ensure
that writes are immediately visible to subsequent read operations initiated by
the same connection. This is **enabled by default**, and is particularly
important in distributed systems to ensure consistency from the perspective of
the writing process.

You can disable this behavior by passing `false` to the function:

```swift theme={null}
import Libsql

let db = try Database(
    path: "./local.db",
    url: <LIBSQL_URL>,
    authToken: <LIBSQL_AUTH_TOKEN>,
    readYourWrites: false
)

let conn = try db.connect()
```

## Simple query

You can pass a string to `query()` to invoke a SQL statement, as well as
optional arguments:

<CodeGroup>
  ```swift Execute theme={null}
  try conn.execute("INSERT INTO users VALUES (?)", [1])
  ```

  ```swift Query theme={null}
  try conn.query("SELECT * FROM users")
  ```

  ```swift Arguments theme={null}
  try conn.query("SELECT * FROM users WHERE id = ?", [1])
  ```
</CodeGroup>

## Prepared Statements

You can prepare a cached statement using `prepare()` and then execute it with
`query()`:

```swift theme={null}
let stmt = try conn.prepare("SELECT * FROM users WHERE id = ?")
stmt.bind([1])
stmt.query()
```

## Placeholders

libSQL supports the use of positional and named placeholders within SQL statements:

<CodeGroup>
  ```swift Positional theme={null}
  try conn.query("SELECT * FROM users WHERE id = ?", [1])
  ```

  ```swift Named theme={null}
  try conn.query("SELECT * FROM users WHERE id = :id", [ ":id": 1 ])
  ```
</CodeGroup>


# Examples
Source: https://docs.turso.tech/sdk/ts/examples





# Astro + Turso
Source: https://docs.turso.tech/sdk/ts/guides/astro

Set up Turso in your Astro project in minutes.

<img alt="Astro banner" />

## Prerequisites

To get the most out of this guide, you'll need to:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have an Astro project — [learn more](https://docs.astro.build/en/install/auto/#1-run-the-setup-wizard)

<Steps>
  <Step title="Install the libSQL SDK">
    <Snippet />
  </Step>

  <Step title="Configure database credentials">
    <Snippet />
  </Step>

  <Step title="Configure libSQL client">
    ```ts src/turso.ts theme={null}
    import { createClient } from "@libsql/client/web";

    export const turso = createClient({
      url: import.meta.env.TURSO_DATABASE_URL!,
      authToken: import.meta.env.TURSO_AUTH_TOKEN,
    });
    ```

    <Note>
      Astro will soon introduce a new ENV API. [Take a
      look](https://docs.astro.build/en/reference/configuration-reference/#experimentalenv).
    </Note>
  </Step>

  <Step title="Execute SQL">
    ```ts theme={null}
    ---
    import { turso } from './turso'

    const { rows } = await turso.execute('SELECT * FROM table_name')
    ---
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Blog" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-tustro-blog">
    See the full source code
  </Card>
</CardGroup>


# Elysia + Turso
Source: https://docs.turso.tech/sdk/ts/guides/elysia

Set up Turso in your Elysia project in minutes.

<img alt="Elysia banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have an Elysia app — [learn more](https://elysiajs.com/quick-start.html)

<Steps>
  <Step title="Install the libSQL SDK">
    <Snippet />
  </Step>

  <Step title="Retrieve database credentials">
    <Snippet />
  </Step>

  <Step title="Configure libSQL client">
    <Snippet />
  </Step>

  <Step title="Execute SQL">
    ```ts theme={null}
    import { Elysia } from "elysia";
    import { turso } from "./lib/turso";

    const app = new Elysia().get("/items", async () => {
      const { rows } = await turso.execute("SELECT * FROM items");
      return rows;
    });
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Expenses tracker app with Elysia & Turso" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-expenses-tracker-elysia">
    See the full source code
  </Card>
</CardGroup>


# Hono + Turso
Source: https://docs.turso.tech/sdk/ts/guides/hono

Set up Turso in your Hono project in minutes.

<img alt="Hono banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a Hono app — [learn more](https://hono.dev/top#quick-start)

<Steps>
  <Step title="Install the libSQL SDK">
    <Snippet />
  </Step>

  <Step title="Retrieve database credentials">
    <Snippet />
  </Step>

  <Step title="Configure libSQL client">
    <Snippet />
  </Step>

  <Step title="Execute SQL">
    ```ts theme={null}
    import { Hono } from "hono";
    import { turso } from "./lib/turso";

    const app = new Hono();

    app.get("/items", async (c) => {
      const { rows } = await turso.execute("SELECT * FROM items");

      return c.json({ rows });
    });
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Expenses tracker app with Hono & Turso" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-expenses-tracker-hono">
    See the full source code
  </Card>
</CardGroup>


# Next.js + Turso
Source: https://docs.turso.tech/sdk/ts/guides/nextjs

Set up Turso in your Next.js project in minutes.

<img alt="Next.js banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a Next.js app — [learn more](https://nextjs.org/docs/getting-started/installation)

<Steps>
  <Step title="Install the libSQL SDK">
    <Snippet />
  </Step>

  <Step title="Configure database credentials">
    <Snippet />
  </Step>

  <Step title="Configure libSQL client">
    <Snippet />
  </Step>

  <Step title="Execute SQL">
    <CodeGroup>
      ```tsx App Router theme={null}
      import { turso } from "@/lib/turso";

      export default async function Page() {
        const { rows } = await turso.execute("SELECT * FROM table_name");

        return (
          <ul>
            {rows.map((row) => (
              <li key={row.id}>{row.id}</li>
            ))}
          </ul>
        );
      }
      ```

      ```ts Pages Directory theme={null}
      import type { InferGetServerSidePropsType, GetServerSideProps } from "next";

      import { turso } from "@/lib/turso";

      export const getServerSideProps = (async () => {
        const { rows } = await turso.execute("SELECT * FROM table_name");

        return {
          props: {
            rows,
          },
        };
      }) satisfies GetServerSideProps<{ rows: any[] }>;

      export default function Page({
        rows,
      }: InferGetServerSidePropsType<typeof getServerSideProps>) {
        return (
          <ul>
            {rows.map((row) => (
              <li key={row.id}>{row.id}</li>
            ))}
          </ul>
        );
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Full Stack App" icon="github" href="https://github.com/tursodatabase/nextjs-turso-starter">
    Build with Next.js, Turso, and Drizzle ORM.
  </Card>
</CardGroup>


# Nuxt + Turso
Source: https://docs.turso.tech/sdk/ts/guides/nuxt

Set up Turso in your Nuxt project in minutes

<img alt="Nuxt banner" />

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a Nuxt app — [learn more](https://nuxt.com/docs/getting-started/installation#new-project)

<Steps>
  <Step title="Install the libSQL SDK">
    <Snippet />
  </Step>

  <Step title="Configure database credentials">
    Get the database URL:

    ```bash theme={null}
    turso db show --url <database-name>
    ```

    Get the database authentication token:

    ```bash theme={null}
    turso db tokens create <database-name>
    ```

    Assign credentials to the environment variables inside `.env`.

    ```bash theme={null}
    NUXT_TURSO_DATABASE_URL=
    NUXT_TURSO_AUTH_TOKEN=
    ```
  </Step>

  <Step title="Configure variables inside Nuxt's runtime config.">
    ```ts nuxt.config.ts theme={null}
    export default defineNuxtConfig({
      runtimeConfig: {
        turso: {
          databaseUrl: "",
          authToken: "",
        },
      },
    });
    ```

    <Note>
      Make sure that names of the keys in the `runtimeConfig` object match the names
      of your environment variables. Read more about this
      [here](https://nuxt.com/docs/guide/going-further/runtime-config).
    </Note>
  </Step>

  <Step title="Configure libSQL Client.">
    ```ts server/utils/turso.ts theme={null}
    import { createClient } from "@libsql/client";
    // You can optionally pass in the event to useRuntimeConfig
    // import { H3Event } from "h3";

    export function useTurso(/* event: H3Event */) {
      const { turso } = useRuntimeConfig(/* event */);

      return createClient({
        url: turso.databaseUrl,
        authToken: turso.authToken,
      });
    }
    ```
  </Step>

  <Step title="Execute SQL">
    ```ts server/api/items.get.ts theme={null}
    export default defineEventHandler(async (event) => {
      const client = useTurso(/* event */);
      const { rows } = await client.execute("select * from table_name");

      return {
        data: {
          items: rows,
        },
      };
    });
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Website + App" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-top-web-frameworks">
    See the full source code
  </Card>
</CardGroup>


# Quasar + Turso
Source: https://docs.turso.tech/sdk/ts/guides/quasar

Set up Turso in your Quasar project in minutes

<img alt="Quasar banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a Quasar app — [learn more](https://quasar.dev/start/quick-start#step-1-create-a-project)

<Steps>
  <Step title="Install the libSQL SDK">
    <Snippet />
  </Step>

  <Step title="Configure database credentials">
    Get the database URL:

    ```bash theme={null}
    turso db show --url <database-name>
    ```

    Get the database authentication token:

    ```bash theme={null}
    turso db tokens create <database-name>
    ```

    Assign credentials to the environment variables inside `.env.local`.

    ```bash theme={null}
    VITE_TURSO_DATABASE_URL="..."
    VITE_TURSO_AUTH_TOKEN="..."
    ```
  </Step>

  <Step title="Configure libSQL Client">
    ```js theme={null}
    import { createClient } from "@libsql/client/web";

    const turso = createClient({
      url: import.meta.env.VITE_TURSO_DATABASE_URL,
      authToken: import.meta.env.VITE_TURSO_AUTH_TOKEN,
    });
    ```

    <Info>
      Avoid a [gotcha
      moment](https://github.com/quasarframework/quasar/discussions/16071) by
      modifying the app configuration using the settings below.
    </Info>

    ```javascript quasar.config.js theme={null}
    {
      build: {
        target: {
          browser: [
            'es2020', 'edge88', 'firefox78', 'chrome87', 'safari13.1'
          ],
          node: 'node16'
        },
        extendViteConf(config) {
          config.optimizeDeps = {
            esbuildOptions: {
              target: 'es2020',
            }
          }
        }
      }
    }
    ```
  </Step>

  <Step title="Fetch data from Turso.">
    ```js IndexPage.vue theme={null}
    import { ref } from "vue";

    const items = ref();

    const { rows } = await turso.execute("select * from my-table");

    items.value = rows;
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Todo App" icon="github" href="https://github.com/tursodatabase/examples/tree/master/quasar-todo-list">
    See the full source code
  </Card>
</CardGroup>


# Qwik + Turso
Source: https://docs.turso.tech/sdk/ts/guides/qwik

Set up Turso in your Qwik project in minutes

<img alt="Qwik banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a Qwik app — [learn more](https://qwik.builder.io/docs/getting-started/#create-an-app-using-the-cli)

<Steps>
  <Step title="Add Turso Integration">
    <CodeGroup>
      ```bash npm theme={null}
      npm run qwik add turso
      ```

      ```bash pnpm theme={null}
      pnpm qwik add turso
      ```

      ```bash yarn theme={null}
      yarn qwik add turso
      ```
    </CodeGroup>
  </Step>

  <Step title="Configure database credentials">
    Get the database URL:

    ```bash theme={null}
    turso db show --url <database-name>
    ```

    Get the database authentication token:

    ```bash theme={null}
    turso db tokens create <database-name>
    ```

    Assign credentials to the environment variables inside `.env.local`.

    ```bash theme={null}
    PRIVATE_TURSO_DATABASE_URL="..."
    PRIVATE_TURSO_AUTH_TOKEN="..."
    ```
  </Step>

  <Step title="Execute SQL">
    ```ts theme={null}
    import { tursoClient } from "~/utils/turso";

    export const useFrameworks = routeLoader$(
      async (requestEvent: RequestEventBase) => {
        const db = tursoClient(requestEvent["env"]);
        const { rows } = await db.execute("select * from table_name");

        return {
          items: rows,
        };
      },
    );
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Social Website" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-find-me-on">
    See the full source code
  </Card>

  <Card title="Shopping Cart" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-turqw-store">
    See the full source code
  </Card>
</CardGroup>


# Remix + Turso
Source: https://docs.turso.tech/sdk/ts/guides/remix

Set up Turso in your Remix project in minutes

<img alt="Remix banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a Remix app — [learn more](https://remix.run/docs/en/main/start/quickstart#quick-start)

<Steps>
  <Step title="Install the libSQL SDK">
    <Snippet />
  </Step>

  <Step title="Configure database credentials">
    <Snippet />
  </Step>

  <Step title="Configure libSQL Client.">
    <Snippet />
  </Step>

  <Step title="Execute SQL">
    ```ts app/routes/_index.ts theme={null}
    import type { LoaderFunction } from "@remix-run/node";

    import { turso } from "~/lib/turso";

    export const loader: LoaderFunction = async () => {
      const { rows } = await turso.execute("SELECT * from TABLE_NAME");

      return {
        items: rows,
      };
    };
    ```
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="E-commerce Store" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-the-mug-store">
    See the full source code
  </Card>

  <Card title="CRM App" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-turso-crm">
    See the full source code
  </Card>
</CardGroup>


# SvelteKit + Turso
Source: https://docs.turso.tech/sdk/ts/guides/sveltekit

Set up Turso in your SvelteKit project in minutes

<img alt="SvelteKit banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Have a SvelteKit app — [learn more](https://kit.svelte.dev/docs/creating-a-project)

<Steps>
  <Step title="Install the libSQL SDK">
    <Snippet />
  </Step>

  <Step title="Configure database credentials">
    <Snippet />
  </Step>

  <Step title="Configure libSQL Client.">
    <CodeGroup>
      ```ts Node.js / Serverless theme={null}
      import { TURSO_DATABASE_URL, TURSO_AUTH_TOKEN } from "$env/static/private";
      import { createClient } from "@libsql/client";

      export const turso = createClient({
        url: TURSO_DATABASE_URL,
        authToken: TURSO_AUTH_TOKEN,
      });
      ```

      ```ts Edge Runtimes theme={null}
      import { TURSO_DATABASE_URL, TURSO_AUTH_TOKEN } from "$env/static/private";
      import { createClient } from "@libsql/client/web";

      export const turso = createClient({
        url: TURSO_DATABASE_URL,
        authToken: TURSO_AUTH_TOKEN,
      });
      ```
    </CodeGroup>
  </Step>

  <Step title="Execute SQL">
    <CodeGroup>
      ```ts src/routes/+page.server.ts theme={null}
      import { turso } from "$lib/turso.server";

      export async function load() {
        const { rows } = await turso.execute("SELECT * FROM table_name");

        return { rows };
      }
      ```

      ```svelte src/routes/+page.svelte theme={null}
      <script lang="ts">
        export let data
      </script>

      <ul>
        {#each data.rows as row}
          <li>{row.id}</li>
        {/each}
      </ul>
      ```
    </CodeGroup>
  </Step>
</Steps>

## Examples

<CardGroup>
  <Card title="Blog" icon="github" href="https://github.com/tursodatabase/examples/tree/master/app-at-the-polls">
    See the full source code
  </Card>
</CardGroup>


# Sentry
Source: https://docs.turso.tech/sdk/ts/integrations/sentry

Trace slow queries and capture SQL errors with Sentry.

The [Sentry](https://sentry.io/integrations/turso) integration for `@libsql/client` provides tracing, breadcrumbs, and error handling for SQL queries.

<Snippet />

## Install

```bash theme={null}
npm install sentry-integration-libsql-client
```

## Quickstart

Once you have a `@libsql/client` instance, you can integrate it with Sentry like this:

```ts theme={null}
import * as Sentry from "@sentry/node";
import { createClient } from "@libsql/client";
import { libsqlIntegration } from "sentry-integration-libsql-client";

const libsqlClient = createClient({
  url: "libsql://...",
  authToken: "...",
});

Sentry.init({
  dsn: "...",
  integrations: [libsqlIntegration(libsqlClient, Sentry)],
});

await libsqlClient.execute("SELECT * FROM users");
```

## Options

By default, everything is turned on. You can configure what you want by passing a third argument to the `libsqlIntegration` function.

| Option        | Default | Description                            |
| ------------- | ------- | -------------------------------------- |
| `tracing`     | `true`  | Enable tracing for SQL queries.        |
| `breadcrumbs` | `true`  | Enable breadcrumbs for SQL queries.    |
| `errors`      | `true`  | Enable error handling for SQL queries. |

```ts theme={null}
Sentry.init({
  dsn: "...",
  integrations: [
    libsqlIntegration(libsqlClient, Sentry, {
      tracing: false,
      breadcrumbs: false,
      errors: false,
    }),
  ],
});
```


# Drizzle + Turso
Source: https://docs.turso.tech/sdk/ts/orm/drizzle

Configure Drizzle to work with Turso

<img alt="Drizzle banner" />

<Info>
  Drizzle uses `@libsql/client` for Turso integration. There is also [beta support for `@tursodatabase/database`](https://orm.drizzle.team/docs/connect-turso-database) for local/embedded use cases.
</Info>

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)

<Steps>
  <Step title="Install Drizzle and the libSQL SDK">
    <CodeGroup>
      ```bash npm theme={null}
      npm i drizzle-orm @libsql/client dotenv
      npm i -D drizzle-kit
      ```

      ```bash pnpm theme={null}
      pnpm add drizzle-orm @libsql/client dotenv
      pnpm add -D drizzle-kit
      ```

      ```bash yarn theme={null}
      yarn add drizzle-orm @libsql/client dotenv
      yarn add -D drizzle-kit
      ```
    </CodeGroup>

    Finish by updating `package.json` to include three new `scripts`:

    ```json theme={null}
    {
      "scripts": {
        "db:generate": "drizzle-kit generate",
        "db:migrate": "drizzle-kit migrate",
        "db:studio": "drizzle-kit studio"
      }
    }
    ```
  </Step>

  <Step title="Retrieve database credentials">
    <Snippet />
  </Step>

  <Step title="Create a Drizzle schema">
    ```ts db/schema.ts theme={null}
    import { sql } from "drizzle-orm";
    import { text, sqliteTable } from "drizzle-orm/sqlite-core";

    export const fooTable = sqliteTable("foo", {
      bar: text("bar").notNull().default("Hey!"),
    });
    ```
  </Step>

  <Step title="Configure Drizzle Kit">
    Create the file `drizzle.config.ts` in the root of your project with the following:

    ```ts drizzle.config.ts theme={null}
    require("dotenv").config();

    import type { Config } from "drizzle-kit";

    export default {
      schema: "./db/schema.ts",
      out: "./migrations",
      dialect: "turso",
      dbCredentials: {
        url: process.env.TURSO_DATABASE_URL!,
        authToken: process.env.TURSO_AUTH_TOKEN,
      },
    } satisfies Config;
    ```

    <Note>
      We're using `dotenv` above, but if you're using something like Next.js, Remix, Astro, or Vite, you can use their built-in environment variables manager to source these values.
    </Note>
  </Step>

  <Step title="Connect Drizzle with libSQL">
    <CodeGroup>
      ```ts Node.js / Serverless theme={null}
      import { drizzle } from "drizzle-orm/libsql";
      import { createClient } from "@libsql/client";

      const turso = createClient({
        url: process.env.TURSO_DATABASE_URL!,
        authToken: process.env.TURSO_AUTH_TOKEN,
      });

      export const db = drizzle(turso);
      ```

      ```ts Edge Runtimes theme={null}
      import { drizzle } from "drizzle-orm/libsql";
      import { createClient } from "@libsql/client/web";

      const turso = createClient({
        url: process.env.TURSO_DATABASE_URL!,
        authToken: process.env.TURSO_AUTH_TOKEN,
      });

      export const db = drizzle(turso);
      ```
    </CodeGroup>
  </Step>

  <Step title="Database migrations">
    Drizzle can generate and apply database migrations with `drizzle-kit`.

    Whenever you make changes to the schema, run `db:generate`:

    ```bash theme={null}
    npm run db:generate
    ```

    Now apply these changes to the database with `db:migrate`:

    ```bash theme={null}
    npm run db:migrate
    ```
  </Step>

  <Step title="Query">
    ```ts theme={null}
    import { db } from "./db";
    import { fooTable } from "./schema";

    const result = await db.select().from(fooTable).all();
    ```
  </Step>

  <Step title="Connect Drizzle Studio">
    <CodeGroup>
      ```bash npm theme={null}
      npm run db:studio
      ```

      ```bash pnpm theme={null}
      pnpm run db:studio
      ```
    </CodeGroup>
  </Step>
</Steps>

## Vector Embeddings

You can extend Drizzle to support Turso's native vector — [learn more](/features/ai-and-embeddings).

<Steps>
  <Step title="Define custom vector type">
    Inside `db/schema.ts`, add the following:

    ```typescript theme={null}
    import { sql } from "drizzle-orm";
    import { customType } from "drizzle-orm/sqlite-core";

    const float32Array = customType<{
      data: number[];
      config: { dimensions: number };
      configRequired: true;
      driverData: Buffer;
    }>({
      dataType(config) {
        return `F32_BLOB(${config.dimensions})`;
      },
      fromDriver(value: Buffer) {
        return Array.from(new Float32Array(value.buffer));
      },
      toDriver(value: number[]) {
        return sql`vector32(${JSON.stringify(value)})`;
      },
    });
    ```
  </Step>

  <Step title="Create a table with a vector column">
    Now where you define the schema, invoke `float32Array` to create a column that stores vectors:

    ```typescript theme={null}
    import { sqliteTable, integer } from "drizzle-orm/sqlite-core";

    export const vectorTable = sqliteTable("vector_table", {
      id: integer("id").primaryKey(),
      vector: float32Array("vector", { dimensions: 3 }),
    });
    ```
  </Step>

  <Step title="Create a vector index">
    You will need to use raw SQL to create the index:

    ```typescript theme={null}
    import { drizzle } from "drizzle-orm/libsql";
    import { createClient } from "@libsql/client";

    const client = createClient({
      url: process.env.TURSO_DATABASE_URL!,
      authToken: process.env.TURSO_AUTH_TOKEN,
    });

    const db = drizzle(client);

    await db.run(sql`
      CREATE INDEX IF NOT EXISTS vector_index
      ON vector_table(vector)
      USING vector_cosine(3)
    `);
    ```
  </Step>

  <Step title="Insert vector data">
    ```typescript theme={null}
    await db
      .insert(vectorTable)
      .values([{ vector: sql`vector32(${JSON.stringify([1.1, 2.2, 3.3])})` }]);
    ```
  </Step>

  <Step title="Query vector data">
    Calculate vector distance:

    ```typescript theme={null}
    const res = await db
      .select({
        distance: sql<number>`vector_distance_cos(${vectorTable.vector}, vector32(${JSON.stringify([2.2, 3.3, 4.4])}))`,
      })
      .from(vectorTable);

    console.log(res);
    ```

    Perform efficient nearest neighbor search:

    ```typescript theme={null}
    const topK = await db
      .select({
        id: sql`id`,
        distance: sql`distance`,
      })
      .from(
        sql`vector_top_k('vector_index', vector32(${JSON.stringify([2.2, 3.3, 4.4])}), 5)`,
      )
      .leftJoin(vectorTable, sql`${vectorTable}.id = id`);

    console.log(topK);
    ```
  </Step>
</Steps>

Remember to create appropriate indexes for efficient vector operations and adjust vector dimensions as needed for your use case.


# Prisma + Turso
Source: https://docs.turso.tech/sdk/ts/orm/prisma

Configure Prisma to work with your Turso database

<img alt="Prisma banner" />

## Prerequisites

Before you start, make sure you:

* [Install the Turso CLI](/cli/installation)
* [Sign up or login to Turso](/cli/authentication#signup)
* Prisma versions 5.4.2 and later

<Steps>
  <Step title="Install the libSQL SDK and its Prisma driver">
    <CodeGroup>
      ```bash npm theme={null}
      npm install @libsql/client @prisma/adapter-libsql
      ```

      ```bash pnpm theme={null}
      pnpm add @libsql/client @prisma/adapter-libsql
      ```

      ```bash yarn theme={null}
      yarn add @libsql/client @prisma/adapter-libsql
      ```
    </CodeGroup>
  </Step>

  <Step title="Retrieve database credentials">
    <Snippet />
  </Step>

  <Step title="Enable the `driverAdapters` preview feature flag:">
    ```js prisma/schema.prisma theme={null}
    generator client {
      provider        = "prisma-client-js"
      previewFeatures = ["driverAdapters"]
    }

    datasource db {
      provider = "sqlite"
      url      = "file:./dev.db"
    }
    ```
  </Step>

  <Step title="Generate Prisma client">
    <CodeGroup>
      ```sh npm theme={null}
      npx prisma generate
      ```

      ```sh pnpm theme={null}
      pnpm dlx prisma generate
      ```
    </CodeGroup>
  </Step>

  <Step title="Update your Prisma Client Instance">
    <CodeGroup>
      ```ts Node.js / Serverless theme={null}
      import { PrismaClient } from "@prisma/client";
      import { PrismaLibSQL } from "@prisma/adapter-libsql";

      const adapter = new PrismaLibSQL({
        url: process.env.TURSO_DATABASE_URL,
        authToken: process.env.TURSO_AUTH_TOKEN,
      })
      const prisma = new PrismaClient({ adapter })
      ```

      ```ts Edge Runtimes theme={null}
      import { PrismaClient } from "@prisma/client";
      import { PrismaLibSQL } from "@prisma/adapter-libsql";

      const adapter = new PrismaLibSQL({
        url: process.env.TURSO_DATABASE_URL,
        authToken: process.env.TURSO_AUTH_TOKEN,
      })
      const prisma = new PrismaClient({ adapter })
      ```
    </CodeGroup>
  </Step>

  <Step title="Database Migrations">
    Prisma Migrate and Introspection workflows are currently not supported when working with Turso — [learn more](https://www.prisma.io/docs/orm/overview/databases/turso#how-to-manage-schema-changes).

    First, generate a migration file using prisma migrate dev against a local SQLite database

    <CodeGroup>
      ```sh npm theme={null}
      npx prisma migrate dev --name init
      ```

      ```sh pnpm theme={null}
      pnpm dlx prisma migrate dev --name init
      ```
    </CodeGroup>

    Then, apply the migration to your Turso database using the Turso's CLI

    ```bash theme={null}
    turso db shell turso-prisma-db < ./prisma/migrations/20230922132717_init/migration.sql
    ```

    <Info>
      Replace `20230922132717_init` with the name of your migration created from the `npx prisma migrate dev` command.
    </Info>
  </Step>

  <Step title="Query">
    ```ts theme={null}
    const response = await prisma.table_name.findMany();
    ```
  </Step>
</Steps>


# Turso Quickstart (TypeScript)
Source: https://docs.turso.tech/sdk/ts/quickstart

Get started with Turso and TypeScript in a few simple steps.

In this TypeScript quickstart we will learn how to:

* Install the Turso package
* Connect to a local or remote database
* Execute a query using SQL
* Sync changes to the cloud

## Recommended: @tursodatabase/database (Local / Embedded)

`@tursodatabase/database` is the recommended package for local and embedded use cases (Node.js, Electron, mobile, IoT). It is built on the Turso Database engine — a ground-up rewrite of SQLite with concurrent writes (MVCC) and async I/O.

<Steps>
  <Step title="Install">
    ```bash theme={null}
    npm install @tursodatabase/database
    ```
  </Step>

  <Step title="Connect">
    ```ts theme={null}
    import { connect } from "@tursodatabase/database";

    const db = await connect("app.db");
    ```
  </Step>

  <Step title="Execute">
    ```ts theme={null}
    const createTable = db.prepare(`
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT NOT NULL
      )
    `);
    await createTable.run();

    const insertUser = db.prepare("INSERT INTO users (username) VALUES (?)");
    await insertUser.run("alice");

    const stmt = db.prepare("SELECT * FROM users");
    const users = await stmt.all();
    console.log(users);
    ```
  </Step>

  <Step title="Sync (push and pull)">
    If you need to sync your local database with Turso Cloud, use [`@tursodatabase/sync`](/sync/usage):

    ```bash theme={null}
    npm install @tursodatabase/sync
    ```

    ```ts theme={null}
    import { connect } from "@tursodatabase/sync";

    const db = await connect({
      path: "./app.db",
      url: process.env.TURSO_DATABASE_URL,
      authToken: process.env.TURSO_AUTH_TOKEN,
    });

    await db.exec("INSERT INTO users (username) VALUES ('bob')");

    // Push local writes to Turso Cloud
    await db.push();

    // Pull remote changes to local database
    const changed = await db.pull();
    ```

    All reads and writes happen against the local database file — fast, offline-capable. `push()` sends your changes to the cloud. `pull()` brings remote changes down. See [Turso Sync](/sync/usage) for details on conflict resolution, checkpointing, and more.
  </Step>
</Steps>

## Remote Turso database: @tursodatabase/serverless

`@tursodatabase/serverless` is the package for applications that connect to a remote [Turso database](/tursodb/quickstart) on Turso Cloud over the network — including Node.js servers, Docker containers, serverless functions, and edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy). It uses only `fetch` — zero native dependencies, works everywhere.

<Steps>
  <Step title="Retrieve database credentials">
    You will need an existing Turso database to continue. If you don't have one, create one with `turso db create --tursodb` (see the [quickstart](/quickstart)).

    <Snippet />

    <Info>You will want to store these as environment variables.</Info>
  </Step>

  <Step title="Install">
    ```bash theme={null}
    npm install @tursodatabase/serverless
    ```
  </Step>

  <Step title="Connect to your database">
    ```ts theme={null}
    import { connect } from "@tursodatabase/serverless";

    const conn = connect({
      url: process.env.TURSO_DATABASE_URL,
      authToken: process.env.TURSO_AUTH_TOKEN,
    });
    ```

    For compatibility with the `@libsql/client` API, use the compat module:

    ```ts theme={null}
    import { createClient } from "@tursodatabase/serverless/compat";

    const client = createClient({
      url: process.env.TURSO_DATABASE_URL,
      authToken: process.env.TURSO_AUTH_TOKEN,
    });
    ```
  </Step>

  <Step title="Execute a query using SQL">
    ```ts theme={null}
    const stmt = await conn.prepare("SELECT * FROM users");
    const rows = await stmt.all();
    ```

    If you need to use placeholders for values, you can do that:

    ```ts theme={null}
    const stmt = await conn.prepare("SELECT * FROM users WHERE id = ?");
    const row = await stmt.get([1]);
    ```
  </Step>
</Steps>

## Remote libSQL database: @libsql/client

`@libsql/client` is the package for applications that connect to a remote [libSQL](/libsql) database on Turso Cloud. It is also the package to use for ORM integration (Drizzle, Prisma). See the [reference](/sdk/ts/reference#libsqlclient) for full documentation.

<Steps>
  <Step title="Install">
    <Snippet />
  </Step>

  <Step title="Connect">
    <Snippet />
  </Step>

  <Step title="Execute">
    ```ts theme={null}
    await turso.execute("SELECT * FROM users");
    ```
  </Step>

  <Step title="Sync">
    If you need to sync your local database with a remote Turso Cloud database (local reads and writes with push/pull to the cloud), use [Turso Sync](/sync/usage). Turso Sync is built on the Turso Database engine and provides local-first sync with explicit `push()` and `pull()` operations.
  </Step>
</Steps>


# Reference
Source: https://docs.turso.tech/sdk/ts/reference

TypeScript Reference for Turso

Turso offers four TypeScript packages:

|                       | `@tursodatabase/database` | `@tursodatabase/sync`       | `@tursodatabase/serverless`                                   | `@libsql/client`                                      |
| --------------------- | ------------------------- | --------------------------- | ------------------------------------------------------------- | ----------------------------------------------------- |
| **Use case**          | Local / embedded database | Local database + cloud sync | Remote Turso database (servers, containers, serverless, edge) | Remote libSQL database, ORM support (Drizzle, Prisma) |
| **Engine**            | Turso (rewrite)           | Turso (rewrite)             | Turso                                                         | libSQL (SQLite fork)                                  |
| **Dependencies**      | Native (Node.js, WASM)    | Native (Node.js)            | `fetch` only — zero native deps                               | Requires Node.js or `/web` subpath                    |
| **Concurrent writes** | Yes (MVCC)                | Yes (MVCC)                  | Yes (MVCC)                                                    | Not supported                                         |
| **Sync**              | —                         | push/pull (local-first)     | —                                                             | Embedded Replicas (writes go to cloud primary)        |
| **ORM support**       | Drizzle (beta)            | —                           | —                                                             | Drizzle, Prisma, and others                           |

**Starting a new project?** Use `@tursodatabase/database` for local/embedded use, `@tursodatabase/sync` for local + cloud sync, and `@tursodatabase/serverless` to reach a remote [Turso database](/tursodb/quickstart) over the network (Node.js servers, Docker containers, serverless functions, edge runtimes). **Connecting to a remote [libSQL](/libsql) database, or using an ORM?** Use `@libsql/client` — it's production-ready and supported by Drizzle, Prisma, and others. Drizzle has [beta support](https://orm.drizzle.team/docs/connect-turso-database) for `@tursodatabase/database` for local/embedded use.

The following runtime environments are known to be compatible:

* Node.js version 12 or later
* Deno
* CloudFlare Workers
* Netlify & Vercel Edge Functions

## @tursodatabase/database

For local and embedded use. Built on the Turso Database engine with concurrent writes (MVCC) and async I/O.

### Installing

```bash theme={null}
npm install @tursodatabase/database
```

### Initializing

```ts theme={null}
import { connect } from "@tursodatabase/database";

const db = await connect("app.db");
```

In-memory databases are also supported:

```ts theme={null}
const db = await connect(":memory:");
```

### Querying

```ts theme={null}
const stmt = db.prepare("SELECT * FROM users");
const users = await stmt.all();

const insert = db.prepare("INSERT INTO users (username) VALUES (?)");
await insert.run("alice");
```

### Encryption

Encrypt local databases at rest using the `encryption` option:

```ts theme={null}
import { connect } from "@tursodatabase/database";

const db = await connect("encrypted.db", {
  encryption: {
    cipher: "aegis256",
    hexkey: "b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327",
  },
});
```

Supported ciphers: `aegis256`, `aegis256x2`, `aegis128l`, `aegis128x2`, `aegis128x4`, `aes256gcm`, `aes128gcm`.

Encrypted databases cannot be read as standard SQLite databases — you must use the Turso Database engine to open them.

<Info>
  Turso Cloud databases can also be encrypted with bring-your-own-key — [learn more](/cloud/encryption).
</Info>

## @tursodatabase/sync

For local database with cloud sync. All reads and writes happen locally; use `push()` to send changes to the cloud and `pull()` to fetch remote changes.

### Installing

```bash theme={null}
npm install @tursodatabase/sync
```

### Initializing

```ts theme={null}
import { connect } from "@tursodatabase/sync";

const db = await connect({
  path: "./app.db",
  url: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});
```

On the first run, the local database is automatically bootstrapped from the remote. See [Turso Sync](/sync/usage) for full details.

### Push and Pull

```ts theme={null}
// Push local writes to Turso Cloud
await db.push();

// Pull remote changes to local database
const changed = await db.pull();
```

### Checkpoint

Compact the local WAL to bound disk usage while preserving sync state:

```ts theme={null}
await db.checkpoint();
```

### Stats

```ts theme={null}
const s = await db.stats();
console.info({
  cdcOperations: s.cdcOperations,
  mainWalSize: s.mainWalSize,
  networkReceivedBytes: s.networkReceivedBytes,
  networkSentBytes: s.networkSentBytes,
  lastPullUnixTime: s.lastPullUnixTime,
  lastPushUnixTime: s.lastPushUnixTime,
  revision: s.revision,
});
```

## @tursodatabase/serverless

The recommended package for **any application that connects to a remote Turso Cloud database** — Node.js servers, Docker containers, serverless functions (AWS Lambda, Vercel Functions), and edge runtimes (Cloudflare Workers, Deno Deploy). Uses only `fetch` — zero native dependencies, works everywhere `fetch` is available.

### Installing

```bash theme={null}
npm install @tursodatabase/serverless
```

### Initializing

```ts theme={null}
import { connect } from "@tursodatabase/serverless";

const conn = connect({
  url: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});
```

For compatibility with the `@libsql/client` API, use the compat module:

```ts theme={null}
import { createClient } from "@tursodatabase/serverless/compat";

const client = createClient({
  url: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});
```

### Querying

```ts theme={null}
const stmt = await conn.prepare("SELECT * FROM users");
const rows = await stmt.all();

const stmt2 = await conn.prepare("SELECT * FROM users WHERE id = ?");
const row = await stmt2.get([1]);
```

## @libsql/client

The `@libsql/client` package is built on [libSQL](https://github.com/tursodatabase/libsql), the open-source fork of SQLite that has powered Turso Cloud for years. It is production-ready, battle-tested, and the right choice for remote access to libSQL databases, when you need ORM integration beyond Drizzle (e.g., Prisma), or when working with an existing `@libsql/client`-based codebase.

<Info>
  With `@libsql/client` Embedded Replicas, reads are local and writes are sent to the cloud primary, then reflected back to the replica. Embedded Replicas are fully supported. For new projects that need sync, we recommend `@tursodatabase/sync` with [Turso Sync](/sync/usage).
</Info>

### Installing

<Snippet />

### Initializing

<Snippet />

<br />

<Info>
  If you're using libsql locally or an sqlite file, you can ignore passing `authToken`.
</Info>

### In-Memory Databases

```ts {4} theme={null}
import { createClient } from "@libsql/client";

const client = createClient({
  url: ":memory:",
});
```

### Local Development

You can work locally using an SQLite file and passing the path to `createClient`:

```ts {4} theme={null}
import { createClient } from "@libsql/client";

const client = createClient({
  url: "file:path/to/db-file.db",
  authToken: "...",
});
```

<Warning>
  The `@libsql/client/web` does not support local file URLs.
</Warning>

### Embedded Replicas

<Info>
  For workloads that need **offline writes**, **bidirectional sync**, or **multi-writer convergence**, we recommend [`@tursodatabase/sync`](#tursodatabasesync) — both reads and writes are local, and you sync explicitly with `push()` / `pull()`.
</Info>

You can work with embedded replicas by passing your Turso Database URL to `syncUrl`:

```ts {5} theme={null}
import { createClient } from "@libsql/client";

const client = createClient({
  url: "file:path/to/db-file.db",
  syncUrl: "libsql://[databaseName]-[organizationSlug].turso.io",
  authToken: "...",
});
```

<Snippet />

#### Manual Sync

```ts theme={null}
await client.sync();
```

#### Periodic Sync

```ts {6} theme={null}
import { createClient } from "@libsql/client";

const client = createClient({
  url: "file:path/to/db-file.db",
  syncUrl: "libsql://[databaseName]-[organizationSlug].turso.io",
  syncInterval: 60,
  authToken: "...",
});
```

### Encryption

<Warning>
  For new projects, we recommend [`@tursodatabase/database`](#tursodatabasedatabase) for local encryption — it is built on the Turso Database engine with better performance and concurrent write support.
</Warning>

<Snippet />

Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — [learn more](/libsql#encryption-at-rest).

### Concurrency

By default, the client performs up to `20` concurrent requests:

```ts {4} theme={null}
import { createClient } from "@libsql/client";

const client = createClient({
  concurrency: 10,
});
```

### Response

Each method listed below returns a `Promise<ResultSet>`:

| Property          | Type                          | Description                                                                                                        |
| ----------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `rows`            | `Array<Row>`                  | An array of Row objects containing the row values, empty for write operations                                      |
| `columns`         | `Array<string>`               | An array of strings with the names of the columns in the order they appear in each Row, empty for write operations |
| `rowsAffected`    | `number`                      | The number of rows affected by a write statement, `0` otherwise                                                    |
| `lastInsertRowid` | `bigint         \| undefined` | The ID of a newly inserted row, or `undefined` if there is none for the statement                                  |

### Simple query

You can pass a string or object to `execute()` to invoke a SQL statement:

<CodeGroup>
  ```ts String theme={null}
  const result = await client.execute("SELECT * FROM users");
  ```

  ```ts Object theme={null}
  const result = await client.execute({
    sql: "SELECT * FROM users WHERE id = ?",
    args: [1],
  });
  ```
</CodeGroup>

### Placeholders

libSQL supports the use of positional and named placeholders within SQL statements:

<CodeGroup>
  ```ts Positional theme={null}
  const result = await client.execute({
    sql: "SELECT * FROM users WHERE id = ?",
    args: [1],
  });

  const result = await client.batch(
    [
      {
        sql: "INSERT INTO users VALUES (?)",
        args: ["Iku"],
      },
    ],
    "write",
  );
  ```

  ```ts Named theme={null}
  const result = await client.execute({
    sql: "INSERT INTO users VALUES (:name)",
    args: { name: "Iku" },
  });

  const result = await client.batch(
    [
      {
        sql: "INSERT INTO users VALUES (:name)",
        args: { name: "Iku" },
      },
    ],
    "write",
  );
  ```
</CodeGroup>

<br />

<Info>
  libSQL supports the same named placeholder characters as SQLite — `:`, `@` and `$`.
</Info>

### Transaction Modes

| Mode       | SQLite command               | Description                                                                                                                                                                                        |
| ---------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `write`    | `BEGIN IMMEDIATE`            | The transaction may execute statements that read and write data. Write transactions executed on a replica are forwarded to the primary instance, and can't operate in parallel.                    |
| `read`     | `BEGIN TRANSACTION READONLY` | The transaction may only execute statements that read data (select). Read transactions can occur on replicas, and can operate in parallel with other read transactions.                            |
| `deferred` | `BEGIN DEFERRED`             | The transaction starts in read mode, then changes to write as soon as a write statement is executed. This mode change may fail if there is a write transaction currently executing on the primary. |

### Batch Transactions

A batch consists of multiple SQL statements executed sequentially within an implicit transaction. The backend handles the transaction: success commits all changes, while any failure results in a full rollback with no modifications.

```ts theme={null}
const result = await client.batch(
  [
    {
      sql: "INSERT INTO users VALUES (?)",
      args: ["Iku"],
    },
    {
      sql: "INSERT INTO users VALUES (?)",
      args: ["Iku 2"],
    },
  ],
  "write",
);
```

### Interactive Transactions

Interactive transactions in SQLite ensure the consistency of a series of read and write operations within a transaction's scope. These transactions give you control over when to commit or roll back changes, isolating them from other client activity.

| Method       | Description                                                         |
| ------------ | ------------------------------------------------------------------- |
| `execute()`  | Similar to `execute()` except within the context of the transaction |
| `commit()`   | Commits all write statements in the transaction                     |
| `rollback()` | Rolls back the entire transaction                                   |
| `close()`    | Immediately stops the transaction                                   |

<CodeGroup>
  ```ts Rollback Usage theme={null}
  try {
    const userId = "user123";
    const withdrawalAmount = 500;

    const transaction = await client.transaction("write");

    const balanceResult = await transaction.execute({
      sql: "SELECT balance FROM accounts WHERE userId = ?",
      args: [userId],
    });

    const currentBalance = balanceResult.rows[0]["balance"] as number;

    if (currentBalance >= withdrawalAmount) {
      await transaction.execute({
        sql: "UPDATE accounts SET balance = balance - ? WHERE userId = ?",
        args: [withdrawalAmount, userId],
      });
    } else {
      console.log("Insufficient funds");
      await transaction.rollback();
      return;
    }

    await transaction.commit();
  } catch (e) {
    console.error(e);
  }
  ```

  ```ts Batch Usage theme={null}
  let transaction: Transaction | null = null;

  try {
    const records = [
      { name: "Alice", age: 30 },
      { name: "Bob", age: 25 },
      { name: "Charlie", age: 35 },
    ];

    transaction = await client.transaction("write");

    for (const record of records) {
      await transaction.execute({
        sql: "INSERT INTO people (name, age) VALUES (?, ?)",
        args: [record.name, record.age],
      });
    }

    await transaction.commit();
  } catch (e) {
    console.error(e);
    if (transaction) await transaction.rollback();
  }
  ```

  ```ts Conditional Usage theme={null}
  try {
    const productId = "prod456";
    const newPrice = 150;

    const transaction = await client.transaction("write");

    const productResult = await transaction.execute({
      sql: "SELECT price FROM products WHERE productId = ?",
      args: [productId],
    });

    const currentPrice = productResult.rows[0]["price"] as number;

    if (currentPrice > newPrice) {
      await transaction.execute({
        sql: "UPDATE products SET price = ? WHERE productId = ?",
        args: [newPrice, productId],
      });
    } else {
      console.log("New price is not lower than current price");
      await transaction.rollback();
      return;
    }

    await transaction.commit();
  } catch (e) {
    console.error(e);
  }
  ```
</CodeGroup>

<br />

<Warning>
  Interactive transactions in libSQL lock the database for writing until
  committed or rolled back, with a 5-second timeout. They can impact performance
  on high-latency or busy databases.
</Warning>

### ATTACH

You can attach multiple databases to the current connection using the `ATTACH` attachment:

```ts theme={null}
import { createClient } from "@libsql/client";

const client = createClient({
  url: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

const txn = await db.transaction("read");

await txn.execute('ATTACH "<database-id>" AS attached');

const rs = await txn.execute("SELECT * FROM attached.users");
```

<Info>
  Make sure to [allow `ATTACH`](/cli/db/config/attach/allow) and create a token
  with the permission to attach a database — [learn
  more](/features/attach-database)
</Info>


# Command-Line Options
Source: https://docs.turso.tech/sql-reference/cli/command-line-options

CLI flags, arguments, and server modes for the tursodb command

# Command-Line Options

```bash theme={null}
tursodb [OPTIONS] [DATABASE] [SQL]
```

## Arguments

| Argument   | Default    | Description                                                                  |
| ---------- | ---------- | ---------------------------------------------------------------------------- |
| `DATABASE` | `:memory:` | Path to a SQLite database file. If omitted, an in-memory database is created |
| `SQL`      | —          | SQL statement to execute. If provided, the shell runs the query and exits    |

### Examples

```bash theme={null}
# In-memory database
tursodb

# Open an existing file
tursodb customers.db

# Run a query and exit
tursodb customers.db "SELECT count(*) FROM orders;"

# Pipe SQL from stdin
echo "SELECT 1 + 1;" | tursodb -q
```

## Options

### `-m, --output-mode <MODE>`

Set the output display format.

| Mode     | Description                           |
| -------- | ------------------------------------- |
| `pretty` | Table with borders (default)          |
| `list`   | Pipe-delimited values                 |
| `line`   | One column per line with column names |

```bash theme={null}
tursodb -m list mydata.db "SELECT * FROM users;"
```

### `-o, --output <PATH>`

Redirect query output to a file instead of stdout.

```bash theme={null}
tursodb -o results.txt mydata.db "SELECT * FROM users;"
```

### `-q, --quiet`

Suppress the startup banner and version information.

```bash theme={null}
tursodb -q mydata.db
```

### `-e, --echo`

Print each SQL statement before executing it. Useful for debugging scripts.

```bash theme={null}
echo "SELECT 42;" | tursodb -q -e
```

```
SELECT 42;
42
```

### `-v, --vfs <VFS>`

Select the Virtual File System backend.

| VFS        | Description                             |
| ---------- | --------------------------------------- |
| `syscall`  | Standard OS file I/O (default)          |
| `memory`   | In-memory storage                       |
| `io_uring` | Linux io\_uring (requires feature flag) |

```bash theme={null}
tursodb -v memory
```

### `-t, --tracing-output <PATH>`

Write internal log traces to a file. Useful for debugging and performance analysis.

```bash theme={null}
tursodb -t trace.log mydata.db
```

### `--readonly`

Open the database in read-only mode. Write operations will return an error.

```bash theme={null}
tursodb --readonly production.db "SELECT count(*) FROM users;"
```

Attempting to write in read-only mode:

```bash theme={null}
tursodb --readonly production.db "INSERT INTO users VALUES (1, 'test');"
# Error: Resource is read-only
```

### `--mcp`

Start a Model Context Protocol server instead of the interactive shell. This allows AI assistants and other MCP clients to interact with the database via JSON-RPC.

```bash theme={null}
tursodb --mcp mydata.db
```

### `--sync-server <ADDRESS>`

Start a sync server for database replication, listening at the given address.

```bash theme={null}
tursodb --sync-server 0.0.0.0:8080 mydata.db
```

## Experimental Feature Flags

These flags enable features that are still under development. They may change or be removed in future releases.

<Warning>
  Experimental features may have incomplete implementations or known limitations. Use them for testing and development only.
</Warning>

| Flag                                     | Description                                                                         |
| ---------------------------------------- | ----------------------------------------------------------------------------------- |
| `--experimental-views`                   | Enable views (`CREATE VIEW` / `CREATE MATERIALIZED VIEW`)                           |
| `--experimental-custom-types`            | Enable custom types (`CREATE TYPE` / `DROP TYPE` / `CREATE DOMAIN` / `DROP DOMAIN`) |
| `--experimental-encryption`              | Enable at-rest database encryption                                                  |
| `--experimental-index-method`            | Enable custom index methods. Necessary for FTS and sparse vector indexes            |
| `--experimental-autovacuum`              | Enable automatic database vacuuming                                                 |
| `--experimental-vacuum`                  | Enable in-place `VACUUM`                                                            |
| `--experimental-attach`                  | Enable `ATTACH DATABASE` / `DETACH DATABASE`                                        |
| `--experimental-generated-columns`       | Enable virtual `GENERATED ALWAYS AS` columns                                        |
| `--experimental-without-rowid`           | Enable `WITHOUT ROWID` tables                                                       |
| `--experimental-multiprocess-wal`        | Enable sharing a database between processes via a shared WAL coordinator            |
| `--experimental-mvcc-passive-checkpoint` | Enable passive checkpointing under MVCC (`journal_mode=mvcc`)                       |

<Info>
  Triggers (`CREATE TRIGGER` / `DROP TRIGGER`) are stable as of Turso 0.7 and no longer require a flag.
</Info>

```bash theme={null}
# Enable views, custom types, and in-place VACUUM
tursodb --experimental-views --experimental-custom-types --experimental-vacuum mydata.db
```

## Other Flags

| Flag               | Description                                                               |
| ------------------ | ------------------------------------------------------------------------- |
| `--mvcc`           | Start the MVCC concurrent-transaction harness instead of the normal shell |
| `--unsafe-testing` | Enable unsafe testing features (e.g., `sqlite_dbpage` writes)             |
| `-h, --help`       | Print usage help                                                          |
| `-V, --version`    | Print version information                                                 |


# Getting Started
Source: https://docs.turso.tech/sql-reference/cli/getting-started

Install and use the Turso interactive SQL shell

# Getting Started

The Turso CLI (`tursodb`) is an interactive SQL shell for working with Turso databases. It supports in-memory and file-based databases, multiple output modes, CSV import, database cloning, and built-in documentation.

## Quick Start

### In-Memory Database

Launch the shell with a transient in-memory database:

```bash theme={null}
tursodb
```

### Open a Database File

```bash theme={null}
tursodb mydata.db
```

### Run a Query Directly

Pass SQL as a command-line argument to run it and exit:

```bash theme={null}
tursodb mydata.db "SELECT * FROM users;"
```

### Pipe SQL from stdin

```bash theme={null}
echo "SELECT sqlite_version();" | tursodb -q
```

## Interactive Shell

When launched without a SQL argument, the shell provides an interactive REPL with command history and syntax highlighting.

### Multi-line Input

Unfinished statements (missing semicolons, unbalanced parentheses) automatically continue on the next line. The prompt indicates nesting depth:

```
tursodb> SELECT *
   ...>   FROM employees
   ...>   WHERE department = 'Engineering';
```

### Command History

Command history is saved automatically to `~/.limbo_history` and accessible with up/down arrow keys.

### Exiting

Use `.quit` or `.exit` to leave the shell. Pressing Ctrl+C twice also exits.

## Output Modes

Turso supports three output modes, selectable with the `-m` flag or the `.mode` dot command.

### Pretty (Default)

Human-readable table with borders:

```bash theme={null}
tursodb -q -m pretty
```

```
┌────┬─────────┬─────────────┬─────────┐
│ id │ name    │ department  │ salary  │
├────┼─────────┼─────────────┼─────────┤
│  1 │ Alice   │ Engineering │ 95000.0 │
├────┼─────────┼─────────────┼─────────┤
│  2 │ Bob     │ Marketing   │ 72000.0 │
└────┴─────────┴─────────────┴─────────┘
```

### List

Pipe-delimited values suitable for scripting:

```bash theme={null}
tursodb -q -m list
```

```
1|Alice|Engineering|95000.0
2|Bob|Marketing|72000.0
```

### Line

One column per line, with column names:

```bash theme={null}
tursodb -q -m line
```

```
        id = 1
      name = Alice
department = Engineering
    salary = 95000.0
```

## Non-Interactive Mode

When input is piped (not a terminal), the shell runs in non-interactive mode:

* Output defaults to `list` mode (override with `-m`)
* No startup banner, history, or syntax highlighting
* Exits with code 1 on query errors

```bash theme={null}
echo "SELECT 1 + 2;" | tursodb -q
```

## Server Modes

The CLI can also run as a server instead of an interactive shell.

### MCP Server

Start a [Model Context Protocol](https://modelcontextprotocol.io/) server, allowing AI assistants to interact with the database:

```bash theme={null}
tursodb --mcp mydata.db
```

### Sync Server

Start a sync server for database replication:

```bash theme={null}
tursodb --sync-server 0.0.0.0:8080 mydata.db
```


# Shell Commands
Source: https://docs.turso.tech/sql-reference/cli/shell-commands

Dot commands available in the Turso interactive SQL shell

# Shell Commands

Dot commands are special commands available in the interactive shell. They start with a period (`.`) and do not require a trailing semicolon.

```
tursodb> .help
```

## Database & Navigation

### .open

Open a database file, optionally specifying a VFS backend.

```
.open <PATH> [VFS]
```

```
tursodb> .open mydata.db
tursodb> .open mydata.db memory
```

### .quit

Exit the shell. Aliases: `.q`, `.qu`, `.qui`.

```
tursodb> .quit
```

### .exit

Exit the shell with an optional return code. Aliases: `.ex`, `.exi`.

```
.exit [CODE]
```

```
tursodb> .exit
tursodb> .exit 1
```

### .cd

Change the current working directory.

```
.cd <DIRECTORY>
```

```
tursodb> .cd /tmp
```

## Schema Inspection

### .tables

List all tables in the database, optionally filtered by a pattern.

```
.tables [PATTERN]
```

```sql theme={null}
CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT);
```

```
tursodb> .tables
employees
departments

tursodb> .tables emp%
employees
```

### .schema

Display the CREATE statement for a table, or all tables if no argument is given.

```
.schema [TABLE]
```

```
tursodb> .schema employees
CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT);

tursodb> .schema
CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT);
```

### .indexes

Show index names, optionally filtered by table.

```
.indexes [TABLE]
```

```sql theme={null}
CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, department TEXT);
CREATE INDEX idx_dept ON employees(department);
CREATE INDEX idx_name ON employees(name);
```

```
tursodb> .indexes
idx_dept
idx_name

tursodb> .indexes employees
idx_dept
idx_name
```

### .databases

List all attached databases.

```
tursodb> .databases
main: /path/to/mydata.db r/w
```

## Output Control

### .mode

Set the output display mode.

```
.mode <MODE>
```

| Mode     | Description                           |
| -------- | ------------------------------------- |
| `pretty` | Table with borders (default)          |
| `list`   | Pipe-delimited values                 |
| `line`   | One column per line with column names |

```
tursodb> .mode list
tursodb> SELECT 1 AS a, 2 AS b;
1|2

tursodb> .mode line
tursodb> SELECT 1 AS a, 2 AS b;
a = 1
b = 2

tursodb> .mode pretty
tursodb> SELECT 1 AS a, 2 AS b;
┌───┬───┐
│ a │ b │
├───┼───┤
│ 1 │ 2 │
└───┴───┘
```

### .headers

Toggle column headers on or off in `list` mode.

```
.headers <on|off>
```

```
tursodb> .mode list
tursodb> .headers on
tursodb> SELECT 1 AS x, 2 AS y;
x|y
1|2

tursodb> .headers off
tursodb> SELECT 1 AS x, 2 AS y;
1|2
```

### .nullvalue

Set the string displayed for NULL values in `list` mode.

```
.nullvalue <STRING>
```

```
tursodb> .mode list
tursodb> .nullvalue [NULL]
tursodb> SELECT 1 AS a, NULL AS b;
1|[NULL]
```

### .output

Redirect query output to a file. Call with no argument or `stdout` to restore output to the terminal.

```
.output [PATH]
```

```
tursodb> .output results.txt
tursodb> SELECT * FROM employees;
tursodb> .output
tursodb> -- Output is back to the terminal
```

### .echo

Toggle echo mode. When on, each SQL statement is printed before execution.

```
.echo <on|off>
```

```
tursodb> .echo on
tursodb> SELECT 42;
SELECT 42;
┌────┐
│ 42 │
├────┤
│ 42 │
└────┘
```

### .show

Display current shell settings.

```
tursodb> .show
Settings:
Output mode: pretty
DB: mydata.db
Output: STDOUT
Null value:
CWD: /home/user
Echo: off
Headers: off
```

## Performance & Diagnostics

### .timer

Toggle query timing. When on, shows execution time and I/O statistics after each query.

```
.timer <on|off>
```

```
tursodb> .timer on
tursodb> SELECT 42;
42
Command stats:
----------------------------
total: 442 us (this includes parsing/coloring of cli app)

query execution stats:
----------------------------
Execution: avg=4 us, total=8 us
I/O: No samples available
```

### .stats

Display database statistics. Use `on`/`off` to toggle automatic display after every query, or `--reset` to clear counters.

```
.stats [on|off] [--reset]
```

```
tursodb> .stats
Connection Metrics:
  Total statements:     3
  High-water marks:
    Max VM steps:       19
    Max rows read:      1

Aggregate Statistics:
Statement Metrics:
  Row Operations:
    Rows read:        1
    Rows written:     2
  Execution:
    VM steps:         48
    Instructions:     47
  ...
```

### .opcodes

Show VDBE (Virtual Database Engine) opcodes with descriptions. Optionally filter by opcode name.

```
.opcodes [OPCODE]
```

```
tursodb> .opcodes Integer
Integer
-------
The 64-bit integer value P1 is written into register P2. This is different
from SQLite, where this opcode is used for 32-bit integers
```

### .vfslist

List available Virtual File System modules.

```
tursodb> .vfslist
Available VFS modules:
memory
syscall
```

## Data Import & Export

### .dump

Output the entire database as SQL statements that can recreate it.

```
tursodb> .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO "employees" VALUES(1,'Alice');
INSERT INTO "employees" VALUES(2,'Bob');
COMMIT;
```

<Info>
  The output of `.dump` can be piped into another Turso instance to recreate the database:

  ```bash theme={null}
  tursodb original.db ".dump" | tursodb -q clone.db
  ```
</Info>

### .import

Import data from a file into a table.

```
.import [--csv] [--skip N] [--verbose] <FILE> <TABLE>
```

| Option      | Default | Description                                                  |
| ----------- | ------- | ------------------------------------------------------------ |
| `--csv`     | on      | Use comma as field separator and newline as record separator |
| `--skip N`  | 0       | Skip the first N rows (useful for skipping a header row)     |
| `--verbose` | off     | Print progress information during import                     |

```
tursodb> CREATE TABLE people (name TEXT, age INTEGER);
tursodb> .import --csv --skip 1 data.csv people
tursodb> SELECT * FROM people;
┌─────────┬─────┐
│ name    │ age │
├─────────┼─────┤
│ Alice   │  30 │
├─────────┼─────┤
│ Bob     │  25 │
├─────────┼─────┤
│ Charlie │  35 │
└─────────┴─────┘
```

Given a CSV file `data.csv`:

```csv theme={null}
name,age
Alice,30
Bob,25
Charlie,35
```

### .clone

Clone the current database to a new file.

```
.clone <OUTPUT_FILE>
```

```
tursodb> .clone backup.db
employees... done
```

### .read

Execute SQL statements from a file.

```
.read <PATH>
```

```
tursodb> .read setup.sql
```

### .load

Load an extension library.

```
.load <PATH>
```

```
tursodb> .load ./liblimbo_regexp
```

<Info>
  Only Turso-native extensions can be loaded. SQLite `.so`/`.dll` loadable extensions are not supported. See the [Extensions](/sql-reference/extensions) documentation for available extensions.
</Info>

## Debugging

### .dbtotxt

Display raw database page contents in hex format. Useful for debugging storage-level issues.

```
.dbtotxt [--page PAGE_NO]
```

```
tursodb> .dbtotxt --page 1
| size 8192 pagesize 4096 filename :memory:
| page 1 offset 0
|      0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00   SQLite format 3.
|     16: 10 00 02 02 00 40 20 20 00 00 00 01 00 00 00 02   .....@  ........
...
```

### .dbconfig

Print or set database configuration flags. Currently a no-op in Turso.

```
.dbconfig [CONFIG] [on|off]
```

## Documentation

### .manual

Display built-in manual pages for Turso features. Call with no argument to list all available manuals.

```
.manual [PAGE]
```

Aliases: `.man`.

```
tursodb> .manual
# Turso Manual Pages

Available manuals:
  custom-types    Custom Types for STRICT Tables
  cdc             Change Data Capture
  encryption      At-Rest Database Encryption
  vector          Vector Search
  materialized-views  Live Materialized Views
  mcp             Model Context Protocol server

tursodb> .manual vector
```


# SQLite Compatibility
Source: https://docs.turso.tech/sql-reference/compatibility

Turso compatibility with SQLite - supported features, known differences, and limitations

# SQLite Compatibility

The authoritative compatibility reference is maintained at [`COMPAT.md`](https://github.com/tursodatabase/turso/blob/main/COMPAT.md) in the repository root. It tracks the support status for every SQL statement, expression, function, PRAGMA, C API function, VDBE opcode, and extension.

Refer to that document for the current state of compatibility. It is updated alongside code changes to stay accurate.

## Turso-Specific Extensions

These features extend Turso beyond SQLite compatibility:

| Feature                                                                         | Description                                              |
| ------------------------------------------------------------------------------- | -------------------------------------------------------- |
| [CREATE TYPE](/sql-reference/statements/create-type)                            | User-defined types for STRICT tables                     |
| [CREATE MATERIALIZED VIEW](/sql-reference/statements/create-materialized-view)  | Live materialized views with incremental maintenance     |
| [BEGIN CONCURRENT](/sql-reference/statements/transactions#begin-concurrent)     | Optimistic concurrent write transactions (MVCC)          |
| [Vector functions](/sql-reference/functions/vector)                             | Vector storage, distance calculations, similarity search |
| [Full-text search](/sql-reference/functions/fts)                                | Tantivy-powered FTS with BM25 scoring                    |
| [CDC](/sql-reference/pragmas#change-data-capture)                               | Change Data Capture via PRAGMA                           |
| [Encryption](/sql-reference/pragmas#encryption)                                 | At-rest database encryption                              |
| [Custom index methods](/sql-reference/statements/create-index#using-method)     | CREATE INDEX ... USING for FTS and custom access methods |
| stddev()                                                                        | Standard deviation aggregate function                    |
| [DEFAULT in VALUES](/sql-reference/statements/insert#default-keyword-in-values) | SQL-standard DEFAULT keyword in INSERT VALUES lists      |

## See Also

* [Experimental Features](/sql-reference/experimental-features) for how to enable experimental features
* [COMPAT.md](https://github.com/tursodatabase/turso/blob/main/COMPAT.md) for the full compatibility matrix


# Data Types
Source: https://docs.turso.tech/sql-reference/data-types

Storage classes, type affinity, STRICT tables, and custom types in Turso

# Data Types

Turso uses the same dynamic type system as SQLite, where values have types but columns do not enforce a single type (unless using STRICT tables). Every value stored in Turso belongs to one of five storage classes. Turso extends this system with [custom types](#custom-types), [composite types](#composite-types-struct-and-union), and [native array types](#array-types) for STRICT tables.

## Storage Classes

| Storage Class | Description                                                                  |
| ------------- | ---------------------------------------------------------------------------- |
| NULL          | The NULL value                                                               |
| INTEGER       | A signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on magnitude |
| REAL          | An 8-byte IEEE 754 floating-point number                                     |
| TEXT          | A UTF-8 encoded string                                                       |
| BLOB          | Raw binary data, stored exactly as input                                     |

```sql theme={null}
SELECT typeof(NULL);       -- 'null'
SELECT typeof(42);         -- 'integer'
SELECT typeof(3.14);       -- 'real'
SELECT typeof('hello');    -- 'text'
SELECT typeof(x'CAFE');    -- 'blob'
```

## Type Affinity

When a column is declared with a type name, Turso assigns a **type affinity** to that column. Type affinity is a recommendation for how to store values, not a strict constraint (unless using STRICT tables). Turso uses the same affinity rules as SQLite.

### Affinity Determination Rules

The type affinity of a column is determined by the declared type name, using these rules applied in order:

| Rule | Condition                                      | Affinity | Examples                                          |
| ---- | ---------------------------------------------- | -------- | ------------------------------------------------- |
| 1    | Type name contains "INT"                       | INTEGER  | `INT`, `INTEGER`, `BIGINT`, `SMALLINT`, `TINYINT` |
| 2    | Type name contains "CHAR", "CLOB", or "TEXT"   | TEXT     | `TEXT`, `VARCHAR(255)`, `CLOB`, `CHARACTER(20)`   |
| 3    | Type name contains "BLOB" or no type specified | BLOB     | `BLOB`, (no type)                                 |
| 4    | Type name contains "REAL", "FLOA", or "DOUB"   | REAL     | `REAL`, `FLOAT`, `DOUBLE`, `DOUBLE PRECISION`     |
| 5    | Otherwise                                      | NUMERIC  | `NUMERIC`, `DECIMAL`, `BOOLEAN`, `DATE`           |

```sql theme={null}
-- Type affinity is a suggestion, not a constraint
CREATE TABLE flexible (
    id INTEGER,
    name TEXT,
    data BLOB
);

-- This works - TEXT value in an INTEGER column
INSERT INTO flexible VALUES ('not a number', 42, 'text in blob');
SELECT typeof(id), typeof(name), typeof(data) FROM flexible;
-- 'text', 'integer', 'text'
```

### Type Conversions

When a value is inserted into a column, Turso attempts to convert the value to the column's affinity:

* **INTEGER affinity**: If the value is TEXT or REAL that looks like an integer, Turso converts the value to INTEGER
* **REAL affinity**: If the value is TEXT that looks like a number, Turso converts to REAL. If the value is an integer, Turso converts to REAL
* **NUMERIC affinity**: Turso tries INTEGER first, then REAL, then keeps as TEXT
* **TEXT affinity**: Integer and REAL values are converted to their text representation
* **BLOB affinity**: No conversion is attempted

## STRICT Tables

STRICT tables enforce type checking at the storage layer. Every value inserted into a STRICT table must match the declared column type or be convertible to the column type.

```sql theme={null}
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    age INTEGER,
    score REAL
) STRICT;

-- This works - values match declared types
INSERT INTO users VALUES (1, 'Alice', 30, 95.5);

-- This fails - 'thirty' cannot be converted to INTEGER
INSERT INTO users VALUES (2, 'Bob', 'thirty', 80.0);
-- Error: cannot store TEXT value in INTEGER column
```

### Allowed Types in STRICT Tables

STRICT tables only allow these base type names:

| Type    | Description                                                |
| ------- | ---------------------------------------------------------- |
| INTEGER | Signed integer                                             |
| REAL    | Floating-point number                                      |
| TEXT    | UTF-8 string                                               |
| BLOB    | Raw binary data                                            |
| ANY     | Any storage class (disables type checking for this column) |

<Info>
  **Turso Extension**: STRICT tables also accept [custom type](/sql-reference/statements/create-type) names, [composite types](#composite-types-struct-and-union) (STRUCT and UNION), and [array types](#array-types). Any base type can be turned into an array column by appending `[]` to the type name (e.g., `INTEGER[]`, `TEXT[]`). Arrays, custom types, and composite types are Turso-specific features.
</Info>

## Custom Types

<Info>
  **Turso Extension**: Custom types are a Turso-specific feature not available in SQLite. Custom types work only with STRICT tables. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features).
</Info>

Custom types let you define how values are encoded before storage and decoded when read, enforce domain constraints at the storage layer, attach operators, and provide defaults. Custom types are declared with the `CREATE TYPE` statement.

```sql theme={null}
-- A type that stores monetary values as cents
CREATE TYPE cents BASE integer
    ENCODE value * 100
    DECODE value / 100;

CREATE TABLE prices (
    id INTEGER PRIMARY KEY,
    amount cents
) STRICT;

INSERT INTO prices VALUES (1, 42);
SELECT amount FROM prices;
-- 42  (stored on disk as 4200)
```

### Built-in Custom Types

Turso provides several built-in custom types available in STRICT tables:

| Type           | Base    | Description                                      |
| -------------- | ------- | ------------------------------------------------ |
| `date`         | TEXT    | ISO 8601 date (YYYY-MM-DD)                       |
| `time`         | TEXT    | ISO 8601 time (HH:MM:SS)                         |
| `timestamp`    | TEXT    | ISO 8601 datetime                                |
| `varchar(N)`   | TEXT    | Text with maximum length constraint              |
| `numeric(P,S)` | BLOB    | Fixed-point decimal with precision and scale     |
| `smallint`     | INTEGER | Integer constrained to -32768..32767             |
| `boolean`      | INTEGER | Integer constrained to 0 or 1                    |
| `uuid`         | BLOB    | UUID stored as 16-byte blob, displayed as string |
| `bytea`        | BLOB    | Binary data (PostgreSQL-compatible alias)        |
| `inet`         | TEXT    | IP address                                       |
| `json`         | TEXT    | Validated JSON text                              |
| `jsonb`        | BLOB    | JSON in binary format                            |

```sql theme={null}
CREATE TABLE events (
    id uuid PRIMARY KEY,
    name varchar(100),
    event_date date,
    is_active boolean DEFAULT 1,
    metadata json
) STRICT;

INSERT INTO events VALUES (
    uuid4(),
    'Product Launch',
    '2025-03-15',
    1,
    '{"venue": "online"}'
);
```

For the full custom types reference including ENCODE/DECODE, operators, parametric types, and validation, see [CREATE TYPE](/sql-reference/statements/create-type).

### Composite Types: STRUCT and UNION

Turso also supports composite types: **STRUCT** (named product type) and **UNION** (discriminated union). These let you store structured data in a single column and access it with dot notation.

```sql theme={null}
-- STRUCT: group related fields, access with col.field
CREATE TYPE point AS STRUCT(x INT, y INT);
CREATE TABLE locations(id INT PRIMARY KEY, pos point) STRICT;
INSERT INTO locations VALUES (1, struct_pack(10, 20));
SELECT pos.x, pos.y FROM locations WHERE pos.x > 5;
-- 10|20

-- UNION: tagged variants, access with col.variant
CREATE TYPE platform_id AS UNION(telegram INT, slack TEXT);
CREATE TABLE contacts(id INT PRIMARY KEY, platform platform_id) STRICT;
INSERT INTO contacts VALUES (1, union_value('telegram', 12345));
INSERT INTO contacts VALUES (2, union_value('slack', 'U0ABC'));
SELECT id, platform.telegram, platform.slack FROM contacts ORDER BY id;
-- 1|12345|
-- 2||U0ABC
```

For the full composite types reference, see [CREATE TYPE — Composite Types](/sql-reference/statements/create-type#composite-types-struct-and-union).

## Array Types

<Info>
  **Turso Extension**: Array types are a Turso-specific feature not available in SQLite. Array columns work only with STRICT tables.
</Info>

Array columns store ordered collections of values. Declare them by appending `[]` to any base type name:

```sql theme={null}
CREATE TABLE sensors (
    id INTEGER PRIMARY KEY,
    readings REAL[],
    labels TEXT[],
    flags INTEGER[]
) STRICT;
```

Multi-dimensional arrays are supported with multiple bracket pairs:

```sql theme={null}
CREATE TABLE matrices (
    id INTEGER PRIMARY KEY,
    data INTEGER[][]
) STRICT;
```

Arrays can be inserted using the `ARRAY[...]` constructor or as JSON text:

```sql theme={null}
INSERT INTO sensors VALUES (1, ARRAY[1.5, 2.5, 3.5], '["a","b"]', '[0, 1, 1]');
```

Arrays are displayed as JSON arrays on output. Element types are validated against the declared base type — for example, an `INTEGER[]` column rejects non-numeric text elements.

For the full set of array functions, operators, and subscript syntax, see [Array Functions](/sql-reference/functions/array).

### Inspecting Types

List all available types (built-in and custom):

```sql theme={null}
PRAGMA list_types;
```

All types are also available through the `sqlite_turso_types` virtual table:

```sql theme={null}
SELECT name, sql FROM sqlite_turso_types;
```

## Comparison and Sorting

Turso uses the same comparison rules as SQLite. Values of different storage classes are ordered as:

```
NULL < INTEGER/REAL < TEXT < BLOB
```

* NULL values are considered less than any other value
* INTEGER and REAL values are compared numerically
* TEXT values are compared using the column's collation sequence (default: BINARY)
* BLOB values are compared using `memcmp()`

```sql theme={null}
SELECT 1 < 2;          -- 1 (true)
SELECT 'abc' < 'abd';  -- 1 (true)
SELECT 1 < '2';        -- 1 (true, numeric < text)
SELECT NULL < 1;        -- NULL (any comparison with NULL yields NULL)
SELECT NULL IS NULL;    -- 1 (use IS to test for NULL)
```

## See Also

* [CREATE TABLE](/sql-reference/statements/create-table) for column definitions and constraints
* [CREATE TYPE](/sql-reference/statements/create-type) for custom type definitions
* [Array Functions](/sql-reference/functions/array) for array construction, manipulation, and operators
* [Expressions](/sql-reference/expressions) for CAST expressions and type conversions
* [Compatibility](/sql-reference/compatibility) for differences from SQLite


# Experimental Features
Source: https://docs.turso.tech/sql-reference/experimental-features

How to enable experimental features in the CLI and each SDK

# Experimental Features

Some Turso features are still experimental and must be explicitly enabled before use. Experimental features are stable enough for testing but their API may change in future releases.

## Experimental Features List

| Feature                 | Flag                      | Description                                                                                                                                                                                                                                  |
| ----------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Views                   | `views`                   | [CREATE VIEW](/sql-reference/statements/create-view) and [CREATE MATERIALIZED VIEW](/sql-reference/statements/create-materialized-view)                                                                                                      |
| Custom Types            | `custom_types`            | [CREATE TYPE](/sql-reference/statements/create-type), [DROP TYPE](/sql-reference/statements/drop-type), [CREATE DOMAIN](/sql-reference/statements/create-domain), and [DROP DOMAIN](/sql-reference/statements/drop-domain) for STRICT tables |
| Encryption              | `encryption`              | At-rest encryption via [PRAGMA cipher / hexkey](/sql-reference/pragmas#encryption)                                                                                                                                                           |
| Index Methods           | `index_method`            | [CREATE INDEX ... USING](/sql-reference/statements/create-index#using-clause) for FTS and custom index types                                                                                                                                 |
| Autovacuum              | `autovacuum`              | Automatic database file compaction                                                                                                                                                                                                           |
| Vacuum                  | `vacuum`                  | In-place [VACUUM](/sql-reference/statements/vacuum) compaction                                                                                                                                                                               |
| Attach                  | `attach`                  | [ATTACH DATABASE](/sql-reference/statements/attach-database) and [DETACH DATABASE](/sql-reference/statements/detach-database)                                                                                                                |
| Generated Columns       | `generated_columns`       | Virtual [`GENERATED ALWAYS AS`](/sql-reference/statements/create-table) columns                                                                                                                                                              |
| Without Rowid           | `without_rowid`           | `WITHOUT ROWID` tables                                                                                                                                                                                                                       |
| Multi-Process WAL       | `multiprocess_wal`        | [Multi-Process Access](/sql-reference/multiprocess-access) — share a database file between OS processes via a shared WAL coordinator                                                                                                         |
| MVCC Passive Checkpoint | `mvcc_passive_checkpoint` | Passive checkpointing under MVCC (`journal_mode=mvcc`)                                                                                                                                                                                       |

<Info>
  Triggers are no longer experimental as of Turso 0.7 — [CREATE TRIGGER](/sql-reference/statements/create-trigger) and [DROP TRIGGER](/sql-reference/statements/drop-trigger) work without any flag.
</Info>

## CLI

Pass `--experimental-<feature>` flags when starting `tursodb`:

```bash theme={null}
tursodb --experimental-views --experimental-custom-types database.db
```

Multiple flags can be combined:

```bash theme={null}
tursodb \
  --experimental-views \
  --experimental-custom-types \
  --experimental-encryption \
  --experimental-index-method \
  --experimental-vacuum \
  database.db
```

## Rust SDK

The Rust binding uses a fluent `Builder` API:

```rust theme={null}
use turso::Builder;

let db = Builder::new_local("database.db")
    .experimental_encryption(true)
    .experimental_materialized_views(true)
    .experimental_custom_types(true)
    .experimental_index_method(true)
    .experimental_vacuum(true)
    .experimental_attach(true)
    .experimental_multiprocess_wal(true)
    .build()?;
```

## Python SDK

Pass a comma-separated string to the `experimental_features` parameter:

```python theme={null}
import turso

conn = turso.connect(
    "database.db",
    experimental_features="views,custom_types,vacuum",
)
```

## JavaScript / Node.js SDK

Pass an array of feature strings to the `experimental` option:

```typescript theme={null}
import { Database } from "@tursodatabase/libsql";

const db = new Database("file:database.db", {
    experimental: ["views", "custom_types", "encryption", "vacuum"],
});
```

## Go SDK

Set the `ExperimentalFeatures` field as a comma-separated string:

```go theme={null}
db, err := turso.NewDatabase(turso.TursoDatabaseConfig{
    Path: "database.db",
    ExperimentalFeatures: "views,custom_types,vacuum",
})
```

The Go driver also supports DSN query parameters:

```go theme={null}
db, err := sql.Open("turso", "database.db?experimental=views,vacuum")
```

## Java SDK

The Java binding currently supports encryption through a dedicated method:

```java theme={null}
TursoDB db = TursoDB.openWithEncryption(
    "database.db",
    OpenFlags.CREATE,
    "aegis256",
    "your-hex-key-here"
);
```

## .NET SDK

The .NET binding currently supports encryption through the C FFI:

```csharp theme={null}
var db = Database.OpenWithEncryption(
    "database.db",
    "aegis256",
    "your-hex-key-here"
);
```

## See Also

* [Compatibility](/sql-reference/compatibility) for the full feature support matrix


# Expressions
Source: https://docs.turso.tech/sql-reference/expressions

SQL expression syntax including literals, operators, CAST, CASE, and subqueries

# Expressions

Expressions are combinations of values, operators, and functions that Turso evaluates to produce a result. Expressions appear in many SQL clauses including SELECT columns, WHERE conditions, ORDER BY, GROUP BY, HAVING, CHECK constraints, and DEFAULT values.

## Literals

### Numeric Literals

```sql theme={null}
SELECT 42;           -- integer literal
SELECT -17;          -- negative integer
SELECT 3.14;         -- real (floating-point) literal
SELECT 2.5e10;       -- scientific notation
SELECT 0xFF;         -- hexadecimal integer (255)
```

### String Literals

String literals are enclosed in single quotes. To include a single quote within a string, use two consecutive single quotes:

```sql theme={null}
SELECT 'hello world';        -- text literal
SELECT 'it''s a test';       -- embedded single quote
SELECT '';                   -- empty string
```

### Blob Literals

Blob literals are hexadecimal strings preceded by `x` or `X`:

```sql theme={null}
SELECT x'CAFEBABE';          -- blob literal
SELECT X'48454C4C4F';        -- blob literal (case insensitive prefix)
```

### NULL Literal

```sql theme={null}
SELECT NULL;                 -- null value
```

### Boolean Literals

Turso does not have a separate boolean type. Use integers `0` (false) and `1` (true):

```sql theme={null}
SELECT 1;    -- true
SELECT 0;    -- false
```

## Operators

### Arithmetic Operators

| Operator    | Description    | Example  | Result                 |
| ----------- | -------------- | -------- | ---------------------- |
| `+`         | Addition       | `3 + 4`  | `7`                    |
| `-`         | Subtraction    | `10 - 3` | `7`                    |
| `*`         | Multiplication | `3 * 4`  | `12`                   |
| `/`         | Division       | `10 / 3` | `3` (integer division) |
| `-` (unary) | Negation       | `-5`     | `-5`                   |
| `+` (unary) | No-op          | `+5`     | `5`                    |

Integer division truncates toward zero. Use `CAST` or multiply by `1.0` for floating-point division:

```sql theme={null}
SELECT 10 / 3;           -- 3 (integer division)
SELECT 10 / 3.0;         -- 3.333... (real division)
SELECT CAST(10 AS REAL) / 3;  -- 3.333...
```

### Comparison Operators

| Operator     | Description           | Example  |
| ------------ | --------------------- | -------- |
| `=` or `==`  | Equal                 | `x = 5`  |
| `!=` or `<>` | Not equal             | `x != 5` |
| `<`          | Less than             | `x < 5`  |
| `>`          | Greater than          | `x > 5`  |
| `<=`         | Less than or equal    | `x <= 5` |
| `>=`         | Greater than or equal | `x >= 5` |

All comparison operators return `1` (true), `0` (false), or `NULL` (if either operand is NULL).

### Logical Operators

| Operator | Description | Example            |
| -------- | ----------- | ------------------ |
| `AND`    | Logical AND | `x > 0 AND x < 10` |
| `OR`     | Logical OR  | `x = 1 OR x = 2`   |
| `NOT`    | Logical NOT | `NOT x = 5`        |

### Bitwise Operators

| Operator | Description | Example   | Result |
| -------- | ----------- | --------- | ------ |
| `&`      | Bitwise AND | `5 & 3`   | `1`    |
| `\|`     | Bitwise OR  | `5 \| 3`  | `7`    |
| `~`      | Bitwise NOT | `~5`      | `-6`   |
| `<<`     | Left shift  | `1 << 4`  | `16`   |
| `>>`     | Right shift | `16 >> 2` | `4`    |

### String Concatenation

| Operator | Description   | Example                         | Result          |
| -------- | ------------- | ------------------------------- | --------------- |
| `\|\|`   | Concatenation | `'hello' \|\| ' ' \|\| 'world'` | `'hello world'` |

When either operand is an array, `||` performs array concatenation instead. See [Array Operators](#array-operators).

### Array Operators

<Info>
  **Turso Extension**: Array operators are a Turso-specific feature not available in SQLite.
</Info>

| Operator                        | Description  | Example                        | Result      |
| ------------------------------- | ------------ | ------------------------------ | ----------- |
| `@>`                            | Contains all | `ARRAY[1,2,3] @> ARRAY[1,2]`   | `1`         |
| `&&`                            | Overlaps     | `ARRAY[1,2,3] && ARRAY[3,4,5]` | `1`         |
| `\|\|`                          | Array concat | `ARRAY[1,2] \|\| ARRAY[3,4]`   | `[1,2,3,4]` |
| `=`, `!=`, `<`, `>`, `<=`, `>=` | Comparison   | `ARRAY[1,2] < ARRAY[1,3]`      | `1`         |

The `@>` operator tests if the left array contains all elements of the right array. The `&&` operator tests if two arrays share any common elements. Both return `1` (true) or `0` (false).

Array comparison is element-wise from left to right. If all compared elements are equal, the shorter array is considered less than the longer one.

```sql theme={null}
SELECT ARRAY[1,2,3] @> ARRAY[1,3];     -- 1 (contains both 1 and 3)
SELECT ARRAY[1,2] && ARRAY[3,4];       -- 0 (no common elements)
SELECT ARRAY[1,2] < ARRAY[1,2,3];      -- 1 (shorter is less)
```

For the full reference, see [Array Functions](/sql-reference/functions/array).

## CAST Expression

The CAST expression converts a value to a specified type.

```sql theme={null}
CAST(expression AS type-name)
```

| Parameter  | Type | Description          |
| ---------- | ---- | -------------------- |
| expression | any  | The value to convert |
| type-name  | type | The target type name |

```sql theme={null}
SELECT CAST(3.7 AS INTEGER);    -- 3 (truncates toward zero)
SELECT CAST(42 AS TEXT);         -- '42'
SELECT CAST('123' AS INTEGER);   -- 123
SELECT CAST('abc' AS INTEGER);   -- 0
SELECT CAST(NULL AS INTEGER);    -- NULL
```

<Info>
  **Turso Extension**: In STRICT tables with custom types, `CAST(value AS custom_type)` applies the custom type's ENCODE function. See [CREATE TYPE](/sql-reference/statements/create-type) for details.
</Info>

## COLLATE Expression

The COLLATE expression specifies a collation sequence for string comparison.

```sql theme={null}
expression COLLATE collation-name
```

Built-in collation sequences:

| Collation | Description                                      |
| --------- | ------------------------------------------------ |
| `BINARY`  | Byte-by-byte comparison (default)                |
| `NOCASE`  | Case-insensitive comparison for ASCII characters |
| `RTRIM`   | Like BINARY but ignores trailing spaces          |

```sql theme={null}
SELECT 'ABC' = 'abc';                        -- 0 (BINARY comparison)
SELECT 'ABC' = 'abc' COLLATE NOCASE;          -- 1 (case-insensitive)
SELECT 'abc ' = 'abc' COLLATE RTRIM;           -- 1 (trailing space ignored)

SELECT name FROM users ORDER BY name COLLATE NOCASE;
```

## Pattern Matching

### LIKE Operator

The LIKE operator performs case-insensitive pattern matching (for ASCII characters). The `%` wildcard matches any sequence of characters, and `_` matches any single character.

```sql theme={null}
expression [NOT] LIKE pattern [ESCAPE escape-char]
```

```sql theme={null}
SELECT 'Hello World' LIKE 'hello%';         -- 1 (case-insensitive)
SELECT 'Hello World' LIKE 'H_llo%';         -- 1 (_ matches 'e')
SELECT 'Hello World' LIKE '%World';          -- 1
SELECT '10%' LIKE '10\%' ESCAPE '\';        -- 1 (escaped % literal)
```

### GLOB Operator

The GLOB operator performs case-sensitive pattern matching using Unix-style wildcards. `*` matches any sequence of characters, and `?` matches any single character.

```sql theme={null}
expression [NOT] GLOB pattern
```

```sql theme={null}
SELECT 'Hello' GLOB 'H*';      -- 1
SELECT 'Hello' GLOB 'h*';      -- 0 (case-sensitive)
SELECT 'Hello' GLOB 'H?llo';   -- 1
SELECT 'Hello' GLOB 'H[a-z]*'; -- 1 (character class)
```

### REGEXP Operator

The REGEXP operator performs regular expression matching. Requires the regexp extension (loaded by default).

```sql theme={null}
expression [NOT] REGEXP pattern
```

```sql theme={null}
SELECT 'Hello123' REGEXP '[A-Za-z]+[0-9]+';  -- 1
SELECT 'test@email.com' REGEXP '^[^@]+@[^@]+\.[^@]+$';  -- 1
```

## BETWEEN Expression

The BETWEEN expression tests whether a value falls within an inclusive range.

```sql theme={null}
expression [NOT] BETWEEN low AND high
```

The BETWEEN expression is equivalent to `expression >= low AND expression <= high`:

```sql theme={null}
SELECT 5 BETWEEN 1 AND 10;     -- 1
SELECT 5 NOT BETWEEN 1 AND 3;  -- 1
SELECT 'b' BETWEEN 'a' AND 'c'; -- 1
```

## IN Expression

The IN expression tests whether a value matches any value in a list or subquery result.

```sql theme={null}
expression [NOT] IN (value1, value2, ...)
expression [NOT] IN (select-statement)
```

```sql theme={null}
SELECT 3 IN (1, 2, 3, 4, 5);           -- 1
SELECT 'red' NOT IN ('blue', 'green');   -- 1

SELECT name FROM users
WHERE id IN (SELECT user_id FROM orders WHERE total > 100);
```

## EXISTS Expression

The EXISTS expression returns `1` if the subquery returns at least one row, and `0` otherwise.

```sql theme={null}
[NOT] EXISTS (select-statement)
```

```sql theme={null}
SELECT EXISTS (SELECT 1 FROM users WHERE name = 'Alice');  -- 1 if Alice exists

SELECT name FROM departments d
WHERE EXISTS (
    SELECT 1 FROM employees e
    WHERE e.department_id = d.id
);
```

## IS NULL / IS NOT NULL

The IS NULL expression tests whether a value is NULL. Unlike `= NULL`, which always returns NULL, `IS NULL` returns `1` or `0`.

```sql theme={null}
expression IS NULL
expression IS NOT NULL
```

```sql theme={null}
SELECT NULL IS NULL;       -- 1
SELECT NULL = NULL;        -- NULL (not 1!)
SELECT 42 IS NOT NULL;     -- 1
SELECT NULL IS NOT NULL;   -- 0
```

## IS DISTINCT FROM

The IS DISTINCT FROM expression compares two values, treating NULL as a comparable value.

```sql theme={null}
expression IS [NOT] DISTINCT FROM expression
```

```sql theme={null}
SELECT 1 IS DISTINCT FROM 2;       -- 1 (different values)
SELECT 1 IS DISTINCT FROM 1;       -- 0 (same value)
SELECT NULL IS DISTINCT FROM NULL;  -- 0 (both NULL)
SELECT NULL IS DISTINCT FROM 1;     -- 1 (NULL vs non-NULL)
SELECT 1 IS NOT DISTINCT FROM 1;    -- 1
```

## CASE Expression

The CASE expression provides conditional logic within SQL expressions.

### Simple CASE

```sql theme={null}
CASE expression
    WHEN value1 THEN result1
    WHEN value2 THEN result2
    ...
    [ELSE default_result]
END
```

### Searched CASE

```sql theme={null}
CASE
    WHEN condition1 THEN result1
    WHEN condition2 THEN result2
    ...
    [ELSE default_result]
END
```

If no WHEN clause matches and there is no ELSE clause, the CASE expression returns NULL.

```sql theme={null}
-- Simple CASE
SELECT name,
    CASE status
        WHEN 'A' THEN 'Active'
        WHEN 'I' THEN 'Inactive'
        ELSE 'Unknown'
    END AS status_text
FROM users;

-- Searched CASE
SELECT name, score,
    CASE
        WHEN score >= 90 THEN 'A'
        WHEN score >= 80 THEN 'B'
        WHEN score >= 70 THEN 'C'
        ELSE 'F'
    END AS grade
FROM students;
```

## Scalar Subqueries

A subquery enclosed in parentheses that returns a single value can be used as an expression.

```sql theme={null}
(select-statement)
```

The subquery must return exactly one column and at most one row. If the subquery returns no rows, the expression evaluates to NULL.

```sql theme={null}
SELECT name,
    (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count
FROM users;

SELECT * FROM products
WHERE price > (SELECT AVG(price) FROM products);
```

## RAISE Function

The RAISE function raises an error condition. In standard SQLite, RAISE can only be used inside triggers. In Turso, `RAISE(ABORT, msg)` can also be used outside triggers — in CHECK constraints, custom type ENCODE expressions, and standalone queries. The other forms (`RAISE(IGNORE)`, `RAISE(ROLLBACK, msg)`, `RAISE(FAIL, msg)`) are only valid inside triggers.

```sql theme={null}
RAISE(IGNORE)
RAISE(ROLLBACK, error-message)
RAISE(ABORT, error-message)
RAISE(FAIL, error-message)
```

| Form                   | Description                                                                       |
| ---------------------- | --------------------------------------------------------------------------------- |
| `RAISE(IGNORE)`        | Abandon the current trigger action but continue the statement                     |
| `RAISE(ROLLBACK, msg)` | Abort the statement and roll back the current transaction                         |
| `RAISE(ABORT, msg)`    | Abort the current statement; prior changes in the transaction are preserved       |
| `RAISE(FAIL, msg)`     | Abort the current statement at the current point; prior row changes are preserved |

```sql theme={null}
-- In a trigger
CREATE TRIGGER validate_age BEFORE INSERT ON users
BEGIN
    SELECT CASE
        WHEN NEW.age < 0 THEN RAISE(ABORT, 'age must be non-negative')
    END;
END;

-- In a custom type ENCODE expression
CREATE TYPE positive_int BASE integer
    ENCODE CASE WHEN value > 0 THEN value
                ELSE RAISE(ABORT, 'value must be positive') END
    DECODE value;
```

## Operator Precedence

Operators are evaluated in the following order (highest precedence first):

| Precedence  | Operators                                                                                            |
| ----------- | ---------------------------------------------------------------------------------------------------- |
| 1 (highest) | `~` (unary NOT), `+` (unary), `-` (unary)                                                            |
| 2           | `\|\|` (concatenation)                                                                               |
| 3           | `*`, `/`                                                                                             |
| 4           | `+`, `-`                                                                                             |
| 5           | `<<`, `>>`, `&`, `\|`                                                                                |
| 6           | `<`, `<=`, `>`, `>=`, `@>`, `&&`                                                                     |
| 7           | `=`, `==`, `!=`, `<>`, `IS`, `IS NOT`, `IS DISTINCT FROM`, `IN`, `LIKE`, `GLOB`, `REGEXP`, `BETWEEN` |
| 8           | `NOT`                                                                                                |
| 9           | `AND`                                                                                                |
| 10 (lowest) | `OR`                                                                                                 |

Use parentheses to override precedence when needed:

```sql theme={null}
SELECT 2 + 3 * 4;       -- 14 (multiplication first)
SELECT (2 + 3) * 4;     -- 20 (parentheses override)
```

## See Also

* [Data Types](/sql-reference/data-types) for storage classes and type affinity
* [Scalar Functions](/sql-reference/functions/scalar) for built-in functions usable in expressions
* [SELECT](/sql-reference/statements/select) for using expressions in queries


# Extensions
Source: https://docs.turso.tech/sql-reference/extensions

Built-in and loadable extensions for additional SQL functions and virtual tables

# Extensions

Turso provides several built-in extensions that add specialized SQL functions and virtual tables. Extensions can be loaded at runtime using the `load_extension()` function.

## Loading Extensions

```sql theme={null}
SELECT load_extension('extension_name');
```

Turso supports loading Turso-native extensions. SQLite `.so`/`.dll` loadable extensions are not supported.

## UUID Extension

The UUID extension provides functions for generating and working with UUIDs. UUIDs are stored as 16-byte BLOBs by default.

### Functions

| Function                   | Parameters | Return Type | Description                                                     |
| -------------------------- | ---------- | ----------- | --------------------------------------------------------------- |
| `uuid4()`                  | none       | BLOB        | Generate a random UUID v4                                       |
| `uuid4_str()`              | none       | TEXT        | Generate a random UUID v4 as string. Alias: `gen_random_uuid()` |
| `uuid7()`                  | none       | BLOB        | Generate a time-ordered UUID v7                                 |
| `uuid7(seconds)`           | INTEGER    | BLOB        | Generate a UUID v7 with specified seconds since epoch           |
| `uuid7_timestamp_ms(uuid)` | BLOB       | INTEGER     | Extract milliseconds since epoch from a UUID v7                 |
| `uuid_str(uuid)`           | BLOB       | TEXT        | Convert a UUID blob to string representation                    |
| `uuid_blob(uuid)`          | TEXT       | BLOB        | Convert a UUID string to blob representation                    |

### Examples

```sql theme={null}
-- Generate UUIDs
SELECT uuid4_str();
-- '550e8400-e29b-41d4-a716-446655440000'

SELECT uuid_str(uuid4());
-- 'f47ac10b-58cc-4372-a567-0e02b2c3d479'

-- UUID v7 is time-ordered (good for primary keys)
SELECT uuid_str(uuid7());
-- '0190a5c0-1234-7abc-8def-0123456789ab'

-- Extract timestamp from UUID v7
SELECT uuid7_timestamp_ms(uuid7());
-- 1720000000000

-- Use in a table
CREATE TABLE documents (
    id BLOB PRIMARY KEY DEFAULT (uuid7()),
    title TEXT
);
INSERT INTO documents (title) VALUES ('My Document');
SELECT uuid_str(id), title FROM documents;
```

## Regexp Extension

The regexp extension provides regular expression functions compatible with [sqlean-regexp](https://github.com/nalgeon/sqlean/blob/main/docs/regexp.md).

### Functions

| Function                                       | Parameters          | Return Type | Description                                                   |
| ---------------------------------------------- | ------------------- | ----------- | ------------------------------------------------------------- |
| `regexp(pattern, source)`                      | TEXT, TEXT          | INTEGER     | Returns 1 if source matches pattern                           |
| `regexp_like(source, pattern)`                 | TEXT, TEXT          | INTEGER     | Returns 1 if source matches pattern (argument order reversed) |
| `regexp_substr(source, pattern)`               | TEXT, TEXT          | TEXT        | Returns the first substring matching pattern, or NULL         |
| `regexp_capture(source, pattern)`              | TEXT, TEXT          | TEXT        | Returns the first capture group match                         |
| `regexp_capture(source, pattern, n)`           | TEXT, TEXT, INTEGER | TEXT        | Returns the n-th capture group match                          |
| `regexp_replace(source, pattern, replacement)` | TEXT, TEXT, TEXT    | TEXT        | Replace matches with replacement string                       |

### Examples

```sql theme={null}
-- Pattern matching
SELECT regexp('[0-9]+', 'abc123def');
-- 1

-- The REGEXP operator uses this extension
SELECT 'hello123' REGEXP '[a-z]+[0-9]+';
-- 1

-- Extract matching substring
SELECT regexp_substr('Price: $42.50', '[0-9]+\.[0-9]+');
-- '42.50'

-- Capture groups
SELECT regexp_capture('2025-03-15', '(\d{4})-(\d{2})-(\d{2})', 1);
-- '2025'

-- Replace
SELECT regexp_replace('Hello World', '[aeiou]', '*');
-- 'H*ll* W*rld'
```

## Vector Extension

<Info>
  **Turso Extension**: Native vector search for similarity search and semantic search applications.
</Info>

The vector extension provides functions for creating, storing, and searching vector embeddings. Vectors are stored as BLOBs.

For the complete function reference, see [Vector Functions](/sql-reference/functions/vector).

### Quick Reference

| Function                      | Description                  |
| ----------------------------- | ---------------------------- |
| `vector32(json)`              | Create a 32-bit float vector |
| `vector64(json)`              | Create a 64-bit float vector |
| `vector_distance_cos(v1, v2)` | Cosine distance              |
| `vector_distance_l2(v1, v2)`  | Euclidean distance           |
| `vector_extract(v)`           | Convert vector to JSON       |
| `vector_concat(v1, v2)`       | Concatenate vectors          |
| `vector_slice(v, start, end)` | Extract vector slice         |

### Example

```sql theme={null}
CREATE TABLE docs (
    id INTEGER PRIMARY KEY,
    content TEXT,
    embedding BLOB
);

INSERT INTO docs VALUES (1, 'database systems', vector32('[0.1, 0.2, 0.3]'));
INSERT INTO docs VALUES (2, 'machine learning', vector32('[0.4, 0.5, 0.6]'));

-- Find similar documents
SELECT content, vector_distance_cos(embedding, vector32('[0.1, 0.25, 0.35]')) AS dist
FROM docs
ORDER BY dist
LIMIT 5;
```

## Time Extension

The time extension is compatible with [sqlean-time](https://github.com/nalgeon/sqlean/blob/main/docs/time.md) and provides advanced time manipulation functions.

<Info>
  A **time value** (`t` in the tables below) is an opaque 13-byte **BLOB**, not an integer — this matches sqlean-time byte-for-byte (`version(1) + seconds(8) + nanoseconds(4)`, with seconds counted from year 0001). Any function that produces a time (`time_now`, `time_date`, `time_add`, …) returns a BLOB, and `typeof(time_now())` is `blob`. Use `time_to_unix` / `time_to_nano` to get an INTEGER, and the `time_fmt_*` functions to get TEXT.

  To store a time value in a `STRICT` table, use a `BLOB` column, or convert it first:

  ```sql theme={null}
  CREATE TABLE X (t BLOB) STRICT;
  INSERT INTO X (t) VALUES (time_now());

  -- Or store a Unix integer:
  CREATE TABLE Y (t INTEGER) STRICT;
  INSERT INTO Y (t) VALUES (time_to_nano(time_now()));
  ```
</Info>

### Key Functions

| Function                    | Parameters       | Return Type | Description                         |
| --------------------------- | ---------------- | ----------- | ----------------------------------- |
| `time_now()`                | none             | BLOB        | Current time                        |
| `time_date(y,m,d,...)`      | INTEGER...       | BLOB        | Create time from components         |
| `make_date(y,m,d)`          | INTEGER...       | BLOB        | Create time from a date             |
| `make_timestamp(...)`       | INTEGER...       | BLOB        | Create time from a timestamp        |
| `time_unix(sec)`            | INTEGER          | BLOB        | Create time from Unix seconds       |
| `to_timestamp(sec)`         | INTEGER          | BLOB        | Create time from Unix seconds       |
| `time_milli(ms)`            | INTEGER          | BLOB        | Create time from milliseconds       |
| `time_micro(us)`            | INTEGER          | BLOB        | Create time from microseconds       |
| `time_nano(ns)`             | INTEGER          | BLOB        | Create time from nanoseconds        |
| `time_to_unix(t)`           | BLOB             | INTEGER     | Convert to Unix seconds             |
| `time_to_milli(t)`          | BLOB             | INTEGER     | Convert to milliseconds             |
| `time_to_micro(t)`          | BLOB             | INTEGER     | Convert to microseconds             |
| `time_to_nano(t)`           | BLOB             | INTEGER     | Convert to nanoseconds              |
| `time_get_year(t)`          | BLOB             | INTEGER     | Extract year                        |
| `time_get_month(t)`         | BLOB             | INTEGER     | Extract month (1-12)                |
| `time_get_day(t)`           | BLOB             | INTEGER     | Extract day (1-31)                  |
| `time_get_hour(t)`          | BLOB             | INTEGER     | Extract hour (0-23)                 |
| `time_get_minute(t)`        | BLOB             | INTEGER     | Extract minute (0-59)               |
| `time_get_second(t)`        | BLOB             | INTEGER     | Extract second (0-59)               |
| `time_get_weekday(t)`       | BLOB             | INTEGER     | Day of week (0=Sunday)              |
| `time_get(t, field)`        | BLOB, TEXT       | INTEGER     | Extract named field                 |
| `time_add(t, d)`            | BLOB, INTEGER    | BLOB        | Add duration                        |
| `time_add_date(t, y, m, d)` | BLOB, INTEGER... | BLOB        | Add years/months/days               |
| `time_sub(t, u)`            | BLOB, BLOB       | INTEGER     | Difference between times (duration) |
| `time_since(t)`             | BLOB             | INTEGER     | Duration since time                 |
| `time_until(t)`             | BLOB             | INTEGER     | Duration until time                 |
| `time_trunc(t, field)`      | BLOB, TEXT       | BLOB        | Truncate to field                   |
| `time_round(t, d)`          | BLOB, INTEGER    | BLOB        | Round to nearest duration           |
| `time_after(t, u)`          | BLOB, BLOB       | INTEGER     | 1 if t is after u                   |
| `time_before(t, u)`         | BLOB, BLOB       | INTEGER     | 1 if t is before u                  |
| `time_compare(t, u)`        | BLOB, BLOB       | INTEGER     | -1, 0, or 1                         |
| `time_equal(t, u)`          | BLOB, BLOB       | INTEGER     | 1 if equal                          |
| `time_fmt_iso(t)`           | BLOB             | TEXT        | Format as ISO 8601                  |
| `time_fmt_datetime(t)`      | BLOB             | TEXT        | Format as datetime                  |
| `time_fmt_date(t)`          | BLOB             | TEXT        | Format as date                      |
| `time_fmt_time(t)`          | BLOB             | TEXT        | Format as time                      |
| `time_parse(s)`             | TEXT             | BLOB        | Parse time string                   |

#### Duration Constants

| Function   | Value         |
| ---------- | ------------- |
| `dur_ns()` | 1 nanosecond  |
| `dur_us()` | 1 microsecond |
| `dur_ms()` | 1 millisecond |
| `dur_s()`  | 1 second      |
| `dur_m()`  | 1 minute      |
| `dur_h()`  | 1 hour        |

### Example

```sql theme={null}
-- Current time formatted
SELECT time_fmt_iso(time_now());
-- '2025-03-15T10:30:00Z'

-- Date arithmetic
SELECT time_fmt_date(time_add_date(time_now(), 0, 1, 0));
-- add 1 month to today

-- Duration since a timestamp
SELECT time_since(time_date(2025, 1, 1, 0, 0, 0)) / dur_h();
-- hours since Jan 1, 2025
```

## Full-Text Search (FTS)

<Info>
  **Turso Extension**: Full-text search powered by Tantivy, replacing SQLite's FTS3/FTS4/FTS5.
</Info>

Turso provides full-text search through the FTS index method. For the complete reference including query syntax, tokenizers, and scoring, see [FTS Functions](/sql-reference/functions/fts).

### Quick Reference

```sql theme={null}
-- Create an FTS index
CREATE INDEX idx ON articles USING fts (title, body);

-- Search
SELECT title, fts_score('idx') AS score
FROM articles
WHERE fts_match('idx', 'database search')
ORDER BY score;

-- Highlighted results
SELECT fts_highlight('idx', 0, '<b>', '</b>') AS title
FROM articles
WHERE fts_match('idx', 'database');
```

## CSV Extension

The CSV extension provides a virtual table for reading CSV files.

### Usage

```sql theme={null}
CREATE VIRTUAL TABLE temp.csv_data USING csv(
    filename='/path/to/data.csv',
    header=yes
);

SELECT * FROM csv_data;
```

| Parameter  | Description                                  |
| ---------- | -------------------------------------------- |
| `filename` | Path to the CSV file                         |
| `header`   | `yes` if the first row contains column names |

## Percentile Extension

Statistical aggregate functions for computing percentiles.

### Functions

| Function                | Parameters   | Return Type | Description                                |
| ----------------------- | ------------ | ----------- | ------------------------------------------ |
| `median(X)`             | column       | REAL        | Median value (50th percentile)             |
| `percentile(Y, P)`      | column, REAL | REAL        | P-th percentile of Y (P between 0 and 100) |
| `percentile_cont(Y, P)` | column, REAL | REAL        | Continuous percentile (interpolated)       |
| `percentile_disc(Y, P)` | column, REAL | REAL        | Discrete percentile (nearest value)        |

### Examples

```sql theme={null}
CREATE TABLE scores (value REAL);
INSERT INTO scores VALUES (10), (20), (30), (40), (50);

SELECT median(value) FROM scores;
-- 30.0

SELECT percentile(value, 75) FROM scores;
-- 40.0

SELECT percentile_cont(value, 0.25) FROM scores;
-- 20.0
```

## Crypto Extension

The crypto extension provides hashing and encoding functions, compatible with [sqlean-crypto](https://github.com/nalgeon/sqlean/blob/main/docs/crypto.md).

### Functions

| Function                      | Parameters | Return Type | Description                                                                  |
| ----------------------------- | ---------- | ----------- | ---------------------------------------------------------------------------- |
| `crypto_md5(data)`            | TEXT/BLOB  | BLOB        | MD5 hash                                                                     |
| `crypto_sha1(data)`           | TEXT/BLOB  | BLOB        | SHA-1 hash                                                                   |
| `crypto_sha256(data)`         | TEXT/BLOB  | BLOB        | SHA-256 hash                                                                 |
| `crypto_sha384(data)`         | TEXT/BLOB  | BLOB        | SHA-384 hash                                                                 |
| `crypto_sha512(data)`         | TEXT/BLOB  | BLOB        | SHA-512 hash                                                                 |
| `crypto_blake3(data)`         | TEXT/BLOB  | BLOB        | BLAKE3 hash                                                                  |
| `crypto_encode(data, format)` | BLOB, TEXT | TEXT        | Encode a blob as text using `format` (e.g. `hex`, `base32`, `base64`, `url`) |
| `crypto_decode(text, format)` | TEXT, TEXT | BLOB        | Decode text in the given `format` back into a blob                           |

```sql theme={null}
-- Hash a value and encode it as hex
SELECT crypto_encode(crypto_sha256('hello'), 'hex');

-- Base64 round-trip
SELECT crypto_decode(crypto_encode(x'CAFE', 'base64'), 'base64');
```

## Fuzzy Extension

The fuzzy extension provides string-similarity, distance, and phonetic functions, compatible with [sqlean-fuzzy](https://github.com/nalgeon/sqlean/blob/main/docs/fuzzy.md).

### Functions

| Function               | Parameters | Return Type | Description                             |
| ---------------------- | ---------- | ----------- | --------------------------------------- |
| `fuzzy_editdist(a, b)` | TEXT, TEXT | INTEGER     | Spellcheck-style edit distance          |
| `fuzzy_leven(a, b)`    | TEXT, TEXT | INTEGER     | Levenshtein distance                    |
| `fuzzy_damlev(a, b)`   | TEXT, TEXT | INTEGER     | Damerau-Levenshtein distance            |
| `fuzzy_osadist(a, b)`  | TEXT, TEXT | INTEGER     | Optimal string alignment distance       |
| `fuzzy_hamming(a, b)`  | TEXT, TEXT | INTEGER     | Hamming distance                        |
| `fuzzy_jarowin(a, b)`  | TEXT, TEXT | REAL        | Jaro-Winkler similarity                 |
| `fuzzy_soundex(s)`     | TEXT       | TEXT        | Soundex phonetic code                   |
| `fuzzy_rsoundex(s)`    | TEXT       | TEXT        | Refined Soundex code                    |
| `fuzzy_caver(s)`       | TEXT       | TEXT        | Caverphone phonetic code                |
| `fuzzy_phonetic(s)`    | TEXT       | TEXT        | US Census phonetic code                 |
| `fuzzy_translit(s)`    | TEXT       | TEXT        | Transliterate Unicode text to ASCII     |
| `fuzzy_script(s)`      | TEXT       | INTEGER     | Identify the writing script of the text |
| `phonetics(a, b)`      | TEXT, TEXT | INTEGER     | 1 if two strings are phonetically equal |

```sql theme={null}
-- Distance between two strings
SELECT fuzzy_leven('awesome', 'aewsime');
-- 3

-- Phonetic encoding
SELECT fuzzy_soundex('Robert');
-- 'R163'
```

## IP Address Extension

The ipaddr extension provides functions for working with IPv4 and IPv6 addresses and networks.

### Functions

| Function                  | Parameters | Return Type | Description                                         |
| ------------------------- | ---------- | ----------- | --------------------------------------------------- |
| `ipfamily(ip)`            | TEXT       | INTEGER     | Returns 4 for IPv4 or 6 for IPv6                    |
| `iphost(ip)`              | TEXT       | TEXT        | The host portion of an address (strips any netmask) |
| `ipmasklen(cidr)`         | TEXT       | INTEGER     | The prefix (mask) length of a CIDR network          |
| `ipnetwork(cidr)`         | TEXT       | TEXT        | The network address of a CIDR value                 |
| `ipcontains(network, ip)` | TEXT, TEXT | INTEGER     | 1 if the CIDR network contains the given address    |

```sql theme={null}
SELECT ipfamily('192.168.1.1');            -- 4
SELECT ipnetwork('192.168.1.42/24');       -- '192.168.1.0/24'
SELECT ipcontains('10.0.0.0/8', '10.1.2.3'); -- 1
```

## generate\_series

The `generate_series` table-valued function generates a sequence of integers.

```sql theme={null}
SELECT value FROM generate_series(start, stop);
SELECT value FROM generate_series(start, stop, step);
```

| Parameter | Type    | Description                 |
| --------- | ------- | --------------------------- |
| start     | INTEGER | Starting value (inclusive)  |
| stop      | INTEGER | Ending value (inclusive)    |
| step      | INTEGER | Step increment (default: 1) |

```sql theme={null}
SELECT value FROM generate_series(1, 5);
-- 1, 2, 3, 4, 5

SELECT value FROM generate_series(0, 10, 2);
-- 0, 2, 4, 6, 8, 10

SELECT value FROM generate_series(10, 1, -3);
-- 10, 7, 4, 1
```

## See Also

* [Vector Functions](/sql-reference/functions/vector) for detailed vector function reference
* [FTS Functions](/sql-reference/functions/fts) for full-text search function reference
* [Aggregate Functions](/sql-reference/functions/aggregate) for percentile functions used with GROUP BY
* [Compatibility](/sql-reference/compatibility) for extension support status


# Aggregate Functions
Source: https://docs.turso.tech/sql-reference/functions/aggregate

Built-in aggregate functions for computing summary values over groups of rows

# Aggregate Functions

Aggregate functions compute a single result from a set of input rows. They are typically used with the `GROUP BY` clause in SELECT statements, but can also be used without `GROUP BY` to aggregate over all rows. When used in a SELECT with non-aggregate columns and no `GROUP BY`, the result is a single row.

All standard aggregate functions ignore NULL values (except `count(*)`). If every input value is NULL, the aggregate returns NULL, with the exception of `count()` (which returns 0) and `total()` (which returns 0.0).

## Function Reference

| Function             | Return Type     | Description                                                                      |
| -------------------- | --------------- | -------------------------------------------------------------------------------- |
| `array_agg(X)`       | BLOB (array)    | Collects all values of X into an array (Turso extension)                         |
| `avg(X)`             | REAL            | Average of all non-NULL values of X                                              |
| `count(X)`           | INTEGER         | Count of rows where X is not NULL                                                |
| `count(*)`           | INTEGER         | Count of all rows in the group                                                   |
| `group_concat(X)`    | TEXT            | Concatenation of all non-NULL values of X, separated by commas                   |
| `group_concat(X, Y)` | TEXT            | Concatenation of all non-NULL values of X, separated by Y                        |
| `string_agg(X, Y)`   | TEXT            | Alias for `group_concat(X, Y)`                                                   |
| `max(X)`             | same as X       | Maximum non-NULL value of X                                                      |
| `min(X)`             | same as X       | Minimum non-NULL value of X                                                      |
| `sum(X)`             | INTEGER or REAL | Sum of all non-NULL values of X. Returns NULL if all values are NULL             |
| `total(X)`           | REAL            | Sum of all non-NULL values of X. Always returns REAL, 0.0 if all values are NULL |

## Detailed Descriptions and Examples

The examples below use the following table:

```sql theme={null}
CREATE TABLE sales (
    id INTEGER PRIMARY KEY,
    region TEXT,
    product TEXT,
    amount REAL,
    quantity INTEGER
);

INSERT INTO sales VALUES
    (1, 'North', 'Widget', 100.00, 5),
    (2, 'North', 'Gadget', 250.00, 2),
    (3, 'South', 'Widget', 150.00, 8),
    (4, 'South', 'Gadget', NULL, 3),
    (5, 'North', 'Widget', 200.00, NULL),
    (6, 'South', 'Widget', 175.00, 6);
```

### avg(X)

```sql theme={null}
avg(X)
```

Returns the average of all non-NULL values of X as a REAL (floating-point) number. Returns NULL if all values are NULL.

| Parameter | Type        | Description               |
| --------- | ----------- | ------------------------- |
| X         | any numeric | The expression to average |

**Return type:** REAL

```sql theme={null}
SELECT avg(amount) FROM sales;
-- 175.0 (sum of non-NULL amounts / count of non-NULL amounts = 875.0 / 5)

SELECT region, avg(amount) AS avg_amount
FROM sales
GROUP BY region;
```

| region | avg\_amount      |
| ------ | ---------------- |
| North  | 183.333333333333 |
| South  | 162.5            |

<Info>
  `avg(X)` ignores NULL values in both the sum and the count. In the example above, the South region has three non-NULL amounts (150 + 175 = 325, but also the NULL row is excluded), so the average is computed over the non-NULL values only.
</Info>

### count(X) and count(\*)

```sql theme={null}
count(X)
count(*)
```

`count(X)` returns the number of rows where X is not NULL. `count(*)` returns the total number of rows in the group, including rows with NULL values.

| Parameter | Type    | Description                                 |
| --------- | ------- | ------------------------------------------- |
| X         | any     | The expression to count non-NULL values for |
| `*`       | special | Counts all rows regardless of NULL          |

**Return type:** INTEGER

```sql theme={null}
SELECT count(*) FROM sales;
-- 6 (all rows)

SELECT count(amount) FROM sales;
-- 5 (excludes the row where amount is NULL)

SELECT region, count(*) AS total_rows, count(amount) AS rows_with_amount
FROM sales
GROUP BY region;
```

| region | total\_rows | rows\_with\_amount |
| ------ | ----------- | ------------------ |
| North  | 3           | 3                  |
| South  | 3           | 2                  |

### group\_concat(X) and group\_concat(X, Y)

```sql theme={null}
group_concat(X)
group_concat(X, Y)
```

Concatenates all non-NULL values of X into a single string. The default separator is a comma (`,`). When Y is provided, it is used as the separator instead.

| Parameter | Type | Description                                |
| --------- | ---- | ------------------------------------------ |
| X         | any  | The expression whose values to concatenate |
| Y         | TEXT | Separator string (default: `","`)          |

**Return type:** TEXT

```sql theme={null}
SELECT group_concat(product) FROM sales;
-- 'Widget,Gadget,Widget,Gadget,Widget,Widget'

SELECT group_concat(DISTINCT product) FROM sales;
-- 'Widget,Gadget'

SELECT region, group_concat(product, ' | ') AS products
FROM sales
GROUP BY region;
```

| region | products                   |
| ------ | -------------------------- |
| North  | Widget \| Gadget \| Widget |
| South  | Widget \| Gadget \| Widget |

### string\_agg(X, Y)

```sql theme={null}
string_agg(X, Y)
```

Alias for `group_concat(X, Y)`. Provided for compatibility with PostgreSQL.

| Parameter | Type | Description                                |
| --------- | ---- | ------------------------------------------ |
| X         | any  | The expression whose values to concatenate |
| Y         | TEXT | Separator string                           |

**Return type:** TEXT

```sql theme={null}
SELECT region, string_agg(product, ', ') AS products
FROM sales
GROUP BY region;
```

| region | products               |
| ------ | ---------------------- |
| North  | Widget, Gadget, Widget |
| South  | Widget, Gadget, Widget |

### max(X) and min(X)

```sql theme={null}
max(X)
min(X)
```

`max(X)` returns the maximum non-NULL value of X. `min(X)` returns the minimum non-NULL value of X. Values are compared using the standard SQLite comparison rules. Returns NULL if all values are NULL.

| Parameter | Type | Description                                            |
| --------- | ---- | ------------------------------------------------------ |
| X         | any  | The expression to find the maximum or minimum value of |

**Return type:** Same as the input type

```sql theme={null}
SELECT max(amount), min(amount) FROM sales;
-- max: 250.0, min: 100.0

SELECT region, max(amount) AS highest, min(amount) AS lowest
FROM sales
GROUP BY region;
```

| region | highest | lowest |
| ------ | ------- | ------ |
| North  | 250.0   | 100.0  |
| South  | 175.0   | 150.0  |

<Info>
  When `max(X)` or `min(X)` is called with a single argument in an aggregate context, it acts as an aggregate function. When called with two or more arguments (e.g., `max(a, b, c)`), it acts as a [scalar function](/sql-reference/functions/scalar) and returns the largest argument.
</Info>

### sum(X) and total(X)

```sql theme={null}
sum(X)
total(X)
```

Both functions return the sum of all non-NULL values of X. They differ in return type and behavior when all values are NULL.

| Parameter | Type        | Description           |
| --------- | ----------- | --------------------- |
| X         | any numeric | The expression to sum |

**Return type:**

* `sum(X)`: INTEGER if all non-NULL inputs are integers and no overflow occurs, otherwise REAL. Returns NULL if all values are NULL.
* `total(X)`: Always REAL. Returns 0.0 if all values are NULL.

```sql theme={null}
SELECT sum(amount), total(amount) FROM sales;
-- sum: 875.0, total: 875.0

SELECT sum(quantity), total(quantity) FROM sales;
-- sum: 24, total: 24.0
```

#### Difference between sum() and total()

The key difference appears when all values in the group are NULL:

```sql theme={null}
CREATE TABLE empty_amounts (val REAL);
INSERT INTO empty_amounts VALUES (NULL), (NULL);

SELECT sum(val) FROM empty_amounts;
-- NULL

SELECT total(val) FROM empty_amounts;
-- 0.0
```

This makes `total()` convenient when you need a numeric result even for empty or all-NULL groups:

```sql theme={null}
SELECT
    region,
    sum(amount) AS sum_amount,
    total(amount) AS total_amount
FROM sales
GROUP BY region;
```

| region | sum\_amount | total\_amount |
| ------ | ----------- | ------------- |
| North  | 550.0       | 550.0         |
| South  | 325.0       | 325.0         |

```sql theme={null}
-- total() is useful in arithmetic to avoid NULL propagation
SELECT total(amount) * 1.1 AS with_tax FROM sales;
-- 962.5

-- sum() with all NULLs would produce NULL, making the multiplication NULL too
```

<Info>
  `sum(X)` returns an integer result when all inputs are integers and the result fits within a 64-bit signed integer. If the sum overflows, it automatically switches to REAL. Use `total(X)` when you always want a floating-point result.
</Info>

## Using Aggregates with GROUP BY

The `GROUP BY` clause partitions rows into groups. Each aggregate function is computed independently for each group.

```sql theme={null}
SELECT
    region,
    product,
    count(*) AS order_count,
    sum(amount) AS total_sales,
    avg(amount) AS avg_sale,
    min(amount) AS min_sale,
    max(amount) AS max_sale
FROM sales
GROUP BY region, product;
```

| region | product | order\_count | total\_sales | avg\_sale | min\_sale | max\_sale |
| ------ | ------- | ------------ | ------------ | --------- | --------- | --------- |
| North  | Gadget  | 1            | 250.0        | 250.0     | 250.0     | 250.0     |
| North  | Widget  | 2            | 300.0        | 150.0     | 100.0     | 200.0     |
| South  | Gadget  | 1            | NULL         | NULL      | NULL      | NULL      |
| South  | Widget  | 2            | 325.0        | 162.5     | 150.0     | 175.0     |

## Filtering Groups with HAVING

The `HAVING` clause filters groups after aggregation. Use `WHERE` to filter rows before aggregation and `HAVING` to filter groups after.

```sql theme={null}
SELECT region, sum(amount) AS total_sales
FROM sales
WHERE amount IS NOT NULL
GROUP BY region
HAVING sum(amount) > 400;
```

| region | total\_sales |
| ------ | ------------ |
| North  | 550.0        |

## Aggregates with DISTINCT

The `DISTINCT` keyword causes the aggregate to consider only unique non-NULL values.

```sql theme={null}
SELECT count(product) FROM sales;
-- 6 (all non-NULL product values)

SELECT count(DISTINCT product) FROM sales;
-- 2 (only 'Widget' and 'Gadget')

SELECT group_concat(DISTINCT product) FROM sales;
-- 'Widget,Gadget'
```

## Aggregates as Window Functions

All standard aggregate functions can be used as window functions. When used with an `OVER` clause, the function computes a running or partitioned result without collapsing rows.

```sql theme={null}
SELECT
    id,
    region,
    amount,
    sum(amount) OVER (PARTITION BY region ORDER BY id) AS running_total,
    count(*) OVER (PARTITION BY region) AS region_count
FROM sales
ORDER BY region, id;
```

| id | region | amount | running\_total | region\_count |
| -- | ------ | ------ | -------------- | ------------- |
| 1  | North  | 100.0  | 100.0          | 3             |
| 2  | North  | 250.0  | 350.0          | 3             |
| 5  | North  | 200.0  | 550.0          | 3             |
| 3  | South  | 150.0  | 150.0          | 3             |
| 4  | South  | NULL   | 150.0          | 3             |
| 6  | South  | 175.0  | 325.0          | 3             |

## Ordered-Set Aggregates (WITHIN GROUP)

<Info>
  `mode`, `percentile_cont` and `percentile_disc` with `WITHIN GROUP (ORDER BY ...)` are built in and available by default — no extension required. The syntax and results match PostgreSQL. (Standard SQLite does not support `WITHIN GROUP`.)
</Info>

Ordered-set aggregates compute a result over the values of an `ORDER BY` expression, sorted within each group:

```sql theme={null}
mode() WITHIN GROUP (ORDER BY sort_expr)
percentile_cont(fraction) WITHIN GROUP (ORDER BY sort_expr)
percentile_disc(fraction) WITHIN GROUP (ORDER BY sort_expr)
```

The `ORDER BY` expression is the value being aggregated. For the percentile functions, the argument before `WITHIN GROUP` is the percentile *fraction*. NULL values of the `ORDER BY` expression are ignored; if there are no non-NULL values, the result is NULL.

### mode()

```sql theme={null}
mode() WITHIN GROUP (ORDER BY X)
```

Returns the most frequent value of X. If several values are equally frequent, the smallest is returned. Works with any type and returns the value in its original type.

| Parameter | Type | Description                                        |
| --------- | ---- | -------------------------------------------------- |
| X         | any  | The expression whose most frequent value to return |

**Return type:** same as X

```sql theme={null}
SELECT mode() WITHIN GROUP (ORDER BY product) FROM sales;
-- 'Widget' (the most frequent product)

SELECT region, mode() WITHIN GROUP (ORDER BY product) AS top_product
FROM sales
GROUP BY region;
```

| region | top\_product |
| ------ | ------------ |
| North  | Widget       |
| South  | Widget       |

<Info>
  `mode()` is only valid with `WITHIN GROUP`; calling `mode(X)` without it is an error.
</Info>

### percentile\_cont(fraction) and percentile\_disc(fraction)

```sql theme={null}
percentile_cont(fraction) WITHIN GROUP (ORDER BY X)
percentile_disc(fraction) WITHIN GROUP (ORDER BY X)
```

Compute the `fraction`-th percentile of X, where `fraction` is between 0.0 and 1.0:

* `percentile_cont` returns a continuous, interpolated value (always REAL).
* `percentile_disc` returns the actual element at the discrete percentile position, in its original type.

| Parameter | Type                                                  | Description                               |
| --------- | ----------------------------------------------------- | ----------------------------------------- |
| fraction  | REAL                                                  | Percentile fraction, 0.0 to 1.0           |
| X         | numeric (`percentile_cont`) / any (`percentile_disc`) | The values to compute the percentile over |

**Return type:** REAL for `percentile_cont`; same as X for `percentile_disc`

```sql theme={null}
-- Median, interpolated vs. an actual data point
SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY amount) FROM sales;  -- 175.0
SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY amount) FROM sales;  -- 175.0

-- Quartiles per region
SELECT region,
       percentile_cont(0.25) WITHIN GROUP (ORDER BY amount) AS q1,
       percentile_cont(0.75) WITHIN GROUP (ORDER BY amount) AS q3
FROM sales
WHERE amount IS NOT NULL
GROUP BY region;
```

The fraction must be a constant with respect to the rows being aggregated — a literal, a constant expression, or a parameter. It cannot reference the columns being aggregated, and an out-of-range constant fraction is reported regardless of how many rows match:

```sql theme={null}
SELECT percentile_cont(2.0) WITHIN GROUP (ORDER BY amount) FROM sales;
-- Error: percentile value 2 is not between 0 and 1
```

### Collation

Text values are ordered using the applicable collation — an explicit `COLLATE` on the `ORDER BY` expression, the column's declared collation, or `BINARY` by default — consistent with `ORDER BY`.

### Notes and limitations

* A single `ORDER BY` expression is required. Multiple expressions, `DESC`, and `NULLS FIRST/LAST` inside `WITHIN GROUP` are not yet supported.
* A subquery as the fraction argument is not currently supported.
* These aggregates work with `GROUP BY`, `FILTER (WHERE ...)`, `HAVING`, subqueries, CTEs, joins, and attached databases.
* The **percentile** extension also provides non-standard two-argument forms, `percentile_cont(Y, P)` and `percentile_disc(Y, P)` (see [Extension Aggregate Functions](#extension-aggregate-functions)). The `WITHIN GROUP` forms above are the SQL-standard versions and require no extension.

## Turso Extension: array\_agg(X)

<Info>
  `array_agg(X)` is a Turso extension and is not part of standard SQLite. It is available by default in Turso without loading any additional extensions.
</Info>

```sql theme={null}
array_agg(X)
```

Collects all values of X (including NULLs) into an array. Returns NULL if the group is empty.

| Parameter | Type | Description                                          |
| --------- | ---- | ---------------------------------------------------- |
| X         | any  | The expression whose values to collect into an array |

**Return type:** BLOB (array)

```sql theme={null}
SELECT array_agg(name) FROM users;
-- ["Alice","Bob","Charlie"]

SELECT department, array_agg(name) FROM employees GROUP BY department;
```

| department  | array\_agg(name)           |
| ----------- | -------------------------- |
| Engineering | \["Alice","Bob"]           |
| Sales       | \["Charlie","Diana","Eve"] |

```sql theme={null}
-- Control ordering with a subquery
SELECT array_agg(name) FROM (SELECT name FROM users ORDER BY name);

-- Combine with array_length
SELECT array_length(array_agg(name)) FROM users;
-- 3
```

For more array functions, see [Array Functions](/sql-reference/functions/array).

## Turso Extension: stddev(X)

<Info>
  `stddev(X)` is a Turso extension and is not part of standard SQLite. It is available by default in Turso without loading any additional extensions.
</Info>

```sql theme={null}
stddev(X)
```

Returns the population standard deviation of all non-NULL values of X. Returns NULL if there are no non-NULL values.

| Parameter | Type        | Description                                         |
| --------- | ----------- | --------------------------------------------------- |
| X         | any numeric | The expression to compute the standard deviation of |

**Return type:** REAL

```sql theme={null}
SELECT stddev(amount) FROM sales;

SELECT region, avg(amount) AS mean, stddev(amount) AS std_dev
FROM sales
GROUP BY region;
```

## Extension Aggregate Functions

The following aggregate functions are available through the **percentile** extension. Load it before use.

<Info>
  These functions require the `percentile` extension. Load it with `SELECT load_extension('./percentile');` or by configuring your connection to auto-load it.
</Info>

### median(X)

```sql theme={null}
median(X)
```

Returns the median (middle value) of all non-NULL values of X.

| Parameter | Type        | Description                          |
| --------- | ----------- | ------------------------------------ |
| X         | any numeric | The expression to find the median of |

**Return type:** REAL

```sql theme={null}
SELECT median(amount) FROM sales;
-- 175.0

SELECT region, median(amount) AS median_amount
FROM sales
GROUP BY region;
```

### percentile(Y, P)

```sql theme={null}
percentile(Y, P)
```

Returns the P-th percentile of all non-NULL values of Y. Uses linear interpolation between adjacent values.

| Parameter | Type        | Description                               |
| --------- | ----------- | ----------------------------------------- |
| Y         | any numeric | The values to compute the percentile over |
| P         | REAL        | The percentile to compute (0.0 to 100.0)  |

**Return type:** REAL

```sql theme={null}
SELECT percentile(amount, 50) FROM sales;   -- 50th percentile (median)
SELECT percentile(amount, 90) FROM sales;   -- 90th percentile
SELECT percentile(amount, 25) FROM sales;   -- 25th percentile (Q1)
```

### percentile\_cont(Y, P) and percentile\_disc(Y, P)

```sql theme={null}
percentile_cont(Y, P)
percentile_disc(Y, P)
```

These two-argument forms are a convenience provided by the **percentile** extension. The SQL-standard spelling — `percentile_cont(P) WITHIN GROUP (ORDER BY Y)` — is built in and needs no extension; see [Ordered-Set Aggregates](#ordered-set-aggregates-within-group) above. `percentile_cont` uses a continuous (interpolated) distribution, while `percentile_disc` returns a discrete input value.

| Parameter | Type        | Description                               |
| --------- | ----------- | ----------------------------------------- |
| Y         | any numeric | The values to compute the percentile over |
| P         | REAL        | The percentile fraction (0.0 to 1.0)      |

**Return type:** REAL

```sql theme={null}
-- Continuous percentile (interpolates between values)
SELECT percentile_cont(amount, 0.5) FROM sales;

-- Discrete percentile (returns an actual input value)
SELECT percentile_disc(amount, 0.5) FROM sales;
```

<Info>
  Note the difference in P range: `percentile(Y, P)` takes P from 0 to 100, while `percentile_cont` and `percentile_disc` take P from 0.0 to 1.0.
</Info>

## See Also

* [Scalar Functions](/sql-reference/functions/scalar) for per-row functions
* [Expressions](/sql-reference/expressions) for operator syntax, CAST, CASE, and subqueries
* [SELECT](/sql-reference/statements/select) for GROUP BY, HAVING, and window function syntax


# Array Functions
Source: https://docs.turso.tech/sql-reference/functions/array

Create, manipulate, and query array values

# Array Functions

<Info>
  **Turso Extension**: Array types and functions are a Turso-specific feature for working with ordered collections directly in SQL. These functions are not part of the SQLite standard. Array columns require [STRICT tables](/sql-reference/statements/create-table#strict-tables).
</Info>

Turso provides built-in support for array columns and a set of functions for constructing, querying, and transforming arrays. Arrays are stored internally as compact record-format BLOBs and displayed as JSON arrays on output.

***

## Array Construction

### ARRAY\[] Literal

Constructs an array from a list of expressions.

```sql theme={null}
ARRAY[expr1, expr2, ...]
```

```sql theme={null}
SELECT ARRAY[1, 2, 3];
-- [1,2,3]

SELECT ARRAY['hello', 'world'];
-- ["hello","world"]

SELECT ARRAY[];
-- []
```

Arrays can also be inserted as JSON text:

```sql theme={null}
CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT;

INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']);
INSERT INTO t VALUES (2, '["x", "y", "z"]');

SELECT tags FROM t ORDER BY id;
-- ["a","b","c"]
-- ["x","y","z"]
```

### string\_to\_array

Splits a string into an array using a delimiter.

```sql theme={null}
string_to_array(text, delimiter)
string_to_array(text, delimiter, null_string)
```

| Parameter     | Type | Description                                                           |
| ------------- | ---- | --------------------------------------------------------------------- |
| `text`        | TEXT | The string to split                                                   |
| `delimiter`   | TEXT | The delimiter to split on. If NULL, splits into individual characters |
| `null_string` | TEXT | Optional. Elements matching this string are replaced with NULL        |

**Returns:** BLOB -- an array value.

```sql theme={null}
SELECT string_to_array('one,two,three', ',');
-- ["one","two","three"]

SELECT string_to_array('hello', NULL);
-- ["h","e","l","l","o"]

SELECT string_to_array('a,NULL,b', ',', 'NULL');
-- ["a",null,"b"]
```

***

## Element Access

### Subscript Operator \[]

Access individual elements using zero-based indexing. Returns NULL for out-of-bounds or negative indices.

```sql theme={null}
array_expr[index]
```

```sql theme={null}
CREATE TABLE t (id INTEGER PRIMARY KEY, items TEXT[]) STRICT;
INSERT INTO t VALUES (1, '["first", "second", "third"]');

SELECT items[0], items[1], items[2] FROM t;
-- first|second|third

SELECT items[100] FROM t;
-- NULL

SELECT items[-1] FROM t;
-- NULL
```

Subscripts can be chained for multi-dimensional arrays:

```sql theme={null}
CREATE TABLE m (id INTEGER PRIMARY KEY, matrix INTEGER[][]) STRICT;
INSERT INTO m VALUES (1, ARRAY[ARRAY[1,2], ARRAY[3,4]]);

SELECT matrix[0][1] FROM m;
-- 2

SELECT matrix[1][0] FROM m;
-- 3
```

### Slice Operator \[start:end]

Extract a sub-array using half-open range `[start, end)`.

```sql theme={null}
array_expr[start:end]
```

```sql theme={null}
CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT;
INSERT INTO t VALUES (1, ARRAY['a','b','c','d']);

SELECT tags[1:3] FROM t;
-- ["b","c"]
```

***

## Scalar Functions

### array\_length

Returns the number of elements along a given dimension of an array.

```sql theme={null}
array_length(array)
array_length(array, dimension)
```

| Parameter   | Type      | Description                                                                  |
| ----------- | --------- | ---------------------------------------------------------------------------- |
| `array`     | BLOB/TEXT | The array to measure                                                         |
| `dimension` | INTEGER   | Optional. 1-based dimension index. Defaults to `1` (the outermost dimension) |

**Returns:** INTEGER -- the number of elements along `dimension`. NULL if the input is NULL, if `dimension < 1`, or if `dimension` exceeds the array's nesting depth.

For multi-dimensional arrays the walker peeks into element zero at each level, so values are reported as if every inner array at a given depth has the same length (the layout Turso materialises).

```sql theme={null}
SELECT array_length(ARRAY[10, 20, 30]);
-- 3

SELECT array_length('[]');
-- 0

SELECT array_length(NULL);
-- NULL

SELECT array_length(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]], 1);
-- 2 (outer dimension)

SELECT array_length(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]], 2);
-- 3 (inner dimension)

SELECT array_length(ARRAY[1, 2, 3], 2);
-- NULL (dimension beyond the array's depth)

SELECT array_length(ARRAY[1, 2, 3], 0);
-- NULL (dimension < 1)
```

### array\_upper

Returns the upper bound (inclusive) of a given dimension of an array.

```sql theme={null}
array_upper(array, dimension)
```

| Parameter   | Type      | Description             |
| ----------- | --------- | ----------------------- |
| `array`     | BLOB/TEXT | The array to measure    |
| `dimension` | INTEGER   | 1-based dimension index |

**Returns:** INTEGER -- the upper bound of `dimension`. NULL with the same conditions as `array_length`.

Turso arrays are always 1-indexed, so `array_upper(arr, dim)` is equivalent to `array_length(arr, dim)` for every valid input. The function is provided for PostgreSQL compatibility.

```sql theme={null}
SELECT array_upper(ARRAY[10, 20, 30], 1);
-- 3

SELECT array_upper(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]], 2);
-- 3

SELECT array_upper(ARRAY[1, 2, 3], 2);
-- NULL
```

### array\_append

Appends an element to the end of an array.

```sql theme={null}
array_append(array, element)
```

| Parameter | Type      | Description                                                            |
| --------- | --------- | ---------------------------------------------------------------------- |
| `array`   | BLOB/TEXT | The array to append to. If NULL, a new single-element array is created |
| `element` | any       | The element to append                                                  |

**Returns:** BLOB -- a new array with the element appended.

```sql theme={null}
SELECT array_append('[1, 2]', 3);
-- [1,2,3]

SELECT array_append('[]', 'x');
-- ["x"]

SELECT array_append(NULL, 'x');
-- ["x"]
```

### array\_prepend

Prepends an element to the beginning of an array.

```sql theme={null}
array_prepend(element, array)
```

| Parameter | Type      | Description             |
| --------- | --------- | ----------------------- |
| `element` | any       | The element to prepend  |
| `array`   | BLOB/TEXT | The array to prepend to |

**Returns:** BLOB -- a new array with the element prepended.

```sql theme={null}
SELECT array_prepend('z', '["a", "b"]');
-- ["z","a","b"]
```

### array\_cat

Concatenates two arrays.

```sql theme={null}
array_cat(array1, array2)
```

| Parameter | Type      | Description      |
| --------- | --------- | ---------------- |
| `array1`  | BLOB/TEXT | The first array  |
| `array2`  | BLOB/TEXT | The second array |

**Returns:** BLOB -- a new array containing all elements from both arrays.

```sql theme={null}
SELECT array_cat('[1, 2]', '[3, 4]');
-- [1,2,3,4]

SELECT array_cat('[]', '[1, 2]');
-- [1,2]
```

The `||` operator also works for array concatenation:

```sql theme={null}
CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT;
INSERT INTO t VALUES (1, ARRAY['a', 'b']);

-- Append element
SELECT tags || 'c' FROM t;
-- ["a","b","c"]

-- Prepend element
SELECT 'z' || tags FROM t;
-- ["z","a","b"]

-- Concatenate arrays
SELECT tags || ARRAY['x', 'y'] FROM t;
-- ["a","b","x","y"]
```

### array\_remove

Removes all occurrences of an element from an array.

```sql theme={null}
array_remove(array, element)
```

| Parameter | Type      | Description              |
| --------- | --------- | ------------------------ |
| `array`   | BLOB/TEXT | The array to remove from |
| `element` | any       | The element to remove    |

**Returns:** BLOB -- a new array with all occurrences of the element removed.

```sql theme={null}
SELECT array_remove('[1, 2, 3, 2, 1]', 2);
-- [1,3,1]

SELECT array_remove('[1, 2, 3]', 99);
-- [1,2,3]
```

### array\_contains

Tests whether an array contains a specific element.

```sql theme={null}
array_contains(array, element)
```

| Parameter | Type      | Description               |
| --------- | --------- | ------------------------- |
| `array`   | BLOB/TEXT | The array to search       |
| `element` | any       | The element to search for |

**Returns:** INTEGER -- 1 if found, 0 if not found.

```sql theme={null}
SELECT array_contains('[1, 2, 3]', 2);
-- 1

SELECT array_contains('[1, 2, 3]', 5);
-- 0
```

### array\_position

Returns the zero-based index of the first occurrence of an element.

```sql theme={null}
array_position(array, element)
```

| Parameter | Type      | Description         |
| --------- | --------- | ------------------- |
| `array`   | BLOB/TEXT | The array to search |
| `element` | any       | The element to find |

**Returns:** INTEGER -- the zero-based index, or NULL if not found.

```sql theme={null}
SELECT array_position('[10, 20, 30]', 20);
-- 1

SELECT array_position('[10, 20, 30]', 10);
-- 0

SELECT array_position('[10, 20, 30]', 99);
-- NULL
```

### array\_slice

Extracts a sub-array by index range.

```sql theme={null}
array_slice(array, start, end)
```

| Parameter | Type      | Description                                       |
| --------- | --------- | ------------------------------------------------- |
| `array`   | BLOB/TEXT | The source array                                  |
| `start`   | INTEGER   | Start index (zero-based, inclusive). NULL means 0 |
| `end`     | INTEGER   | End index (exclusive)                             |

**Returns:** BLOB -- a new array containing elements from `start` to `end - 1`.

```sql theme={null}
SELECT array_slice('[1, 2, 3, 4]', 1, 3);
-- [2,3]

SELECT array_slice('[1, 2, 3, 4]', NULL, 2);
-- [1,2]
```

### array\_to\_string

Joins array elements into a string with a delimiter.

```sql theme={null}
array_to_string(array, delimiter)
array_to_string(array, delimiter, null_string)
```

| Parameter     | Type      | Description                                                                 |
| ------------- | --------- | --------------------------------------------------------------------------- |
| `array`       | BLOB/TEXT | The array to join                                                           |
| `delimiter`   | TEXT      | The separator between elements                                              |
| `null_string` | TEXT      | Optional. Replacement text for NULL elements. If omitted, NULLs are skipped |

**Returns:** TEXT -- the joined string.

```sql theme={null}
SELECT array_to_string('[1, 2, 3]', ',');
-- 1,2,3

SELECT array_to_string('["hello", "world"]', ' ');
-- hello world

SELECT array_to_string('[1, null, 3]', ',');
-- 1,3

SELECT array_to_string('[1, null, 3]', ',', 'N/A');
-- 1,N/A,3
```

### array\_contains\_all

Tests whether one array contains all elements of another.

```sql theme={null}
array_contains_all(haystack, needles)
```

| Parameter  | Type      | Description                         |
| ---------- | --------- | ----------------------------------- |
| `haystack` | BLOB/TEXT | The array to search in              |
| `needles`  | BLOB/TEXT | The array of elements to search for |

**Returns:** INTEGER -- 1 if all elements of `needles` are found in `haystack`, 0 otherwise.

```sql theme={null}
SELECT array_contains_all('[1, 2, 3, 4, 5]', '[2, 4]');
-- 1

SELECT array_contains_all('[1, 2, 3]', '[2, 4]');
-- 0

SELECT array_contains_all('[1, 2, 3]', '[]');
-- 1
```

The `@>` operator is an alias for this function. See [Array Operators](/sql-reference/expressions#array-operators).

### array\_overlap

Tests whether two arrays share any common elements.

```sql theme={null}
array_overlap(array1, array2)
```

| Parameter | Type      | Description      |
| --------- | --------- | ---------------- |
| `array1`  | BLOB/TEXT | The first array  |
| `array2`  | BLOB/TEXT | The second array |

**Returns:** INTEGER -- 1 if any element appears in both arrays, 0 otherwise.

```sql theme={null}
SELECT array_overlap('[1, 2, 3]', '[3, 4, 5]');
-- 1

SELECT array_overlap('[1, 2, 3]', '[4, 5, 6]');
-- 0

SELECT array_overlap('[]', '[1, 2]');
-- 0
```

`array_overlaps` is accepted as an alias. The `&&` operator is also an alias. See [Array Operators](/sql-reference/expressions#array-operators).

***

## Aggregate Function

### array\_agg

Collects values from a group of rows into an array.

```sql theme={null}
array_agg(expression)
```

| Parameter    | Type | Description                                    |
| ------------ | ---- | ---------------------------------------------- |
| `expression` | any  | The value to collect. NULL values are included |

**Returns:** BLOB -- an array containing one element per row in the group. Returns NULL for empty groups.

```sql theme={null}
CREATE TABLE t (grp TEXT, val TEXT);
INSERT INTO t VALUES ('x', 'a'), ('x', 'b'), ('y', 'c'), ('y', 'd'), ('y', 'e');

SELECT grp, array_agg(val) FROM t GROUP BY grp ORDER BY grp;
```

| grp | array\_agg(val) |
| --- | --------------- |
| x   | \["a","b"]      |
| y   | \["c","d","e"]  |

```sql theme={null}
-- Control order with a subquery
SELECT array_agg(val) FROM (SELECT val FROM t ORDER BY val);
-- ["a","b","c","d","e"]

-- Combine with array_length
SELECT array_length(array_agg(val)) FROM t;
-- 5
```

***

## Array Operators

### Containment: @>

Tests whether the left array contains all elements of the right array. Equivalent to `array_contains_all(left, right)`.

```sql theme={null}
array1 @> array2
```

```sql theme={null}
SELECT ARRAY[1,2,3] @> ARRAY[1,2];
-- 1

SELECT ARRAY[1,2,3] @> ARRAY[1,4];
-- 0
```

### Overlap: &&

Tests whether two arrays share any common elements. Equivalent to `array_overlap(left, right)`.

```sql theme={null}
array1 && array2
```

```sql theme={null}
SELECT ARRAY[1,2,3] && ARRAY[3,4,5];
-- 1

SELECT ARRAY[1,2] && ARRAY[3,4];
-- 0
```

### Comparison: =, !=, \<, >, \<=, >=

Arrays support element-wise comparison. Elements are compared pairwise from left to right; if all compared elements are equal, the shorter array is considered less than the longer one.

```sql theme={null}
SELECT ARRAY[1,2,3] = ARRAY[1,2,3];   -- 1
SELECT ARRAY[1,2] < ARRAY[1,3];       -- 1
SELECT ARRAY[1,2] < ARRAY[1,2,3];     -- 1 (shorter is less)
```

### Concatenation: ||

When either operand is an array, `||` performs array concatenation or element append/prepend instead of string concatenation.

```sql theme={null}
SELECT ARRAY[1,2] || ARRAY[3,4];  -- [1,2,3,4]  (array + array)
SELECT ARRAY[1,2] || 3;           -- [1,2,3]    (append element)
SELECT 0 || ARRAY[1,2];           -- [0,1,2]    (prepend element)
```

### Using operators in WHERE clauses

```sql theme={null}
CREATE TABLE docs (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT;
INSERT INTO docs VALUES (1, ARRAY['sql','database','tutorial']);
INSERT INTO docs VALUES (2, ARRAY['rust','systems']);
INSERT INTO docs VALUES (3, ARRAY['sql','rust']);

-- Find docs tagged with both 'sql' and 'rust'
SELECT id FROM docs WHERE tags @> ARRAY['sql','rust'];
-- 3

-- Find docs with any overlap with a set of tags
SELECT id FROM docs WHERE tags && ARRAY['database','systems'] ORDER BY id;
-- 1
-- 2
```

***

## Subscript Assignment

Array elements can be updated individually using subscript syntax in UPDATE statements.

```sql theme={null}
UPDATE table SET column[index] = value WHERE ...;
```

```sql theme={null}
CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT;
INSERT INTO t VALUES (1, ARRAY['a','b','c']);

UPDATE t SET tags[0] = 'X' WHERE id = 1;
SELECT tags FROM t;
-- ["X","b","c"]

-- Multiple subscripts in one UPDATE
UPDATE t SET tags[0] = 'A', tags[2] = 'C' WHERE id = 1;
SELECT tags FROM t;
-- ["A","b","C"]
```

Out-of-bounds assignments are silently ignored (the array is unchanged).

***

## Examples

### Tagging system

```sql theme={null}
CREATE TABLE articles (
    id INTEGER PRIMARY KEY,
    title TEXT NOT NULL,
    tags TEXT[]
) STRICT;

INSERT INTO articles VALUES (1, 'Getting Started with SQL', ARRAY['sql','beginner','tutorial']);
INSERT INTO articles VALUES (2, 'Advanced Indexing', ARRAY['sql','performance','advanced']);
INSERT INTO articles VALUES (3, 'Rust for Systems', ARRAY['rust','systems']);

-- Find articles tagged 'sql'
SELECT title FROM articles WHERE array_contains(tags, 'sql');

-- Find articles with both 'sql' and 'advanced'
SELECT title FROM articles WHERE tags @> ARRAY['sql','advanced'];
-- Advanced Indexing

-- Count tags per article
SELECT title, array_length(tags) AS tag_count FROM articles;

-- Collect all unique tags
SELECT array_agg(DISTINCT tag) FROM (
    SELECT array_to_string(tags, ',') AS tag FROM articles
);
```

### Multi-dimensional arrays

```sql theme={null}
CREATE TABLE matrices (
    id INTEGER PRIMARY KEY,
    data INTEGER[][]
) STRICT;

INSERT INTO matrices VALUES (1, ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]]);

-- Access a row
SELECT data[0] FROM matrices WHERE id = 1;
-- [1,2,3]

-- Access a single element
SELECT data[1][2] FROM matrices WHERE id = 1;
-- 6

-- Get number of rows
SELECT array_length(data) FROM matrices WHERE id = 1;
-- 2
```

### Type checking

Array columns in STRICT tables validate element types on insert and update:

```sql theme={null}
CREATE TABLE scores (id INTEGER PRIMARY KEY, vals INTEGER[]) STRICT;

-- OK: exact integers
INSERT INTO scores VALUES (1, '[1, 2, 3]');

-- OK: floats that are exact integers are coerced
INSERT INTO scores VALUES (2, '[1.0, 2.0]');

-- OK: numeric strings are coerced
INSERT INTO scores VALUES (3, '["42", "100"]');

-- Error: non-numeric string in INTEGER array
INSERT INTO scores VALUES (4, '["hello"]');
-- Error: cannot store TEXT value in INTEGER

-- NULL elements are always allowed
INSERT INTO scores VALUES (5, '[1, null, 3]');
```

## See Also

* [Data Types](/sql-reference/data-types#array-types) for array column type declarations
* [CREATE TABLE](/sql-reference/statements/create-table) for STRICT table requirements
* [Expressions](/sql-reference/expressions#array-operators) for array operator syntax
* [Aggregate Functions](/sql-reference/functions/aggregate) for `array_agg`


# Date and Time Functions
Source: https://docs.turso.tech/sql-reference/functions/date-time

Functions for creating, formatting, and manipulating dates, times, and timestamps

# Date and Time Functions

Turso provides a complete set of date and time functions compatible with SQLite. These functions accept time values in various formats, apply optional modifiers, and return results as TEXT, REAL, or INTEGER depending on the function.

All date and time functions use the [proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar) and assume UTC unless the `localtime` modifier is applied.

## Functions

### date()

Returns the date as TEXT in `YYYY-MM-DD` format.

```sql theme={null}
date(time-value, modifier, modifier, ...)
```

| Parameter    | Type                   | Description                                                 |
| ------------ | ---------------------- | ----------------------------------------------------------- |
| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |
| `modifier`   | TEXT                   | Zero or more [modifiers](#modifiers) applied left to right  |

**Returns:** TEXT in `YYYY-MM-DD` format, or NULL if any argument is invalid.

```sql theme={null}
SELECT date('now');
-- '2025-06-15'

SELECT date('2025-06-15 14:30:00');
-- '2025-06-15'

SELECT date('2025-06-15', '+1 month');
-- '2025-07-15'

SELECT date('2025-06-15', 'start of year');
-- '2025-01-01'
```

***

### time()

Returns the time as TEXT in `HH:MM:SS` format.

```sql theme={null}
time(time-value, modifier, modifier, ...)
```

| Parameter    | Type                   | Description                                                 |
| ------------ | ---------------------- | ----------------------------------------------------------- |
| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |
| `modifier`   | TEXT                   | Zero or more [modifiers](#modifiers) applied left to right  |

**Returns:** TEXT in `HH:MM:SS` format, or NULL if any argument is invalid.

```sql theme={null}
SELECT time('now');
-- '14:30:00'

SELECT time('2025-06-15 14:30:00');
-- '14:30:00'

SELECT time('14:30:00', '+2 hours');
-- '16:30:00'

SELECT time('14:30:00', '+90 minutes');
-- '16:00:00'
```

***

### datetime()

Returns the date and time as TEXT in `YYYY-MM-DD HH:MM:SS` format.

```sql theme={null}
datetime(time-value, modifier, modifier, ...)
```

| Parameter    | Type                   | Description                                                 |
| ------------ | ---------------------- | ----------------------------------------------------------- |
| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |
| `modifier`   | TEXT                   | Zero or more [modifiers](#modifiers) applied left to right  |

**Returns:** TEXT in `YYYY-MM-DD HH:MM:SS` format, or NULL if any argument is invalid.

```sql theme={null}
SELECT datetime('now');
-- '2025-06-15 14:30:00'

SELECT datetime('2025-06-15', '+1 day', '+6 hours');
-- '2025-06-16 06:00:00'

SELECT datetime(1718458200, 'unixepoch');
-- '2024-06-15 14:30:00'
```

***

### julianday()

Returns the [Julian day number](https://en.wikipedia.org/wiki/Julian_day) as a REAL. The Julian day number is the number of days since noon on November 24, 4714 B.C. (proleptic Gregorian calendar).

```sql theme={null}
julianday(time-value, modifier, modifier, ...)
```

| Parameter    | Type                   | Description                                                 |
| ------------ | ---------------------- | ----------------------------------------------------------- |
| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |
| `modifier`   | TEXT                   | Zero or more [modifiers](#modifiers) applied left to right  |

**Returns:** REAL representing the Julian day number, or NULL if any argument is invalid.

```sql theme={null}
SELECT julianday('2025-06-15');
-- 2460811.5

SELECT julianday('now');
-- 2460811.104166...

-- Compute the number of days between two dates
SELECT julianday('2025-12-25') - julianday('2025-06-15');
-- 193.0
```

***

### unixepoch()

Returns the Unix timestamp as an INTEGER. The Unix timestamp is the number of seconds since `1970-01-01 00:00:00 UTC`.

```sql theme={null}
unixepoch(time-value, modifier, modifier, ...)
```

| Parameter    | Type                   | Description                                                 |
| ------------ | ---------------------- | ----------------------------------------------------------- |
| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) |
| `modifier`   | TEXT                   | Zero or more [modifiers](#modifiers) applied left to right  |

**Returns:** INTEGER representing seconds since the Unix epoch, or NULL if any argument is invalid. If the `subsec` modifier is used, returns REAL with fractional seconds.

```sql theme={null}
SELECT unixepoch('now');
-- 1718458200

SELECT unixepoch('2025-06-15 14:30:00');
-- 1750001400

SELECT unixepoch('2025-06-15 14:30:00.123', 'subsec');
-- 1750001400.123
```

***

### strftime()

Returns a formatted date/time string according to the specified format string.

```sql theme={null}
strftime(format, time-value, modifier, modifier, ...)
```

| Parameter    | Type                   | Description                                                             |
| ------------ | ---------------------- | ----------------------------------------------------------------------- |
| `format`     | TEXT                   | A format string containing [substitution codes](#strftime-format-codes) |
| `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats)             |
| `modifier`   | TEXT                   | Zero or more [modifiers](#modifiers) applied left to right              |

**Returns:** TEXT with format codes replaced by date/time components, or NULL if any argument is invalid.

The other date/time functions can be expressed as `strftime` calls:

| Function         | Equivalent strftime                  |
| ---------------- | ------------------------------------ |
| `date(...)`      | `strftime('%Y-%m-%d', ...)`          |
| `time(...)`      | `strftime('%H:%M:%S', ...)`          |
| `datetime(...)`  | `strftime('%Y-%m-%d %H:%M:%S', ...)` |
| `julianday(...)` | `strftime('%J', ...)` (as TEXT)      |
| `unixepoch(...)` | `strftime('%s', ...)` (as TEXT)      |

```sql theme={null}
SELECT strftime('%Y', 'now');
-- '2025'

SELECT strftime('%m/%d/%Y', '2025-06-15');
-- '06/15/2025'

SELECT strftime('%H:%M', '2025-06-15 14:30:00');
-- '14:30'

SELECT strftime('%Y-%m-%d %H:%M:%f', '2025-06-15 14:30:00.123');
-- '2025-06-15 14:30:00.123'

SELECT strftime('%s', '2025-06-15 14:30:00');
-- '1750001400'

SELECT strftime('%W', '2025-06-15');
-- '23' (week of year)
```

#### strftime Format Codes

| Code | Description                             | Example      |
| ---- | --------------------------------------- | ------------ |
| `%d` | Day of month (01-31)                    | `15`         |
| `%e` | Day of month with leading space ( 1-31) | `15`         |
| `%f` | Fractional seconds (SS.SSS)             | `00.123`     |
| `%F` | ISO 8601 date (YYYY-MM-DD)              | `2025-06-15` |
| `%H` | Hour (00-23)                            | `14`         |
| `%I` | Hour (01-12)                            | `02`         |
| `%j` | Day of year (001-366)                   | `166`        |
| `%J` | Julian day number                       | `2460811.5`  |
| `%k` | Hour with leading space ( 0-23)         | `14`         |
| `%l` | Hour with leading space ( 1-12)         | ` 2`         |
| `%m` | Month (01-12)                           | `06`         |
| `%M` | Minute (00-59)                          | `30`         |
| `%p` | AM or PM                                | `PM`         |
| `%P` | am or pm                                | `pm`         |
| `%R` | Time as HH:MM                           | `14:30`      |
| `%s` | Seconds since Unix epoch                | `1750001400` |
| `%S` | Seconds (00-59)                         | `00`         |
| `%T` | Time as HH:MM:SS                        | `14:30:00`   |
| `%u` | Day of week (1=Monday, 7=Sunday)        | `7`          |
| `%w` | Day of week (0=Sunday, 6=Saturday)      | `0`          |
| `%W` | Week of year (00-53)                    | `23`         |
| `%Y` | Year (0000-9999)                        | `2025`       |
| `%%` | Literal `%` character                   | `%`          |

***

### timediff()

Returns the difference between two time values as a TEXT string in the format `(+|-)YYYY-MM-DD HH:MM:SS.SSS`. The result represents the time that must be added to `time2` to produce `time1`.

```sql theme={null}
timediff(time1, time2)
```

| Parameter | Type                   | Description            |
| --------- | ---------------------- | ---------------------- |
| `time1`   | TEXT, REAL, or INTEGER | The later time value   |
| `time2`   | TEXT, REAL, or INTEGER | The earlier time value |

**Returns:** TEXT in `(+|-)YYYY-MM-DD HH:MM:SS.SSS` format, or NULL if either argument is invalid.

```sql theme={null}
SELECT timediff('2025-06-15', '2025-06-10');
-- '+0000-00-05 00:00:00.000'

SELECT timediff('2025-01-01', '2024-01-01');
-- '+0001-00-00 00:00:00.000'

SELECT timediff('2025-06-15 14:30:00', '2025-06-15 10:00:00');
-- '+0000-00-00 04:30:00.000'

SELECT timediff('2024-01-01', '2025-06-15');
-- '-0001-05-14 00:00:00.000'
```

## Time Value Formats

All date and time functions accept time values in the following formats.

| Format                    | Example                     | Description                              |
| ------------------------- | --------------------------- | ---------------------------------------- |
| `YYYY-MM-DD`              | `'2025-06-15'`              | Date only (time defaults to 00:00:00)    |
| `YYYY-MM-DD HH:MM`        | `'2025-06-15 14:30'`        | Date and time (seconds default to 00)    |
| `YYYY-MM-DD HH:MM:SS`     | `'2025-06-15 14:30:00'`     | Date and time with seconds               |
| `YYYY-MM-DD HH:MM:SS.SSS` | `'2025-06-15 14:30:00.123'` | Date and time with fractional seconds    |
| `YYYY-MM-DDTHH:MM`        | `'2025-06-15T14:30'`        | ISO 8601 with `T` separator              |
| `YYYY-MM-DDTHH:MM:SS`     | `'2025-06-15T14:30:00'`     | ISO 8601 with `T` separator              |
| `YYYY-MM-DDTHH:MM:SS.SSS` | `'2025-06-15T14:30:00.123'` | ISO 8601 with `T` separator              |
| `HH:MM`                   | `'14:30'`                   | Time only (date defaults to 2000-01-01)  |
| `HH:MM:SS`                | `'14:30:00'`                | Time only with seconds                   |
| `HH:MM:SS.SSS`            | `'14:30:00.123'`            | Time only with fractional seconds        |
| `now`                     | `'now'`                     | Current date and time in UTC             |
| Julian day number         | `2460811.5`                 | REAL number representing a Julian day    |
| Unix timestamp            | `1718458200`                | INTEGER, requires `'unixepoch'` modifier |

<Info>
  When passing a Unix timestamp as the time value, you must include the `'unixepoch'` modifier so the function knows to interpret the number as seconds since 1970-01-01, not as a Julian day number.
</Info>

```sql theme={null}
-- Unix timestamp requires the 'unixepoch' modifier
SELECT datetime(1718458200, 'unixepoch');
-- '2024-06-15 14:30:00'

-- Without 'unixepoch', the number is treated as a Julian day
SELECT datetime(1718458200);
-- NULL (or a very distant date)

-- 'now' returns the current UTC time
SELECT datetime('now');
-- '2025-06-15 14:30:00'
```

## Modifiers

Modifiers transform the time value. Multiple modifiers are applied left to right. If any modifier is invalid, the function returns NULL.

### Offset Modifiers

Offset modifiers add or subtract a specified amount from the time value.

| Modifier           | Description            | Example                  |
| ------------------ | ---------------------- | ------------------------ |
| `NNN days`         | Add NNN days           | `'+7 days'`, `'-1 days'` |
| `NNN hours`        | Add NNN hours          | `'+6 hours'`             |
| `NNN minutes`      | Add NNN minutes        | `'+30 minutes'`          |
| `NNN seconds`      | Add NNN seconds        | `'+90 seconds'`          |
| `NNN.NNNN seconds` | Add fractional seconds | `'+0.5 seconds'`         |
| `NNN months`       | Add NNN months         | `'+1 months'`            |
| `NNN years`        | Add NNN years          | `'+1 years'`             |

NNN can be a positive or negative integer (or real number for seconds). The `+` sign is optional for positive values.

```sql theme={null}
SELECT date('2025-06-15', '+7 days');
-- '2025-06-22'

SELECT datetime('2025-06-15 14:30:00', '-6 hours');
-- '2025-06-15 08:30:00'

SELECT date('2025-01-31', '+1 months');
-- '2025-03-03' (Jan 31 + 1 month = Mar 3)
```

### Time Offset Modifier

A time offset in the format `+HH:MM` or `-HH:MM` adds or subtracts the specified hours and minutes.

```sql theme={null}
SELECT time('14:30:00', '+05:30');
-- '20:00:00'

SELECT datetime('2025-06-15 14:30:00', '-08:00');
-- '2025-06-15 06:30:00'
```

### Start-of Modifiers

These modifiers reset the time value to the start of a period.

| Modifier         | Description                                      |
| ---------------- | ------------------------------------------------ |
| `start of day`   | Sets time to 00:00:00, keeps date                |
| `start of month` | Sets to first day of the month, time to 00:00:00 |
| `start of year`  | Sets to January 1 of the year, time to 00:00:00  |

```sql theme={null}
SELECT datetime('2025-06-15 14:30:00', 'start of day');
-- '2025-06-15 00:00:00'

SELECT date('2025-06-15', 'start of month');
-- '2025-06-01'

SELECT date('2025-06-15', 'start of year');
-- '2025-01-01'
```

### Weekday Modifier

The `weekday N` modifier advances the date to the next occurrence of the specified weekday, where 0 = Sunday, 1 = Monday, ..., 6 = Saturday. If the current date already falls on that weekday, it is unchanged.

```sql theme={null}
-- Next Sunday (weekday 0)
SELECT date('2025-06-15', 'weekday 0');
-- '2025-06-15' (June 15, 2025 is a Sunday)

-- Next Monday
SELECT date('2025-06-15', 'weekday 1');
-- '2025-06-16'

-- Next Friday
SELECT date('2025-06-15', 'weekday 5');
-- '2025-06-20'
```

### Interpretation Modifiers

| Modifier    | Description                                                              |
| ----------- | ------------------------------------------------------------------------ |
| `unixepoch` | Interpret the time value as a Unix timestamp (seconds since 1970-01-01)  |
| `julianday` | Interpret the time value as a Julian day number                          |
| `auto`      | Automatically detect whether the value is a Unix timestamp or Julian day |

```sql theme={null}
SELECT datetime(1718458200, 'unixepoch');
-- '2024-06-15 14:30:00'

SELECT datetime(2460811.5, 'julianday');
-- '2025-06-15 00:00:00'

SELECT datetime(0, 'unixepoch');
-- '1970-01-01 00:00:00'
```

### Timezone Modifiers

| Modifier    | Description                    |
| ----------- | ------------------------------ |
| `localtime` | Convert from UTC to local time |
| `utc`       | Convert from local time to UTC |

```sql theme={null}
SELECT datetime('2025-06-15 14:30:00', 'localtime');
-- '2025-06-15 07:30:00' (example: UTC-7)

SELECT time('now', 'localtime');
-- Local current time
```

### Rounding Modifiers

| Modifier  | Description                                                                          |
| --------- | ------------------------------------------------------------------------------------ |
| `ceiling` | When adding months, if the day overflows, advance to the first day of the next month |
| `floor`   | When adding months, if the day overflows, use the last day of the target month       |

These modifiers affect how month arithmetic handles months with different numbers of days.

```sql theme={null}
-- Without floor/ceiling, month overflow rolls forward
SELECT date('2025-01-31', '+1 months');
-- '2025-03-03'

-- With floor, clamp to last day of target month
SELECT date('2025-01-31', 'floor', '+1 months');
-- '2025-02-28'

-- With ceiling, advance to first of next month
SELECT date('2025-01-31', 'ceiling', '+1 months');
-- '2025-03-01'
```

### Subsec Modifier

The `subsec` modifier causes `unixepoch()` to return a REAL with fractional seconds instead of truncating to INTEGER.

```sql theme={null}
SELECT unixepoch('2025-06-15 14:30:00.456', 'subsec');
-- 1750001400.456
```

## Practical Examples

### Get the Current Date and Time

```sql theme={null}
SELECT date('now');                    -- Current UTC date
SELECT time('now');                    -- Current UTC time
SELECT datetime('now');                -- Current UTC date and time
SELECT datetime('now', 'localtime');   -- Current local date and time
```

### Date Arithmetic

```sql theme={null}
-- Tomorrow
SELECT date('now', '+1 day');

-- 90 days from now
SELECT date('now', '+90 days');

-- Last day of the current month
SELECT date('now', 'start of month', '+1 month', '-1 day');

-- First Monday of the current month
SELECT date('now', 'start of month', 'weekday 1');
```

### Age Calculation

```sql theme={null}
SELECT
    name,
    birth_date,
    timediff('now', birth_date) AS age_diff
FROM users;
```

### Convert Between Formats

```sql theme={null}
-- Unix timestamp to human-readable
SELECT datetime(1718458200, 'unixepoch');

-- Human-readable to Unix timestamp
SELECT unixepoch('2025-06-15 14:30:00');

-- Date to Julian day
SELECT julianday('2025-06-15');

-- Julian day to date
SELECT date(2460811.5);
```

### Group Records by Time Period

```sql theme={null}
-- Count orders per month
SELECT
    strftime('%Y-%m', order_date) AS month,
    COUNT(*) AS order_count
FROM orders
GROUP BY strftime('%Y-%m', order_date)
ORDER BY month;

-- Count events per day of week
SELECT
    CASE strftime('%w', event_date)
        WHEN '0' THEN 'Sunday'
        WHEN '1' THEN 'Monday'
        WHEN '2' THEN 'Tuesday'
        WHEN '3' THEN 'Wednesday'
        WHEN '4' THEN 'Thursday'
        WHEN '5' THEN 'Friday'
        WHEN '6' THEN 'Saturday'
    END AS day_name,
    COUNT(*) AS event_count
FROM events
GROUP BY strftime('%w', event_date)
ORDER BY strftime('%w', event_date);
```

### Filter by Date Range

```sql theme={null}
-- Records from the last 30 days
SELECT * FROM logs
WHERE timestamp >= datetime('now', '-30 days');

-- Records from a specific month
SELECT * FROM orders
WHERE order_date >= '2025-06-01'
  AND order_date < '2025-07-01';

-- Records from this year
SELECT * FROM sales
WHERE date(sale_date) >= date('now', 'start of year');
```

### Compute Elapsed Time

```sql theme={null}
-- Days between two dates
SELECT julianday('2025-12-25') - julianday('now') AS days_until_christmas;

-- Seconds between two timestamps
SELECT unixepoch('2025-06-15 18:00:00') - unixepoch('2025-06-15 14:30:00') AS seconds_elapsed;
-- 12600
```

## See Also

* [Expressions](/sql-reference/expressions) for using functions in expressions
* [Data Types](/sql-reference/data-types) for TEXT, REAL, and INTEGER storage classes
* [Scalar Functions](/sql-reference/functions/scalar) for other built-in functions


# Full-Text Search
Source: https://docs.turso.tech/sql-reference/functions/fts

Full-text search with Tantivy-powered FTS indexes, scoring, and highlighting

# Full-Text Search

<Info>
  **Turso Extension**: Full-text search (FTS) in Turso is powered by [Tantivy](https://github.com/quickwit-oss/tantivy), not SQLite's FTS3/FTS4/FTS5 modules. The syntax and functions differ from SQLite FTS.
</Info>

Turso provides full-text search through custom FTS indexes and three SQL functions: `fts_match` for filtering, `fts_score` for relevance ranking, and `fts_highlight` for displaying results with matched terms highlighted.

***

## Creating FTS Indexes

FTS indexes are created with the `USING fts` clause on `CREATE INDEX`. Each indexed column participates in the full-text search.

```sql theme={null}
CREATE INDEX index_name ON table_name USING fts (column1, column2, ...);
```

### Tokenizer Configuration

Each column can use a different tokenizer via the `WITH` clause.

```sql theme={null}
-- All columns use the default tokenizer
CREATE INDEX idx_articles ON articles USING fts (title, body);

-- Per-column tokenizer configuration
CREATE INDEX idx_articles ON articles USING fts (
    title WITH tokenizer=simple,
    body WITH tokenizer=ngram
);

-- Global tokenizer for all columns
CREATE INDEX idx_tags ON tags USING fts (tag) WITH (tokenizer = 'raw');
```

### Available Tokenizers

| Tokenizer    | Description                                                                        | Use case                                   |
| ------------ | ---------------------------------------------------------------------------------- | ------------------------------------------ |
| `default`    | Unicode-aware tokenizer with lowercasing and punctuation splitting (40-char limit) | General-purpose text search                |
| `raw`        | No tokenization -- matches the entire field value as a single token                | IDs, UUIDs, tags, exact-match fields       |
| `simple`     | Splits on whitespace and punctuation without lowercasing                           | Simple text without case-insensitive needs |
| `whitespace` | Splits on whitespace only                                                          | Whitespace-delimited tokens                |
| `ngram`      | Generates 2-3 character n-grams from the text                                      | Autocomplete, substring matching           |

**Tokenizer examples:**

| Input         | default          | raw           | ngram                                      |
| ------------- | ---------------- | ------------- | ------------------------------------------ |
| `Hello World` | `hello`, `world` | `Hello World` | `He`, `Hel`, `el`, `ell`, `ll`, `llo`, ... |
| `user-123`    | `user`, `123`    | `user-123`    | `us`, `use`, `se`, `ser`, ...              |

### Field Weights

You can assign relative weights to indexed columns to influence the BM25 relevance score.

```sql theme={null}
CREATE INDEX idx_articles ON articles USING fts (title, body)
    WITH (weights = 'title=2.0,body=1.0');
```

| Parameter | Default              | Description                                                                        |
| --------- | -------------------- | ---------------------------------------------------------------------------------- |
| `weights` | `1.0` for all fields | Comma-separated `column=weight` pairs. Higher weights increase score contribution. |

***

## Functions

### fts\_match

Returns 1 if the row matches the full-text query, or 0 otherwise. Used in the `WHERE` clause to filter rows.

```sql theme={null}
fts_match(column1, column2, ..., query)
```

| Parameter               | Type | Description                                                       |
| ----------------------- | ---- | ----------------------------------------------------------------- |
| `column1, column2, ...` | TEXT | One or more columns covered by an FTS index                       |
| `query`                 | TEXT | The search query string (see [Query Syntax](#query-syntax) below) |

**Returns:** INTEGER -- 1 if the row matches, 0 otherwise.

The columns passed to `fts_match` must correspond to columns in an existing FTS index. When Turso's query planner detects `fts_match` in a WHERE clause, it routes the query through the FTS index for efficient lookup.

```sql theme={null}
SELECT id, title FROM articles
WHERE fts_match(title, body, 'database');

-- Single column
SELECT id, title FROM articles
WHERE fts_match(body, 'machine learning');
```

### fts\_score

Computes the BM25 relevance score for each matching row. Lower scores indicate higher relevance.

```sql theme={null}
fts_score(column1, column2, ..., query)
```

| Parameter               | Type | Description                                 |
| ----------------------- | ---- | ------------------------------------------- |
| `column1, column2, ...` | TEXT | One or more columns covered by an FTS index |
| `query`                 | TEXT | The search query string                     |

**Returns:** REAL -- the BM25 relevance score. Lower values mean higher relevance.

Use `ORDER BY score ASC` or `ORDER BY score DESC` depending on your preference for result ordering. When used in SELECT, the FTS index automatically provides scored results.

```sql theme={null}
SELECT
    id,
    title,
    fts_score(title, body, 'database') AS score
FROM articles
ORDER BY score DESC
LIMIT 10;
```

### fts\_highlight

Returns text with matching query terms wrapped in custom tags. Useful for displaying search results with visual emphasis on matched terms.

```sql theme={null}
fts_highlight(column1, column2, ..., open_tag, close_tag, query)
```

| Parameter               | Type | Description                                                |
| ----------------------- | ---- | ---------------------------------------------------------- |
| `column1, column2, ...` | TEXT | One or more text columns to highlight                      |
| `open_tag`              | TEXT | The tag to insert before each matching term (e.g. `'<b>'`) |
| `close_tag`             | TEXT | The tag to insert after each matching term (e.g. `'</b>'`) |
| `query`                 | TEXT | The search query string                                    |

**Returns:** TEXT -- the input text with matching terms wrapped in the specified tags. Returns the original text if no matches are found. Returns NULL if the query, open\_tag, or close\_tag is NULL.

When multiple columns are provided, their text is concatenated with spaces.

```sql theme={null}
SELECT fts_highlight(title, '<b>', '</b>', 'database') AS highlighted
FROM articles
WHERE fts_match(title, body, 'database');
-- <b>Database</b> Design Patterns

-- Multiple columns are concatenated
SELECT fts_highlight(title, body, '<mark>', '</mark>', 'database') AS highlighted
FROM articles
WHERE fts_match(title, body, 'database');

-- Standalone usage (without an FTS index)
SELECT fts_highlight('The quick brown fox', '<em>', '</em>', 'quick fox');
-- The <em>quick</em> brown <em>fox</em>
```

***

## Query Syntax

The query string passed to `fts_match` and `fts_score` supports Tantivy's query parser syntax.

### Basic queries

| Syntax              | Example                 | Description                                  |
| ------------------- | ----------------------- | -------------------------------------------- |
| Single term         | `'database'`            | Match rows containing "database"             |
| Multiple terms (OR) | `'database search'`     | Match rows containing "database" OR "search" |
| Boolean AND         | `'database AND search'` | Match rows containing both terms             |
| Boolean NOT         | `'database NOT nosql'`  | Match "database" but exclude "nosql"         |

### Advanced queries

| Syntax          | Example              | Description                                |
| --------------- | -------------------- | ------------------------------------------ |
| Phrase search   | `'"exact phrase"'`   | Match the exact phrase                     |
| Prefix search   | `'data*'`            | Match terms starting with "data"           |
| Column-specific | `'title:database'`   | Match "database" only in the `title` field |
| Boosting        | `'title:database^2'` | Boost matches in `title` by a factor of 2  |

### Examples

```sql theme={null}
-- Simple term search
SELECT * FROM articles WHERE fts_match(title, body, 'database');

-- Phrase search: match "full text search" as an exact phrase
SELECT * FROM articles WHERE fts_match(title, body, '"full text search"');

-- Boolean AND: both terms must be present
SELECT * FROM articles WHERE fts_match(title, body, 'database AND performance');

-- Prefix search: match "optim", "optimize", "optimization", etc.
SELECT * FROM articles WHERE fts_match(title, body, 'optim*');

-- Column-specific: only match "rust" in the title field
SELECT * FROM articles WHERE fts_match(title, body, 'title:rust');

-- Boosted search: title matches count 2x more toward the score
SELECT id, title, fts_score(title, body, 'title:database^2 body:database') AS score
FROM articles
ORDER BY score DESC;

-- Exclusion: match "database" but not "nosql"
SELECT * FROM articles WHERE fts_match(title, body, 'database NOT nosql');
```

***

## Complete Example

This example walks through creating a table, adding an FTS index, inserting data, and running queries with scoring and highlighting.

```sql theme={null}
-- Create a table
CREATE TABLE articles (
    id INTEGER PRIMARY KEY,
    title TEXT NOT NULL,
    body TEXT,
    author TEXT
);

-- Create an FTS index on title and body with field weights
CREATE INDEX idx_articles_fts ON articles USING fts (title, body)
    WITH (weights = 'title=2.0,body=1.0');

-- Insert sample data
INSERT INTO articles VALUES (1, 'Introduction to Databases', 'Databases store and organize data for efficient retrieval.', 'Alice');
INSERT INTO articles VALUES (2, 'Full-Text Search in Practice', 'Full-text search allows finding documents by content.', 'Bob');
INSERT INTO articles VALUES (3, 'Database Performance Tuning', 'Optimizing database queries requires understanding indexes.', 'Alice');
INSERT INTO articles VALUES (4, 'Getting Started with Rust', 'Rust is a systems programming language focused on safety.', 'Carol');

-- Simple search: find articles mentioning "database"
SELECT id, title FROM articles
WHERE fts_match(title, body, 'database');
-- 1 | Introduction to Databases
-- 3 | Database Performance Tuning

-- Ranked search: order by relevance
SELECT
    id,
    title,
    fts_score(title, body, 'database') AS score
FROM articles
WHERE fts_match(title, body, 'database')
ORDER BY score DESC
LIMIT 10;

-- Highlighted results
SELECT
    id,
    fts_highlight(title, '<b>', '</b>', 'database') AS title,
    fts_highlight(body, '<b>', '</b>', 'database') AS body
FROM articles
WHERE fts_match(title, body, 'database');

-- Combine FTS with regular SQL filters
SELECT id, title, author,
    fts_score(title, body, 'database') AS score
FROM articles
WHERE fts_match(title, body, 'database')
  AND author = 'Alice'
ORDER BY score DESC;
```

***

## Index Maintenance

### OPTIMIZE INDEX

Merges all Tantivy segments into a single optimized segment. This improves query performance and reduces storage overhead, particularly after bulk inserts.

```sql theme={null}
-- Optimize a specific FTS index
OPTIMIZE INDEX idx_articles_fts;

-- Optimize all FTS indexes in the database
OPTIMIZE INDEX;
```

**When to use:**

* After bulk data imports with many INSERT statements
* When query performance degrades over time
* During scheduled maintenance windows

**What it does:**

* Flushes any pending documents to disk
* Merges all segments into a single segment
* Removes deleted document tombstones
* Invalidates internal caches for fresh reads

### DML and Index Updates

FTS indexes are updated automatically when you modify the underlying table.

| Operation | FTS behavior                                                            |
| --------- | ----------------------------------------------------------------------- |
| `INSERT`  | New rows are indexed immediately (batched commits every 1000 documents) |
| `UPDATE`  | Implemented as DELETE + INSERT internally                               |
| `DELETE`  | Marks documents as deleted via tombstones, cleaned up on OPTIMIZE       |

```sql theme={null}
-- All of these automatically update the FTS index
INSERT INTO articles VALUES (5, 'New Article', 'Content here.', 'Dave');
UPDATE articles SET body = 'Updated content.' WHERE id = 5;
DELETE FROM articles WHERE id = 5;
```

***

## Comparison with SQLite FTS5

| Feature                | SQLite FTS5                          | Turso FTS                                 |
| ---------------------- | ------------------------------------ | ----------------------------------------- |
| Filtering              | `WHERE t MATCH 'query'`              | `WHERE fts_match(cols, 'query')`          |
| Ranking                | `bm25(t)`, `rank` column             | `fts_score(cols, 'query')`                |
| Highlighting           | `highlight(t, col_idx, open, close)` | `fts_highlight(cols, open, close, query)` |
| Snippets               | `snippet(t, ...)`                    | Not supported                             |
| Boolean operators      | AND, OR, NOT                         | AND, OR, NOT                              |
| Phrase search          | `"exact phrase"`                     | `"exact phrase"`                          |
| Prefix search          | `word*`                              | `word*`                                   |
| Column filter          | `col:term`                           | `col:term`                                |
| Tokenizers             | unicode61, ascii, porter             | default, raw, simple, whitespace, ngram   |
| Segment management     | Automatic                            | Manual via `OPTIMIZE INDEX`               |
| Transaction visibility | Immediate                            | After COMMIT                              |

***

## Current Limitations

* **No snippet function**: Use `fts_highlight` for term emphasis; context snippets are not yet available.
* **No automatic segment merging**: Use `OPTIMIZE INDEX` periodically after bulk writes.
* **No read-your-writes in a transaction**: FTS changes within a transaction are not visible to queries until the transaction is committed. ROLLBACK correctly discards both table and FTS changes.
* **No MATCH operator syntax**: Use `fts_match()` function calls instead of `WHERE table MATCH 'query'`.

## See Also

* [CREATE INDEX](/sql-reference/statements/create-index) for the full `CREATE INDEX ... USING fts` syntax
* [Vector Functions](/sql-reference/functions/vector) for similarity search with embeddings


# JSON Functions
Source: https://docs.turso.tech/sql-reference/functions/json

Create, extract, modify, and aggregate JSON data

# JSON Functions

Turso provides a full set of JSON functions compatible with SQLite's JSON1 extension. These functions operate on JSON stored as TEXT or in Turso's internal binary JSON (JSONB) format.

Most functions come in pairs: a `json_*` variant that returns TEXT and a `jsonb_*` variant that returns BLOB in the internal binary format. The JSONB variants are more efficient when the result will be stored or passed to another JSON function rather than returned to the application.

## JSON Path Syntax

Many JSON functions accept a **path** argument that identifies a specific element within a JSON document.

| Syntax        | Meaning                                           |
| ------------- | ------------------------------------------------- |
| `$`           | The root element                                  |
| `$.key`       | Object member named `key`                         |
| `$[N]`        | Array element at index `N` (zero-based)           |
| `$.key1.key2` | Nested object member                              |
| `$.key[0]`    | First element of an array inside an object member |
| `$[0].key`    | Object member inside the first array element      |

Path arguments must begin with `$`. If a path does not match any element, functions generally return `NULL`.

```sql theme={null}
SELECT json_extract('{"a": {"b": [10, 20, 30]}}', '$.a.b[1]');
-- 20
```

***

## JSON Creation and Validation

### json

Validates a JSON string and returns it in minified form. If the input is not valid JSON, an error is raised.

```sql theme={null}
json(json_text)
```

| Parameter   | Type | Description                          |
| ----------- | ---- | ------------------------------------ |
| `json_text` | TEXT | A JSON string to validate and minify |

**Returns:** TEXT -- the minified JSON string.

```sql theme={null}
SELECT json('  { "name": "Alice" ,  "age": 30 } ');
-- {"name":"Alice","age":30}
```

### jsonb

Converts a JSON string to the internal binary JSON format.

```sql theme={null}
jsonb(json_text)
```

| Parameter   | Type | Description              |
| ----------- | ---- | ------------------------ |
| `json_text` | TEXT | A JSON string to convert |

**Returns:** BLOB -- the value in binary JSON format.

```sql theme={null}
SELECT typeof(jsonb('{"a":1}'));
-- blob
```

### json\_array / jsonb\_array

Creates a JSON array from the arguments.

```sql theme={null}
json_array(value1, value2, ...)
jsonb_array(value1, value2, ...)
```

| Parameter             | Type | Description                                                   |
| --------------------- | ---- | ------------------------------------------------------------- |
| `value1, value2, ...` | any  | Values to include in the array. SQL NULL becomes JSON `null`. |

**Returns:** TEXT (json\_array) or BLOB (jsonb\_array) -- a JSON array.

```sql theme={null}
SELECT json_array(1, 'hello', NULL, 3.14);
-- [1,"hello",null,3.14]

SELECT json_array();
-- []
```

### json\_object / jsonb\_object

Creates a JSON object from alternating label/value pairs. When called with `*`, expands all columns of the row into label/value pairs using column names as keys.

```sql theme={null}
json_object(label1, value1, label2, value2, ...)
jsonb_object(label1, value1, label2, value2, ...)
json_object(*)
```

| Parameter             | Type | Description                                         |
| --------------------- | ---- | --------------------------------------------------- |
| `label1, label2, ...` | TEXT | Keys for the JSON object. Must be strings.          |
| `value1, value2, ...` | any  | Corresponding values. SQL NULL becomes JSON `null`. |

**Returns:** TEXT (json\_object) or BLOB (jsonb\_object) -- a JSON object.

```sql theme={null}
SELECT json_object('name', 'Alice', 'age', 30);
-- {"name":"Alice","age":30}

SELECT json_object('items', json_array(1, 2, 3));
-- {"items":[1,2,3]}
```

### json\_quote

Converts a SQL value to its JSON representation.

```sql theme={null}
json_quote(value)
```

| Parameter | Type | Description                  |
| --------- | ---- | ---------------------------- |
| `value`   | any  | A SQL value to quote as JSON |

**Returns:** TEXT -- the JSON representation of the value.

```sql theme={null}
SELECT json_quote('hello');
-- "hello"

SELECT json_quote(42);
-- 42

SELECT json_quote(NULL);
-- null
```

### json\_valid

Returns 1 if the argument is well-formed JSON, or 0 otherwise.

```sql theme={null}
json_valid(json_text)
```

| Parameter   | Type | Description                      |
| ----------- | ---- | -------------------------------- |
| `json_text` | TEXT | A string to check for valid JSON |

**Returns:** INTEGER -- 1 if valid JSON, 0 otherwise.

```sql theme={null}
SELECT json_valid('{"name":"Alice"}');
-- 1

SELECT json_valid('not json');
-- 0

SELECT json_valid(NULL);
-- 0
```

### json\_error\_position

Returns the character position of the first syntax error in a JSON string, or 0 if the string is valid JSON.

```sql theme={null}
json_error_position(json_text)
```

| Parameter   | Type | Description                       |
| ----------- | ---- | --------------------------------- |
| `json_text` | TEXT | A string to check for JSON errors |

**Returns:** INTEGER -- character position of the first error (1-based), or 0 if valid.

```sql theme={null}
SELECT json_error_position('{"a":1}');
-- 0

SELECT json_error_position('{"a":}');
-- 6
```

***

## JSON Extraction

### json\_extract / jsonb\_extract

Extracts one or more values from a JSON document using path arguments.

```sql theme={null}
json_extract(json_text, path)
json_extract(json_text, path1, path2, ...)
jsonb_extract(json_text, path)
```

| Parameter   | Type         | Description                       |
| ----------- | ------------ | --------------------------------- |
| `json_text` | TEXT or BLOB | A JSON document                   |
| `path`      | TEXT         | One or more JSON path expressions |

**Returns:** With a single path, returns the extracted value using its natural SQL type (INTEGER, REAL, TEXT, or NULL). JSON objects and arrays are returned as TEXT. With multiple paths, returns a JSON array of the extracted values. `jsonb_extract` returns BLOB.

```sql theme={null}
SELECT json_extract('{"name":"Alice","age":30}', '$.name');
-- Alice

SELECT json_extract('{"name":"Alice","age":30}', '$.age');
-- 30

-- Multiple paths return a JSON array
SELECT json_extract('{"a":1,"b":2,"c":3}', '$.a', '$.c');
-- [1,3]
```

### -> operator

Extracts a value from JSON and returns it as JSON. This is a shorthand for `json_extract` that always returns JSON text (objects and arrays remain as JSON, strings are JSON-quoted).

```sql theme={null}
json_text -> path
```

| Parameter   | Type         | Description            |
| ----------- | ------------ | ---------------------- |
| `json_text` | TEXT or BLOB | A JSON document        |
| `path`      | TEXT         | A JSON path expression |

**Returns:** TEXT -- the extracted value as JSON.

```sql theme={null}
SELECT '{"name":"Alice"}' -> '$.name';
-- "Alice"

SELECT '{"items":[1,2,3]}' -> '$.items';
-- [1,2,3]
```

### ->> operator

Extracts a value from JSON and returns it as a SQL value. Strings are unquoted, numbers are returned as INTEGER or REAL, and booleans are returned as integers (0 or 1).

```sql theme={null}
json_text ->> path
```

| Parameter   | Type         | Description            |
| ----------- | ------------ | ---------------------- |
| `json_text` | TEXT or BLOB | A JSON document        |
| `path`      | TEXT         | A JSON path expression |

**Returns:** The extracted value as its natural SQL type (TEXT, INTEGER, REAL, or NULL).

```sql theme={null}
SELECT '{"name":"Alice"}' ->> '$.name';
-- Alice

SELECT '{"count":42}' ->> '$.count';
-- 42
```

### json\_type

Returns the type of a JSON value as a string: `"null"`, `"true"`, `"false"`, `"integer"`, `"real"`, `"text"`, `"array"`, or `"object"`.

```sql theme={null}
json_type(json_text)
json_type(json_text, path)
```

| Parameter   | Type         | Description                                                      |
| ----------- | ------------ | ---------------------------------------------------------------- |
| `json_text` | TEXT or BLOB | A JSON document                                                  |
| `path`      | TEXT         | Optional. A JSON path to inspect. If omitted, inspects the root. |

**Returns:** TEXT -- the JSON type name.

```sql theme={null}
SELECT json_type('{"a":1}');
-- object

SELECT json_type('[1, 2, 3]');
-- array

SELECT json_type('{"a": 1}', '$.a');
-- integer

SELECT json_type('{"a": "hello"}', '$.a');
-- text
```

***

## JSON Modification

### json\_insert / jsonb\_insert

Inserts new values into a JSON document. Existing values are **not** overwritten. If the path already exists, the value is left unchanged.

```sql theme={null}
json_insert(json_text, path1, value1, path2, value2, ...)
jsonb_insert(json_text, path1, value1, path2, value2, ...)
```

| Parameter   | Type         | Description                                    |
| ----------- | ------------ | ---------------------------------------------- |
| `json_text` | TEXT or BLOB | A JSON document                                |
| `path`      | TEXT         | A JSON path where the value should be inserted |
| `value`     | any          | The value to insert                            |

**Returns:** TEXT (json\_insert) or BLOB (jsonb\_insert) -- the modified JSON.

```sql theme={null}
SELECT json_insert('{"a":1}', '$.b', 2);
-- {"a":1,"b":2}

-- Existing values are NOT overwritten
SELECT json_insert('{"a":1}', '$.a', 99);
-- {"a":1}
```

### json\_replace / jsonb\_replace

Replaces existing values in a JSON document. If the path does not exist, no insertion is made.

```sql theme={null}
json_replace(json_text, path1, value1, path2, value2, ...)
jsonb_replace(json_text, path1, value1, path2, value2, ...)
```

| Parameter   | Type         | Description                                  |
| ----------- | ------------ | -------------------------------------------- |
| `json_text` | TEXT or BLOB | A JSON document                              |
| `path`      | TEXT         | A JSON path identifying the value to replace |
| `value`     | any          | The replacement value                        |

**Returns:** TEXT (json\_replace) or BLOB (jsonb\_replace) -- the modified JSON.

```sql theme={null}
SELECT json_replace('{"a":1,"b":2}', '$.a', 99);
-- {"a":99,"b":2}

-- Non-existent paths are ignored
SELECT json_replace('{"a":1}', '$.b', 2);
-- {"a":1}
```

### json\_set / jsonb\_set

Inserts or replaces values in a JSON document. Combines the behavior of `json_insert` and `json_replace`: if the path exists, the value is replaced; if it does not exist, the value is inserted.

```sql theme={null}
json_set(json_text, path1, value1, path2, value2, ...)
jsonb_set(json_text, path1, value1, path2, value2, ...)
```

| Parameter   | Type         | Description               |
| ----------- | ------------ | ------------------------- |
| `json_text` | TEXT or BLOB | A JSON document           |
| `path`      | TEXT         | A JSON path for the value |
| `value`     | any          | The value to set          |

**Returns:** TEXT (json\_set) or BLOB (jsonb\_set) -- the modified JSON.

```sql theme={null}
-- Replace existing
SELECT json_set('{"a":1}', '$.a', 99);
-- {"a":99}

-- Insert new
SELECT json_set('{"a":1}', '$.b', 2);
-- {"a":1,"b":2}
```

### json\_remove / jsonb\_remove

Removes one or more elements from a JSON document.

```sql theme={null}
json_remove(json_text, path1, path2, ...)
jsonb_remove(json_text, path1, path2, ...)
```

| Parameter   | Type         | Description                      |
| ----------- | ------------ | -------------------------------- |
| `json_text` | TEXT or BLOB | A JSON document                  |
| `path`      | TEXT         | One or more JSON paths to remove |

**Returns:** TEXT (json\_remove) or BLOB (jsonb\_remove) -- the modified JSON.

```sql theme={null}
SELECT json_remove('{"a":1,"b":2,"c":3}', '$.b');
-- {"a":1,"c":3}

SELECT json_remove('[1,2,3,4]', '$[1]');
-- [1,3,4]
```

### json\_patch / jsonb\_patch

Applies an RFC 7396 merge patch to a JSON document. Object members in the patch overwrite members in the target. A `null` value in the patch removes the corresponding member.

```sql theme={null}
json_patch(json_text, patch)
jsonb_patch(json_text, patch)
```

| Parameter   | Type         | Description              |
| ----------- | ------------ | ------------------------ |
| `json_text` | TEXT or BLOB | The target JSON document |
| `patch`     | TEXT or BLOB | The merge patch to apply |

**Returns:** TEXT (json\_patch) or BLOB (jsonb\_patch) -- the patched JSON.

```sql theme={null}
SELECT json_patch('{"a":1,"b":2}', '{"b":3,"c":4}');
-- {"a":1,"b":3,"c":4}

-- null in the patch removes a key
SELECT json_patch('{"a":1,"b":2}', '{"b":null}');
-- {"a":1}
```

### json\_pretty

Returns a pretty-printed (indented) representation of a JSON document.

```sql theme={null}
json_pretty(json_text)
```

| Parameter   | Type         | Description     |
| ----------- | ------------ | --------------- |
| `json_text` | TEXT or BLOB | A JSON document |

**Returns:** TEXT -- the formatted JSON string with indentation.

```sql theme={null}
SELECT json_pretty('{"name":"Alice","scores":[90,85,92]}');
/*
{
    "name": "Alice",
    "scores": [
        90,
        85,
        92
    ]
}
*/
```

***

## JSON Array Functions

### json\_array\_length

Returns the number of elements in a JSON array. Returns 0 for an empty array and NULL for non-array JSON values.

```sql theme={null}
json_array_length(json_text)
json_array_length(json_text, path)
```

| Parameter   | Type         | Description                                            |
| ----------- | ------------ | ------------------------------------------------------ |
| `json_text` | TEXT or BLOB | A JSON document                                        |
| `path`      | TEXT         | Optional. A JSON path to an array within the document. |

**Returns:** INTEGER -- the number of elements, or NULL if the value at the path is not an array.

```sql theme={null}
SELECT json_array_length('[1, 2, 3, 4]');
-- 4

SELECT json_array_length('{"items": [10, 20]}', '$.items');
-- 2

SELECT json_array_length('{"a": 1}');
-- NULL
```

***

## JSON Aggregate Functions

### json\_group\_array / jsonb\_group\_array

Aggregate function that collects values from a group into a JSON array.

```sql theme={null}
json_group_array(value)
jsonb_group_array(value)
```

| Parameter | Type | Description                          |
| --------- | ---- | ------------------------------------ |
| `value`   | any  | The value to aggregate from each row |

**Returns:** TEXT (json\_group\_array) or BLOB (jsonb\_group\_array) -- a JSON array of all values in the group.

```sql theme={null}
CREATE TABLE items (category TEXT, name TEXT);
INSERT INTO items VALUES ('fruit', 'apple'), ('fruit', 'banana'), ('veggie', 'carrot');

SELECT category, json_group_array(name) FROM items GROUP BY category;
-- fruit  | ["apple","banana"]
-- veggie | ["carrot"]
```

### json\_group\_object / jsonb\_group\_object

Aggregate function that collects label/value pairs from a group into a JSON object.

```sql theme={null}
json_group_object(label, value)
jsonb_group_object(label, value)
```

| Parameter | Type | Description              |
| --------- | ---- | ------------------------ |
| `label`   | TEXT | The key for each entry   |
| `value`   | any  | The value for each entry |

**Returns:** TEXT (json\_group\_object) or BLOB (jsonb\_group\_object) -- a JSON object.

```sql theme={null}
CREATE TABLE settings (key TEXT, value TEXT);
INSERT INTO settings VALUES ('theme', 'dark'), ('lang', 'en');

SELECT json_group_object(key, value) FROM settings;
-- {"theme":"dark","lang":"en"}
```

***

## JSON Table-Valued Functions

### json\_each

A table-valued function that walks the top-level elements of a JSON array or object, returning one row per element.

```sql theme={null}
SELECT * FROM json_each(json_text);
SELECT * FROM json_each(json_text, path);
```

| Parameter   | Type         | Description                                                               |
| ----------- | ------------ | ------------------------------------------------------------------------- |
| `json_text` | TEXT or BLOB | A JSON document                                                           |
| `path`      | TEXT         | Optional. A JSON path to the array or object to iterate. Defaults to `$`. |

**Output columns:**

| Column    | Type            | Description                                                                                |
| --------- | --------------- | ------------------------------------------------------------------------------------------ |
| `key`     | TEXT or INTEGER | Object key (TEXT) or array index (INTEGER)                                                 |
| `value`   | any             | The value of the element, as JSON text for objects/arrays or as a SQL value for primitives |
| `type`    | TEXT            | JSON type: `null`, `true`, `false`, `integer`, `real`, `text`, `array`, or `object`        |
| `atom`    | any             | The SQL value for primitives (NULL for arrays and objects)                                 |
| `id`      | INTEGER         | A sequential identifier for the element                                                    |
| `parent`  | INTEGER         | The id of the parent element (NULL for top-level)                                          |
| `fullkey` | TEXT            | The full JSON path to this element                                                         |
| `path`    | TEXT            | The JSON path to the parent of this element                                                |

```sql theme={null}
SELECT key, value, type FROM json_each('[10, "hello", null]');
-- 0 | 10    | integer
-- 1 | hello | text
-- 2 | null  | null

SELECT key, value FROM json_each('{"a":1, "b":2}');
-- a | 1
-- b | 2

-- With a path
SELECT key, value FROM json_each('{"data": [1, 2, 3]}', '$.data');
-- 0 | 1
-- 1 | 2
-- 2 | 3
```

### json\_tree

<Warning>
  `json_tree` has partial support. Some advanced traversal features may not work as expected.
</Warning>

A table-valued function that recursively walks a JSON document, returning one row for every element at every level of nesting.

```sql theme={null}
SELECT * FROM json_tree(json_text);
SELECT * FROM json_tree(json_text, path);
```

| Parameter   | Type         | Description                                                    |
| ----------- | ------------ | -------------------------------------------------------------- |
| `json_text` | TEXT or BLOB | A JSON document                                                |
| `path`      | TEXT         | Optional. A JSON path to the subtree to walk. Defaults to `$`. |

**Output columns:** Same as `json_each`.

```sql theme={null}
SELECT key, value, type, path FROM json_tree('{"a": [1, 2]}');
-- NULL | {"a":[1,2]} | object  | $
-- a    | [1,2]       | array   | $
-- 0    | 1           | integer | $.a
-- 1    | 2           | integer | $.a
```

***

## Practical Examples

### Storing and querying JSON data

```sql theme={null}
CREATE TABLE events (id INTEGER PRIMARY KEY, data TEXT);
INSERT INTO events VALUES (1, '{"type":"click","x":100,"y":200}');
INSERT INTO events VALUES (2, '{"type":"scroll","offset":500}');

-- Extract specific fields
SELECT id, data ->> '$.type' AS event_type FROM events;
-- 1 | click
-- 2 | scroll

-- Filter by JSON value
SELECT * FROM events WHERE data ->> '$.type' = 'click';
```

### Modifying JSON in place

```sql theme={null}
UPDATE events
SET data = json_set(data, '$.timestamp', '2025-01-15T10:30:00Z')
WHERE id = 1;

SELECT data FROM events WHERE id = 1;
-- {"type":"click","x":100,"y":200,"timestamp":"2025-01-15T10:30:00Z"}
```

### Building JSON from relational data

```sql theme={null}
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);
INSERT INTO users VALUES (1, 'Alice', 'alice@example.com');
INSERT INTO users VALUES (2, 'Bob', 'bob@example.com');

SELECT json_object('users', json_group_array(
    json_object('id', id, 'name', name, 'email', email)
)) FROM users;
-- {"users":[{"id":1,"name":"Alice","email":"alice@example.com"},{"id":2,"name":"Bob","email":"bob@example.com"}]}
```

### Flattening JSON arrays with json\_each

```sql theme={null}
CREATE TABLE orders (id INTEGER PRIMARY KEY, items TEXT);
INSERT INTO orders VALUES (1, '["widget","gadget","gizmo"]');
INSERT INTO orders VALUES (2, '["sprocket"]');

-- Expand each order's items into individual rows
SELECT orders.id, each.value AS item
FROM orders, json_each(orders.items) AS each;
-- 1 | widget
-- 1 | gadget
-- 1 | gizmo
-- 2 | sprocket
```

## See Also

* [Data Types](/sql-reference/data-types) for how JSON values map to SQL types
* [Expressions](/sql-reference/expressions) for using JSON operators in expressions


# Math Functions
Source: https://docs.turso.tech/sql-reference/functions/math

Mathematical functions for trigonometry, logarithms, rounding, and arithmetic

# Math Functions

Turso provides all SQLite math functions. These functions operate on numeric values and return REAL unless otherwise noted. If any argument is NULL, the function returns NULL. If any argument is non-numeric and cannot be converted, the function returns NULL or 0.0 depending on the function.

## Function Reference

### Trigonometric Functions

| Function      | Description                                                                                    | Return Type |
| ------------- | ---------------------------------------------------------------------------------------------- | ----------- |
| `acos(X)`     | Inverse cosine of X in radians. X must be between -1.0 and 1.0                                 | REAL        |
| `asin(X)`     | Inverse sine of X in radians. X must be between -1.0 and 1.0                                   | REAL        |
| `atan(X)`     | Inverse tangent of X in radians                                                                | REAL        |
| `atan2(Y, X)` | Inverse tangent of Y/X in radians, using the signs of both arguments to determine the quadrant | REAL        |
| `cos(X)`      | Cosine of X, where X is in radians                                                             | REAL        |
| `sin(X)`      | Sine of X, where X is in radians                                                               | REAL        |
| `tan(X)`      | Tangent of X, where X is in radians                                                            | REAL        |

### Hyperbolic Functions

| Function   | Description                                                     | Return Type |
| ---------- | --------------------------------------------------------------- | ----------- |
| `acosh(X)` | Inverse hyperbolic cosine of X. X must be >= 1.0                | REAL        |
| `asinh(X)` | Inverse hyperbolic sine of X                                    | REAL        |
| `atanh(X)` | Inverse hyperbolic tangent of X. X must be between -1.0 and 1.0 | REAL        |
| `cosh(X)`  | Hyperbolic cosine of X                                          | REAL        |
| `sinh(X)`  | Hyperbolic sine of X                                            | REAL        |
| `tanh(X)`  | Hyperbolic tangent of X                                         | REAL        |

### Angle Conversion

| Function     | Description                       | Return Type |
| ------------ | --------------------------------- | ----------- |
| `degrees(X)` | Convert X from radians to degrees | REAL        |
| `radians(X)` | Convert X from degrees to radians | REAL        |

### Rounding and Truncation

| Function     | Description                               | Return Type |
| ------------ | ----------------------------------------- | ----------- |
| `ceil(X)`    | Smallest integer not less than X          | INTEGER     |
| `ceiling(X)` | Alias for `ceil(X)`                       | INTEGER     |
| `floor(X)`   | Largest integer not greater than X        | INTEGER     |
| `trunc(X)`   | Integer part of X (truncates toward zero) | INTEGER     |

### Logarithmic and Exponential Functions

| Function    | Description                                          | Return Type |
| ----------- | ---------------------------------------------------- | ----------- |
| `exp(X)`    | e raised to the power X                              | REAL        |
| `ln(X)`     | Natural logarithm of X. X must be > 0                | REAL        |
| `log(X)`    | Natural logarithm of X (same as `ln(X)`)             | REAL        |
| `log(B, X)` | Logarithm of X with base B. Both must be > 0, B != 1 | REAL        |
| `log10(X)`  | Base-10 logarithm of X. X must be > 0                | REAL        |
| `log2(X)`   | Base-2 logarithm of X. X must be > 0                 | REAL        |

<Info>
  The single-argument `log(X)` returns the natural logarithm (base e), matching SQLite behavior. This differs from some other databases where `log(X)` returns the base-10 logarithm.
</Info>

### Arithmetic Functions

| Function      | Description                                            | Return Type |
| ------------- | ------------------------------------------------------ | ----------- |
| `mod(X, Y)`   | Remainder of X divided by Y                            | REAL        |
| `pow(X, Y)`   | X raised to the power Y                                | REAL        |
| `power(X, Y)` | Alias for `pow(X, Y)`                                  | REAL        |
| `sqrt(X)`     | Square root of X. X must be >= 0                       | REAL        |
| `sign(X)`     | Returns -1, 0, or +1 for negative, zero, or positive X | INTEGER     |
| `gcd(A, B)`   | Greatest common divisor of two integers                | INTEGER     |
| `lcm(A, B)`   | Least common multiple of two integers                  | INTEGER     |

### Constants

| Function | Description                                 | Return Type |
| -------- | ------------------------------------------- | ----------- |
| `pi()`   | Returns the value of pi (3.141592653589793) | REAL        |

## Signatures

### acos()

```sql theme={null}
acos(X)
```

| Parameter | Type            | Description                |
| --------- | --------------- | -------------------------- |
| `X`       | REAL or INTEGER | Value between -1.0 and 1.0 |

**Returns:** REAL. The angle in radians whose cosine is X. Returns NULL if X is outside \[-1.0, 1.0].

***

### acosh()

```sql theme={null}
acosh(X)
```

| Parameter | Type            | Description  |
| --------- | --------------- | ------------ |
| `X`       | REAL or INTEGER | Value >= 1.0 |

**Returns:** REAL. The inverse hyperbolic cosine of X. Returns NULL if X \< 1.0.

***

### asin()

```sql theme={null}
asin(X)
```

| Parameter | Type            | Description                |
| --------- | --------------- | -------------------------- |
| `X`       | REAL or INTEGER | Value between -1.0 and 1.0 |

**Returns:** REAL. The angle in radians whose sine is X. Returns NULL if X is outside \[-1.0, 1.0].

***

### asinh()

```sql theme={null}
asinh(X)
```

| Parameter | Type            | Description       |
| --------- | --------------- | ----------------- |
| `X`       | REAL or INTEGER | Any numeric value |

**Returns:** REAL. The inverse hyperbolic sine of X.

***

### atan()

```sql theme={null}
atan(X)
```

| Parameter | Type            | Description       |
| --------- | --------------- | ----------------- |
| `X`       | REAL or INTEGER | Any numeric value |

**Returns:** REAL. The angle in radians whose tangent is X. Result is in the range (-pi/2, pi/2).

***

### atan2()

```sql theme={null}
atan2(Y, X)
```

| Parameter | Type            | Description      |
| --------- | --------------- | ---------------- |
| `Y`       | REAL or INTEGER | The y-coordinate |
| `X`       | REAL or INTEGER | The x-coordinate |

**Returns:** REAL. The angle in radians between the positive x-axis and the point (X, Y). Result is in the range (-pi, pi]. Unlike `atan(Y/X)`, `atan2` correctly handles all quadrants and the case where X is zero.

***

### atanh()

```sql theme={null}
atanh(X)
```

| Parameter | Type            | Description                            |
| --------- | --------------- | -------------------------------------- |
| `X`       | REAL or INTEGER | Value between -1.0 and 1.0 (exclusive) |

**Returns:** REAL. The inverse hyperbolic tangent of X. Returns NULL if X is outside (-1.0, 1.0).

***

### ceil() / ceiling()

```sql theme={null}
ceil(X)
ceiling(X)
```

| Parameter | Type            | Description       |
| --------- | --------------- | ----------------- |
| `X`       | REAL or INTEGER | Any numeric value |

**Returns:** INTEGER. The smallest integer not less than X.

```sql theme={null}
SELECT ceil(3.2);    -- 4
SELECT ceil(-3.2);   -- -3
SELECT ceiling(5.0); -- 5
```

***

### cos()

```sql theme={null}
cos(X)
```

| Parameter | Type            | Description      |
| --------- | --------------- | ---------------- |
| `X`       | REAL or INTEGER | Angle in radians |

**Returns:** REAL. The cosine of X.

***

### cosh()

```sql theme={null}
cosh(X)
```

| Parameter | Type            | Description       |
| --------- | --------------- | ----------------- |
| `X`       | REAL or INTEGER | Any numeric value |

**Returns:** REAL. The hyperbolic cosine of X.

***

### degrees()

```sql theme={null}
degrees(X)
```

| Parameter | Type            | Description      |
| --------- | --------------- | ---------------- |
| `X`       | REAL or INTEGER | Angle in radians |

**Returns:** REAL. X converted from radians to degrees.

```sql theme={null}
SELECT degrees(pi());   -- 180.0
SELECT degrees(pi()/2); -- 90.0
```

***

### exp()

```sql theme={null}
exp(X)
```

| Parameter | Type            | Description |
| --------- | --------------- | ----------- |
| `X`       | REAL or INTEGER | Exponent    |

**Returns:** REAL. The value of e raised to the power X.

```sql theme={null}
SELECT exp(0);   -- 1.0
SELECT exp(1);   -- 2.718281828459045
```

***

### gcd()

```sql theme={null}
gcd(A, B)
```

| Parameter | Type    | Description    |
| --------- | ------- | -------------- |
| `A`       | INTEGER | First operand  |
| `B`       | INTEGER | Second operand |

**Returns:** INTEGER. The greatest common divisor of A and B. The result is always non-negative regardless of the signs of the inputs. `gcd(0, 0)` returns `0`. Returns NULL if either argument is NULL. Raises an integer-overflow error when the result is not representable as a signed 64-bit integer (the only such case involves `-9223372036854775808`).

```sql theme={null}
SELECT gcd(12, 8);     -- 4
SELECT gcd(-12, 8);    -- 4
SELECT gcd(0, 7);      -- 7
SELECT gcd(0, 0);      -- 0
SELECT gcd(NULL, 7);   -- NULL
```

<Info>
  `gcd` and `lcm` are PostgreSQL/MySQL/Oracle-compatible additions that SQLite does not ship. They operate on signed 64-bit integers; non-integer text inputs that cannot be parsed return NULL.
</Info>

***

### lcm()

```sql theme={null}
lcm(A, B)
```

| Parameter | Type    | Description    |
| --------- | ------- | -------------- |
| `A`       | INTEGER | First operand  |
| `B`       | INTEGER | Second operand |

**Returns:** INTEGER. The least common multiple of A and B. The result is always non-negative regardless of the signs of the inputs. `lcm(_, 0)` and `lcm(0, _)` return `0`. Returns NULL if either argument is NULL. Raises an integer-overflow error when the multiplication does not fit in a signed 64-bit integer.

```sql theme={null}
SELECT lcm(4, 6);      -- 12
SELECT lcm(-4, 6);     -- 12
SELECT lcm(5, 0);      -- 0
SELECT lcm(NULL, 7);   -- NULL
```

***

### floor()

```sql theme={null}
floor(X)
```

| Parameter | Type            | Description       |
| --------- | --------------- | ----------------- |
| `X`       | REAL or INTEGER | Any numeric value |

**Returns:** INTEGER. The largest integer not greater than X.

```sql theme={null}
SELECT floor(3.7);   -- 3
SELECT floor(-3.2);  -- -4
SELECT floor(5.0);   -- 5
```

***

### ln()

```sql theme={null}
ln(X)
```

| Parameter | Type            | Description |
| --------- | --------------- | ----------- |
| `X`       | REAL or INTEGER | Value > 0   |

**Returns:** REAL. The natural logarithm (base e) of X. Returns NULL if X \<= 0.

```sql theme={null}
SELECT ln(1);         -- 0.0
SELECT ln(exp(1));    -- 1.0
SELECT ln(2);         -- 0.6931471805599453
```

***

### log()

```sql theme={null}
log(X)
log(B, X)
```

| Parameter | Type            | Description                  |
| --------- | --------------- | ---------------------------- |
| `X`       | REAL or INTEGER | Value > 0                    |
| `B`       | REAL or INTEGER | Base > 0 and != 1 (optional) |

**Returns:** REAL. With one argument, returns the natural logarithm of X (same as `ln(X)`). With two arguments, returns the logarithm of X in base B.

```sql theme={null}
SELECT log(10);      -- 2.302585... (natural log)
SELECT log(10, 100); -- 2.0 (log base 10 of 100)
SELECT log(2, 256);  -- 8.0 (log base 2 of 256)
```

***

### log10()

```sql theme={null}
log10(X)
```

| Parameter | Type            | Description |
| --------- | --------------- | ----------- |
| `X`       | REAL or INTEGER | Value > 0   |

**Returns:** REAL. The base-10 logarithm of X. Returns NULL if X \<= 0.

```sql theme={null}
SELECT log10(100);   -- 2.0
SELECT log10(1000);  -- 3.0
```

***

### log2()

```sql theme={null}
log2(X)
```

| Parameter | Type            | Description |
| --------- | --------------- | ----------- |
| `X`       | REAL or INTEGER | Value > 0   |

**Returns:** REAL. The base-2 logarithm of X. Returns NULL if X \<= 0.

```sql theme={null}
SELECT log2(256);   -- 8.0
SELECT log2(1024);  -- 10.0
```

***

### mod()

```sql theme={null}
mod(X, Y)
```

| Parameter | Type            | Description |
| --------- | --------------- | ----------- |
| `X`       | REAL or INTEGER | Dividend    |
| `Y`       | REAL or INTEGER | Divisor     |

**Returns:** REAL. The remainder when X is divided by Y. Returns NULL if Y is 0.

```sql theme={null}
SELECT mod(10, 3);    -- 1.0
SELECT mod(10.5, 3);  -- 1.5
SELECT mod(-10, 3);   -- -1.0
```

***

### pi()

```sql theme={null}
pi()
```

Takes no parameters.

**Returns:** REAL. The mathematical constant pi (3.141592653589793).

```sql theme={null}
SELECT pi();           -- 3.141592653589793
SELECT 2 * pi() * 5;  -- 31.41592653589793 (circumference of circle with radius 5)
```

***

### pow() / power()

```sql theme={null}
pow(X, Y)
power(X, Y)
```

| Parameter | Type            | Description |
| --------- | --------------- | ----------- |
| `X`       | REAL or INTEGER | Base        |
| `Y`       | REAL or INTEGER | Exponent    |

**Returns:** REAL. X raised to the power Y.

```sql theme={null}
SELECT pow(2, 10);      -- 1024.0
SELECT power(3, 3);     -- 27.0
SELECT pow(100, 0.5);   -- 10.0 (square root)
```

***

### radians()

```sql theme={null}
radians(X)
```

| Parameter | Type            | Description      |
| --------- | --------------- | ---------------- |
| `X`       | REAL or INTEGER | Angle in degrees |

**Returns:** REAL. X converted from degrees to radians.

```sql theme={null}
SELECT radians(180);   -- 3.141592653589793
SELECT radians(90);    -- 1.5707963267948966
```

***

### sign()

```sql theme={null}
sign(X)
```

| Parameter | Type            | Description       |
| --------- | --------------- | ----------------- |
| `X`       | REAL or INTEGER | Any numeric value |

**Returns:** INTEGER. Returns -1 if X is negative, 0 if X is zero, and +1 if X is positive. Returns NULL if X is NULL.

```sql theme={null}
SELECT sign(-42);  -- -1
SELECT sign(0);    -- 0
SELECT sign(3.14); -- 1
```

***

### sin()

```sql theme={null}
sin(X)
```

| Parameter | Type            | Description      |
| --------- | --------------- | ---------------- |
| `X`       | REAL or INTEGER | Angle in radians |

**Returns:** REAL. The sine of X.

***

### sinh()

```sql theme={null}
sinh(X)
```

| Parameter | Type            | Description       |
| --------- | --------------- | ----------------- |
| `X`       | REAL or INTEGER | Any numeric value |

**Returns:** REAL. The hyperbolic sine of X.

***

### sqrt()

```sql theme={null}
sqrt(X)
```

| Parameter | Type            | Description |
| --------- | --------------- | ----------- |
| `X`       | REAL or INTEGER | Value >= 0  |

**Returns:** REAL. The square root of X. Returns NULL if X is negative.

```sql theme={null}
SELECT sqrt(144);  -- 12.0
SELECT sqrt(2);    -- 1.4142135623730951
```

***

### tan()

```sql theme={null}
tan(X)
```

| Parameter | Type            | Description      |
| --------- | --------------- | ---------------- |
| `X`       | REAL or INTEGER | Angle in radians |

**Returns:** REAL. The tangent of X.

***

### tanh()

```sql theme={null}
tanh(X)
```

| Parameter | Type            | Description       |
| --------- | --------------- | ----------------- |
| `X`       | REAL or INTEGER | Any numeric value |

**Returns:** REAL. The hyperbolic tangent of X. Result is always in the range (-1.0, 1.0).

***

### trunc()

```sql theme={null}
trunc(X)
```

| Parameter | Type            | Description       |
| --------- | --------------- | ----------------- |
| `X`       | REAL or INTEGER | Any numeric value |

**Returns:** INTEGER. The integer part of X, removing any fractional digits. Truncates toward zero.

```sql theme={null}
SELECT trunc(3.7);   -- 3
SELECT trunc(-3.7);  -- -3
SELECT trunc(5.0);   -- 5
```

## Examples

### Trigonometry

```sql theme={null}
-- Sine and cosine of 45 degrees
SELECT sin(radians(45));  -- 0.7071067811865475
SELECT cos(radians(45));  -- 0.7071067811865476

-- Angle from coordinates
SELECT degrees(atan2(1, 1));  -- 45.0

-- Verify the Pythagorean identity: sin^2 + cos^2 = 1
SELECT pow(sin(1.0), 2) + pow(cos(1.0), 2);  -- 1.0
```

### Rounding

```sql theme={null}
-- Different rounding behaviors
SELECT ceil(2.3), floor(2.3), trunc(2.3);
-- 3, 2, 2

SELECT ceil(-2.3), floor(-2.3), trunc(-2.3);
-- -2, -3, -2
```

### Logarithms

```sql theme={null}
-- Compute the number of bits needed to represent N
SELECT ceil(log2(1000));  -- 10

-- Decibel conversion
SELECT 10 * log10(100);  -- 20.0

-- Verify log identities
SELECT ln(exp(5));  -- 5.0
```

### Distance Calculation

```sql theme={null}
-- Euclidean distance between two points
SELECT sqrt(pow(3 - 0, 2) + pow(4 - 0, 2));
-- 5.0 (3-4-5 triangle)
```

## See Also

* [Expressions](/sql-reference/expressions) for arithmetic operators
* [Aggregate Functions](/sql-reference/functions/aggregate) for SUM, AVG, MIN, MAX
* [Data Types](/sql-reference/data-types) for REAL and INTEGER storage classes


# Scalar Functions
Source: https://docs.turso.tech/sql-reference/functions/scalar

Built-in scalar functions for string manipulation, math, type inspection, and more

# Scalar Functions

Scalar functions accept one or more arguments and return a single value. They can be used anywhere an expression is valid: SELECT columns, WHERE conditions, ORDER BY, GROUP BY, HAVING, CHECK constraints, and DEFAULT values.

## Function Reference

### Math Functions

| Function         | Description                                                                                |
| ---------------- | ------------------------------------------------------------------------------------------ |
| `abs(X)`         | Absolute value of X. Returns INTEGER if X is INTEGER, REAL if X is REAL, NULL if X is NULL |
| `max(X, Y, ...)` | Returns the argument with the maximum value                                                |
| `min(X, Y, ...)` | Returns the argument with the minimum value                                                |
| `random()`       | Returns a random 64-bit signed integer                                                     |
| `round(X)`       | Rounds X to the nearest integer                                                            |
| `round(X, Y)`    | Rounds X to Y decimal places                                                               |
| `sign(X)`        | Returns -1, 0, or 1 for negative, zero, or positive X                                      |

### String Functions

| Function                       | Description                                                                                         |
| ------------------------------ | --------------------------------------------------------------------------------------------------- |
| `char(X1, X2, ..., XN)`        | Returns a string composed of characters with Unicode code points X1 through XN                      |
| `chr(X)`                       | Alias for `char(X)`. PostgreSQL/SQL-standard spelling                                               |
| `concat(X, ...)`               | Concatenates all arguments as strings. NULL arguments are skipped                                   |
| `concat_ws(SEP, X, ...)`       | Concatenates arguments with separator SEP. NULL arguments are skipped                               |
| `format(FORMAT, ...)`          | Returns a formatted string using printf-style format specifiers                                     |
| `hex(X)`                       | Returns the uppercase hexadecimal representation of X                                               |
| `instr(X, Y)`                  | Returns the 1-based position of the first occurrence of Y in X, or 0 if not found                   |
| `strpos(X, Y)`                 | Alias for `instr(X, Y)`. PostgreSQL spelling, identical argument order                              |
| `length(X)`                    | Returns the string length in characters, or blob length in bytes                                    |
| `char_length(X)`               | Alias for `length(X)`. SQL-standard short form                                                      |
| `character_length(X)`          | Alias for `length(X)`. SQL-standard long form                                                       |
| `lower(X)`                     | Returns a lowercase copy of string X                                                                |
| `lpad(X, N)` / `lpad(X, N, F)` | Pads X on the left to length N using F (defaults to a single space)                                 |
| `ltrim(X)`                     | Removes leading whitespace from X                                                                   |
| `ltrim(X, Y)`                  | Removes leading characters found in Y from X                                                        |
| `octet_length(X)`              | Returns the length of X in bytes                                                                    |
| `printf(FORMAT, ...)`          | Alias for `format()`. Returns a formatted string                                                    |
| `quote(X)`                     | Returns the SQL literal representation of X                                                         |
| `repeat(X, N)`                 | Returns X concatenated N times. `N <= 0` produces an empty string                                   |
| `replace(X, Y, Z)`             | Returns X with every occurrence of Y replaced by Z                                                  |
| `reverse(X)`                   | Returns the characters of string X in reverse order. Alias for `string_reverse(X)`                  |
| `rpad(X, N)` / `rpad(X, N, F)` | Pads X on the right to length N using F (defaults to a single space)                                |
| `rtrim(X)`                     | Removes trailing whitespace from X                                                                  |
| `rtrim(X, Y)`                  | Removes trailing characters found in Y from X                                                       |
| `soundex(X)`                   | Returns the Soundex encoding of string X                                                            |
| `string_reverse(X)`            | Returns the characters of string X in reverse order                                                 |
| `substr(X, Y)`                 | Returns the substring of X starting at position Y (1-based)                                         |
| `substr(X, Y, Z)`              | Returns Z characters from X starting at position Y                                                  |
| `substring(X, Y)`              | Alias for `substr(X, Y)`                                                                            |
| `substring(X, Y, Z)`           | Alias for `substr(X, Y, Z)`                                                                         |
| `trim(X)`                      | Removes leading and trailing whitespace from X                                                      |
| `trim(X, Y)`                   | Removes leading and trailing characters found in Y from X                                           |
| `btrim(X)` / `btrim(X, Y)`     | Alias for `trim(X)` / `trim(X, Y)`. PostgreSQL/Oracle spelling                                      |
| `unhex(X)`                     | Converts hexadecimal string X to a blob. Returns NULL if X contains non-hex characters              |
| `unhex(X, Y)`                  | Like `unhex(X)`, but characters in Y are silently ignored in X                                      |
| `unicode(X)`                   | Returns the Unicode code point of the first character of string X                                   |
| `unistr(X)`                    | Evaluates PostgreSQL-style Unicode escapes in X (`\XXXX`, `\+XXXXXX`, `\uXXXX`, `\UXXXXXXXX`, `\\`) |
| `unistr_quote(X)`              | Returns X as a quoted string literal with non-ASCII characters written as `unistr` escapes          |
| `upper(X)`                     | Returns an uppercase copy of string X                                                               |
| `zeroblob(N)`                  | Returns a blob consisting of N zero bytes                                                           |

### Conditional Functions

| Function              | Description                                                                     |
| --------------------- | ------------------------------------------------------------------------------- |
| `coalesce(X, Y, ...)` | Returns the first non-NULL argument. Returns NULL if all arguments are NULL     |
| `ifnull(X, Y)`        | Returns X if X is not NULL, otherwise returns Y. Equivalent to `coalesce(X, Y)` |
| `iif(X, Y, Z)`        | Returns Y if X is true (non-zero), otherwise returns Z                          |
| `if(X, Y, Z)`         | Alias for `iif(X, Y, Z)`                                                        |
| `nullif(X, Y)`        | Returns NULL if X equals Y, otherwise returns X                                 |

### Type Functions

| Function    | Description                                                                               |
| ----------- | ----------------------------------------------------------------------------------------- |
| `typeof(X)` | Returns the type of X as a string: `"null"`, `"integer"`, `"real"`, `"text"`, or `"blob"` |

### Pattern Matching Functions

| Function        | Description                                                                           |
| --------------- | ------------------------------------------------------------------------------------- |
| `glob(X, Y)`    | Returns 1 if string Y matches glob pattern X, 0 otherwise. Case-sensitive             |
| `like(X, Y)`    | Returns 1 if string Y matches LIKE pattern X, 0 otherwise. Case-insensitive for ASCII |
| `like(X, Y, Z)` | Like `like(X, Y)` but uses Z as the escape character                                  |

### Optimizer Hints

| Function           | Description                                                                                    |
| ------------------ | ---------------------------------------------------------------------------------------------- |
| `likelihood(X, Y)` | Returns X unchanged. Hints to the query planner that X is true with probability Y (0.0 to 1.0) |
| `likely(X)`        | Returns X unchanged. Equivalent to `likelihood(X, 0.9375)`                                     |
| `unlikely(X)`      | Returns X unchanged. Equivalent to `likelihood(X, 0.0625)`                                     |

### Blob Functions

| Function        | Description                                                           |
| --------------- | --------------------------------------------------------------------- |
| `hex(X)`        | Returns the uppercase hexadecimal representation of blob or integer X |
| `randomblob(N)` | Returns a blob of N bytes filled with pseudo-random data              |
| `unhex(X)`      | Converts hexadecimal string X to a blob                               |
| `zeroblob(N)`   | Returns a blob of N zero bytes                                        |

### System Functions

| Function              | Description                                                                            |
| --------------------- | -------------------------------------------------------------------------------------- |
| `last_insert_rowid()` | Returns the rowid of the most recent successful INSERT on the same database connection |
| `changes()`           | Returns the number of rows modified by the most recent INSERT, UPDATE, or DELETE       |
| `total_changes()`     | Returns the total number of rows modified since the database connection was opened     |
| `sqlite_version()`    | Returns the version string `"3.42.0"`                                                  |
| `sqlite_source_id()`  | Returns the source identifier string for the SQLite-compatible engine                  |
| `turso_version()`     | Returns the Turso engine version string (e.g. `"0.7.0"`)                               |
| `load_extension(X)`   | Loads a Turso-native extension from the shared library at path X                       |

## Detailed Descriptions and Examples

### abs(X)

Returns the absolute value of X. The return type matches the input: INTEGER for integer inputs, REAL for floating-point inputs. Returns NULL if X is NULL. Returns INTEGER for a text value that looks like an integer.

```sql theme={null}
SELECT abs(-42);       -- 42
SELECT abs(3.14);      -- 3.14
SELECT abs(0);         -- 0
SELECT abs(NULL);      -- NULL
```

### char(X1, X2, ..., XN) and chr(X)

`char` returns a string composed of characters having the Unicode code points X1 through XN. Arguments that are not valid code points are replaced with the Unicode replacement character (U+FFFD).

`chr` is a single-argument alias provided for PostgreSQL and SQL-standard compatibility. It is identical to `char(X)`.

```sql theme={null}
SELECT char(72, 101, 108, 108, 111);   -- 'Hello'
SELECT char(9731);                       -- snowman character
SELECT hex(char(0));                     -- '00'
SELECT chr(65);                          -- 'A'
```

### coalesce(X, Y, ...)

Returns the first argument that is not NULL. If all arguments are NULL, returns NULL. Requires at least two arguments.

```sql theme={null}
SELECT coalesce(NULL, NULL, 'fallback');   -- 'fallback'
SELECT coalesce(1, 2, 3);                  -- 1
SELECT coalesce(NULL, 42);                 -- 42
```

### concat(X, ...) and concat\_ws(SEP, X, ...)

`concat` joins all arguments as strings, skipping NULLs. `concat_ws` inserts the separator between non-NULL arguments.

```sql theme={null}
SELECT concat('Hello', ' ', 'World');          -- 'Hello World'
SELECT concat('a', NULL, 'b');                 -- 'ab'
SELECT concat_ws(', ', 'Alice', 'Bob', NULL, 'Carol');  -- 'Alice, Bob, Carol'
SELECT concat_ws('-', 2024, 1, 15);            -- '2024-1-15'
```

### format(FORMAT, ...) and printf(FORMAT, ...)

Returns a formatted string using printf-style format specifiers. `printf` is an alias for `format`.

| Specifier | Description                                      |
| --------- | ------------------------------------------------ |
| `%d`      | Signed integer                                   |
| `%f`      | Floating-point                                   |
| `%s`      | String                                           |
| `%x`      | Lowercase hexadecimal integer                    |
| `%X`      | Uppercase hexadecimal integer                    |
| `%o`      | Octal integer                                    |
| `%e`      | Scientific notation                              |
| `%g`      | General floating-point (shortest representation) |
| `%%`      | Literal percent sign                             |

```sql theme={null}
SELECT format('Hello, %s! You are #%d.', 'Alice', 1);
-- 'Hello, Alice! You are #1.'

SELECT printf('%.2f%%', 99.5);
-- '99.50%'

SELECT format('0x%08X', 255);
-- '0x000000FF'
```

### glob(X, Y)

Returns 1 if string Y matches the glob pattern X, and 0 otherwise. Glob matching is case-sensitive and uses `*` for any sequence of characters, `?` for any single character, and `[...]` for character classes.

```sql theme={null}
SELECT glob('*.txt', 'readme.txt');   -- 1
SELECT glob('*.TXT', 'readme.txt');   -- 0 (case-sensitive)
SELECT glob('H?llo', 'Hello');        -- 1
```

<Info>
  The `glob(X, Y)` function is the functional form of the `Y GLOB X` operator. Note the reversed argument order compared to the operator syntax.
</Info>

### hex(X) and unhex(X)

`hex` returns the uppercase hexadecimal representation of its argument. For text, it returns the hex encoding of the UTF-8 bytes. For blobs, it encodes each byte. For integers, it returns the hex of the value.

`unhex` converts a hexadecimal string back to a blob. Returns NULL if the input contains non-hex characters, unless a second argument specifies characters to ignore.

```sql theme={null}
SELECT hex('ABC');             -- '414243'
SELECT hex(255);               -- 'FF'
SELECT hex(x'CAFE');           -- 'CAFE'

SELECT unhex('48454C4C4F');    -- x'48454C4C4F' (blob for 'HELLO')
SELECT unhex('48-45-4C', '-'); -- x'48454C' (ignoring dashes)
SELECT unhex('ZZZZ');          -- NULL (invalid hex)
```

### iif(X, Y, Z) and if(X, Y, Z)

Returns Y if X is true (non-zero and non-NULL), otherwise returns Z. `if` is an alias for `iif`.

```sql theme={null}
SELECT iif(1 > 0, 'yes', 'no');       -- 'yes'
SELECT iif(NULL, 'yes', 'no');         -- 'no'
SELECT if(10 > 5, 'big', 'small');     -- 'big'
```

### instr(X, Y) and strpos(X, Y)

Returns the 1-based position of the first occurrence of string Y in string X. Returns 0 if Y is not found in X. If either argument is NULL, returns NULL.

`strpos` is a PostgreSQL-compatible alias with the same argument order.

```sql theme={null}
SELECT instr('Hello World', 'World');   -- 7
SELECT instr('Hello World', 'xyz');     -- 0
SELECT instr('abcabc', 'bc');           -- 2
SELECT strpos('Hello World', 'World');  -- 7
```

### length(X), char\_length(X), character\_length(X), and octet\_length(X)

`length` returns the number of characters in a text value, or the number of bytes in a blob value. For NULL, returns NULL. For numeric values, returns the length of the text representation.

`char_length` and `character_length` are SQL-standard aliases for `length`, accepted for compatibility with PostgreSQL and other engines.

`octet_length` always returns the length in bytes, regardless of type.

```sql theme={null}
SELECT length('Hello');             -- 5
SELECT length(x'AABBCC');           -- 3 (bytes for blob)
SELECT length(12345);               -- 5 (text representation)
SELECT char_length('café');         -- 4 (counts characters, not bytes)
SELECT character_length('café');    -- 4
SELECT octet_length('Hello');       -- 5 (ASCII, 1 byte per char)
SELECT octet_length('café');        -- 5 (é is 2 bytes in UTF-8)
```

### like(X, Y) and like(X, Y, Z)

Returns 1 if string Y matches LIKE pattern X, and 0 otherwise. `%` matches any sequence of characters, `_` matches any single character. Matching is case-insensitive for ASCII characters. The optional third argument Z specifies an escape character.

```sql theme={null}
SELECT like('%ello%', 'Hello World');     -- 1
SELECT like('H_llo', 'Hello');            -- 1
SELECT like('10\%%', '10% discount', '\'); -- 1 (escaped %)
```

<Info>
  The `like(X, Y)` function is the functional form of the `Y LIKE X` operator. Note the reversed argument order compared to the operator syntax.
</Info>

### lower(X), upper(X)

`lower` returns a copy of string X with all ASCII characters converted to lowercase. `upper` converts to uppercase.

```sql theme={null}
SELECT lower('Hello World');   -- 'hello world'
SELECT upper('Hello World');   -- 'HELLO WORLD'
```

### ltrim(X), rtrim(X), trim(X), btrim(X)

These functions remove characters from the ends of a string. Without a second argument, they remove whitespace. With a second argument Y, they remove any characters present in the string Y.

`btrim` is a PostgreSQL/Oracle-compatible alias for `trim` (both the one- and two-argument forms).

```sql theme={null}
-- Whitespace trimming
SELECT ltrim('   Hello');          -- 'Hello'
SELECT rtrim('Hello   ');          -- 'Hello'
SELECT trim('   Hello   ');        -- 'Hello'
SELECT btrim('   Hello   ');       -- 'Hello'

-- Character trimming
SELECT ltrim('xxxHello', 'x');       -- 'Hello'
SELECT rtrim('Helloyyy', 'y');       -- 'Hello'
SELECT trim('***Hello***', '*');     -- 'Hello'
SELECT trim('abcHelloabc', 'abc');   -- 'Hello' (removes any of a, b, or c)
SELECT btrim('xyzHelloxyz', 'xyz');  -- 'Hello'
```

### max(X, Y, ...) and min(X, Y, ...)

The multi-argument forms of `max` and `min` return the largest or smallest argument, respectively. Arguments are compared using the standard SQLite comparison rules. If any argument is NULL, the result is NULL.

```sql theme={null}
SELECT max(1, 5, 3);          -- 5
SELECT min(1, 5, 3);          -- 1
SELECT max('apple', 'banana'); -- 'banana'
SELECT min(10, NULL, 3);      -- NULL
```

<Info>
  The multi-argument `max()` and `min()` are scalar functions. When called with a single argument inside an aggregate query (e.g., `SELECT max(salary) FROM employees`), they act as [aggregate functions](/sql-reference/functions/aggregate).
</Info>

### nullif(X, Y)

Returns NULL if X equals Y, otherwise returns X. This is useful for converting sentinel values to NULL.

```sql theme={null}
SELECT nullif(0, 0);        -- NULL
SELECT nullif(5, 0);        -- 5
SELECT nullif('N/A', 'N/A'); -- NULL
SELECT nullif('hello', '');  -- 'hello'
```

### quote(X)

Returns the text of an SQL literal that represents the value X. Strings are enclosed in single quotes with escaping. BLOBs are encoded as hex literals. NULL returns the string `'NULL'`. Numbers are returned as-is.

```sql theme={null}
SELECT quote('it''s');       -- '''it''s'''
SELECT quote(42);            -- '42'
SELECT quote(NULL);          -- 'NULL'
SELECT quote(x'CAFE');       -- 'X''CAFE'''
```

### random() and randomblob(N)

`random` returns a pseudo-random 64-bit signed integer. `randomblob` returns a blob of N pseudo-random bytes.

```sql theme={null}
SELECT random();           -- e.g., -4520312828827489743
SELECT hex(randomblob(4)); -- e.g., 'A1B2C3D4' (4 random bytes)
SELECT abs(random()) % 100; -- random number between 0 and 99
```

### lpad(X, N) and lpad(X, N, F)

Left-pads the string X with the fill string F until the result has exactly N characters. F defaults to a single space. If X is already at least N characters long, it is truncated from the right to length N. F is cycled when it is more than one character long. Returns NULL if any argument is NULL. If F is an empty string, the input is returned unchanged.

Lengths are counted in Unicode characters, not bytes, so multi-byte input is padded correctly.

```sql theme={null}
SELECT lpad('abc', 6);              -- '   abc'
SELECT lpad('abc', 6, 'xy');        -- 'xyxabc'
SELECT lpad('abcdef', 3);           -- 'abc'   (truncated from the right)
SELECT lpad('aéc', 4);              -- ' aéc'  (character count, not bytes)
SELECT lpad('abc', 10, '');         -- 'abc'   (empty fill)
SELECT lpad(NULL, 5);               -- NULL
```

### rpad(X, N) and rpad(X, N, F)

Right-pads the string X with the fill string F until the result has exactly N characters. F defaults to a single space. Truncation, fill cycling, NULL propagation, and empty-fill handling are identical to `lpad` (see the section above).

```sql theme={null}
SELECT rpad('abc', 6);              -- 'abc   '
SELECT rpad('abc', 6, 'xy');        -- 'abcxyx'
SELECT rpad('abcdef', 3);           -- 'abc'   (truncated from the right)
SELECT rpad('aéc', 4);              -- 'aéc '
```

### repeat(X, N)

Returns the string X concatenated with itself N times. Returns an empty string when `N <= 0`. Returns NULL if either argument is NULL. Non-text inputs are converted to their text representation first.

```sql theme={null}
SELECT repeat('ab', 3);      -- 'ababab'
SELECT repeat('x', 0);       -- ''
SELECT repeat('x', -1);      -- ''
SELECT repeat('', 5);        -- ''
SELECT repeat(NULL, 3);      -- NULL
```

### replace(X, Y, Z)

Returns a copy of string X with every occurrence of string Y replaced by string Z. If Y is empty, X is returned unchanged.

```sql theme={null}
SELECT replace('Hello World', 'World', 'Turso');   -- 'Hello Turso'
SELECT replace('aabbcc', 'bb', 'XX');               -- 'aaXXcc'
SELECT replace('2024-01-15', '-', '/');              -- '2024/01/15'
```

### reverse(X) and string\_reverse(X)

Returns string X with its characters in reverse order. Reversal is done on Unicode characters, so multi-byte characters are preserved intact. Returns NULL if X is NULL. Non-text inputs are converted to their text representation first.

`reverse` is the PostgreSQL (since version 17), MySQL, and Oracle spelling; `string_reverse` is the canonical Turso name. The two are interchangeable.

```sql theme={null}
SELECT reverse('hello');         -- 'olleh'
SELECT reverse('');              -- ''
SELECT reverse('café');          -- 'éfac'
SELECT string_reverse('abc');    -- 'cba'
SELECT reverse(NULL);            -- NULL
```

### round(X) and round(X, Y)

Rounds X to Y decimal places. If Y is omitted, it defaults to 0. The return type is always REAL.

```sql theme={null}
SELECT round(3.7);         -- 4.0
SELECT round(3.14159, 2);  -- 3.14
SELECT round(2.5);         -- 3.0
SELECT round(-2.5);        -- -3.0
```

### sign(X)

Returns -1 for negative values, 0 for zero, and 1 for positive values. Returns NULL if X is NULL.

```sql theme={null}
SELECT sign(-42);    -- -1
SELECT sign(0);      -- 0
SELECT sign(3.14);   -- 1
SELECT sign(NULL);   -- NULL
```

### substr(X, Y) and substr(X, Y, Z)

Returns a substring of X starting at the Y-th character (1-based). If Z is provided, the substring is at most Z characters long. Negative Y counts from the end of the string. `substring` is an alias.

```sql theme={null}
SELECT substr('Hello World', 7);       -- 'World'
SELECT substr('Hello World', 1, 5);    -- 'Hello'
SELECT substr('Hello World', -5);      -- 'World'
SELECT substring('Hello', 2, 3);       -- 'ell'
```

### typeof(X)

Returns the storage class of X as a lowercase string: `"null"`, `"integer"`, `"real"`, `"text"`, or `"blob"`.

```sql theme={null}
SELECT typeof(42);         -- 'integer'
SELECT typeof(3.14);       -- 'real'
SELECT typeof('hello');    -- 'text'
SELECT typeof(NULL);       -- 'null'
SELECT typeof(x'CAFE');    -- 'blob'
SELECT typeof(1 + 1.0);   -- 'real'
```

### unicode(X)

Returns the Unicode code point of the first character of string X. Returns NULL if X is NULL or an empty string.

```sql theme={null}
SELECT unicode('A');     -- 65
SELECT unicode('Hello'); -- 72 (code point of 'H')
```

### soundex(X)

Returns the Soundex encoding of string X as a four-character code. Soundex encodes a string based on how it sounds in English, which is useful for fuzzy name matching.

```sql theme={null}
SELECT soundex('Robert');    -- 'R163'
SELECT soundex('Rupert');    -- 'R163'
SELECT soundex('Smith');     -- 'S530'
SELECT soundex('Smythe');    -- 'S530'
```

### zeroblob(N)

Returns a blob consisting of N zero bytes (0x00). Useful for pre-allocating blob storage.

```sql theme={null}
SELECT length(zeroblob(10));    -- 10
SELECT hex(zeroblob(4));        -- '00000000'
```

### last\_insert\_rowid()

Returns the rowid of the most recent successful INSERT on the current database connection. Returns 0 if no INSERT has been performed.

```sql theme={null}
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO users (name) VALUES ('Alice');
SELECT last_insert_rowid();  -- 1
INSERT INTO users (name) VALUES ('Bob');
SELECT last_insert_rowid();  -- 2
```

### changes() and total\_changes()

`changes` returns the number of rows modified by the most recent INSERT, UPDATE, or DELETE statement. `total_changes` returns the total number of rows modified since the database connection was opened.

```sql theme={null}
CREATE TABLE t (x INTEGER);
INSERT INTO t VALUES (1), (2), (3);
SELECT changes();        -- 3

DELETE FROM t WHERE x > 1;
SELECT changes();        -- 2
SELECT total_changes();  -- 5 (3 inserted + 2 deleted)
```

### sqlite\_version()

Returns the SQLite-compatible version string.

```sql theme={null}
SELECT sqlite_version();  -- '3.42.0'
```

### turso\_version()

Returns the version string of the Turso engine.

```sql theme={null}
SELECT turso_version();  -- e.g. '0.7.0'
```

### load\_extension(X)

Loads a Turso-native extension from the shared library at path X.

```sql theme={null}
SELECT load_extension('./my_extension');
```

<Info>
  Extension loading must be enabled on the database connection. See the [Extensions documentation](/docs/extensions) for details on building and loading extensions.
</Info>

## See Also

* [Aggregate Functions](/sql-reference/functions/aggregate) for functions that operate across groups of rows
* [Expressions](/sql-reference/expressions) for operator syntax, CAST, CASE, and subqueries
* [Data Types](/sql-reference/data-types) for storage classes and type affinity
* [SELECT](/sql-reference/statements/select) for using functions in queries


# Vector Functions
Source: https://docs.turso.tech/sql-reference/functions/vector

Create, compare, and manipulate vector embeddings

# Vector Functions

<Info>
  **Turso Extension**: Vector functions are a Turso-specific feature for working with vector embeddings directly in SQL. These functions are not part of the SQLite standard.
</Info>

Turso provides built-in functions for storing and querying vector embeddings. Vectors are stored as BLOBs in a compact binary format and can be compared using distance functions for similarity search.

***

## Vector Creation

### vector32

Creates a 32-bit floating-point vector from a JSON array of numbers.

```sql theme={null}
vector32(json_array)
```

| Parameter    | Type | Description                                       |
| ------------ | ---- | ------------------------------------------------- |
| `json_array` | TEXT | A JSON array of numbers, e.g. `'[1.0, 2.0, 3.0]'` |

**Returns:** BLOB -- the vector in 32-bit float binary format.

This is the most common vector format and offers a good balance of precision and storage efficiency. Each dimension uses 4 bytes.

```sql theme={null}
SELECT vector32('[1.0, 2.0, 3.0]');
-- (BLOB: 12 bytes)

-- Store embeddings in a table
CREATE TABLE documents (
    id INTEGER PRIMARY KEY,
    content TEXT,
    embedding BLOB
);

INSERT INTO documents VALUES (1, 'Introduction to databases', vector32('[0.1, 0.3, 0.5, 0.7]'));
```

### vector64

Creates a 64-bit floating-point vector from a JSON array of numbers.

```sql theme={null}
vector64(json_array)
```

| Parameter    | Type | Description                                       |
| ------------ | ---- | ------------------------------------------------- |
| `json_array` | TEXT | A JSON array of numbers, e.g. `'[1.0, 2.0, 3.0]'` |

**Returns:** BLOB -- the vector in 64-bit float binary format.

Use this format when you need higher precision. Each dimension uses 8 bytes.

```sql theme={null}
SELECT vector64('[1.0, 2.0, 3.0]');
-- (BLOB: 24 bytes)
```

### vector

Creates a vector using the default format (32-bit float).

```sql theme={null}
vector(json_array)
```

| Parameter    | Type | Description             |
| ------------ | ---- | ----------------------- |
| `json_array` | TEXT | A JSON array of numbers |

**Returns:** BLOB -- the vector in default (32-bit float) binary format.

```sql theme={null}
SELECT vector('[1.0, 2.0, 3.0]');
```

### vector8

Creates an 8-bit quantized vector from a JSON array of numbers.

```sql theme={null}
vector8(json_array)
```

| Parameter    | Type | Description                            |
| ------------ | ---- | -------------------------------------- |
| `json_array` | TEXT | A JSON array of integer values (0-255) |

**Returns:** BLOB -- the vector in 8-bit integer binary format.

Use this format for storage-efficient approximate representations. Each dimension uses 1 byte.

```sql theme={null}
SELECT vector8('[128, 64, 255, 0]');
```

### vector1bit

Creates a 1-bit binary vector from a JSON array.

```sql theme={null}
vector1bit(json_array)
```

| Parameter    | Type | Description               |
| ------------ | ---- | ------------------------- |
| `json_array` | TEXT | A JSON array of 0s and 1s |

**Returns:** BLOB -- the vector in 1-bit binary format.

Use this format for extremely compact representations. Eight dimensions are packed into a single byte.

```sql theme={null}
SELECT vector1bit('[1, 0, 1, 1, 0, 0, 1, 0]');
```

### vector32\_sparse

Creates a 32-bit floating-point **sparse** vector from a JSON array. Only non-zero components are stored, which is efficient for high-dimensional vectors that are mostly zero.

```sql theme={null}
vector32_sparse(json_array)
```

| Parameter    | Type | Description                                        |
| ------------ | ---- | -------------------------------------------------- |
| `json_array` | TEXT | A JSON array of numbers, typically with many zeros |

**Returns:** BLOB -- the vector in 32-bit float sparse binary format.

```sql theme={null}
SELECT vector32_sparse('[0.0, 0.0, 1.5, 0.0, 0.0, 2.5]');
```

<Info>
  Sparse vectors can be indexed with the experimental sparse vector index method (enable `--experimental-index-method`). See [CREATE INDEX](/sql-reference/statements/create-index).
</Info>

***

## Distance Functions

Distance functions compute the distance between two vectors. Smaller values indicate more similar vectors.

### vector\_distance\_cos

Computes the cosine distance between two vectors, defined as `1 - cosine_similarity`. A result of 0 means the vectors are identical in direction; a result of 1 means they are orthogonal.

```sql theme={null}
vector_distance_cos(v1, v2)
```

| Parameter | Type | Description                                                     |
| --------- | ---- | --------------------------------------------------------------- |
| `v1`      | BLOB | First vector                                                    |
| `v2`      | BLOB | Second vector (must have the same number of dimensions as `v1`) |

**Returns:** REAL -- the cosine distance between the two vectors.

```sql theme={null}
SELECT vector_distance_cos(
    vector32('[1.0, 0.0, 0.0]'),
    vector32('[0.0, 1.0, 0.0]')
);
-- 1.0 (orthogonal vectors)

SELECT vector_distance_cos(
    vector32('[1.0, 0.0]'),
    vector32('[1.0, 0.0]')
);
-- 0.0 (identical vectors)
```

### vector\_distance\_l2

Computes the Euclidean (L2) distance between two vectors. A result of 0 means the vectors are identical.

```sql theme={null}
vector_distance_l2(v1, v2)
```

| Parameter | Type | Description                                                     |
| --------- | ---- | --------------------------------------------------------------- |
| `v1`      | BLOB | First vector                                                    |
| `v2`      | BLOB | Second vector (must have the same number of dimensions as `v1`) |

**Returns:** REAL -- the Euclidean distance between the two vectors.

```sql theme={null}
SELECT vector_distance_l2(
    vector32('[1.0, 0.0, 0.0]'),
    vector32('[0.0, 1.0, 0.0]')
);
-- 1.4142135623730951

SELECT vector_distance_l2(
    vector32('[3.0, 4.0]'),
    vector32('[0.0, 0.0]')
);
-- 5.0
```

### vector\_distance\_dot

Computes the negative dot product distance between two vectors. The result is the negation of the dot product, so smaller (more negative) values indicate higher similarity.

```sql theme={null}
vector_distance_dot(v1, v2)
```

| Parameter | Type | Description                                                     |
| --------- | ---- | --------------------------------------------------------------- |
| `v1`      | BLOB | First vector                                                    |
| `v2`      | BLOB | Second vector (must have the same number of dimensions as `v1`) |

**Returns:** REAL -- the negative dot product of the two vectors.

```sql theme={null}
SELECT vector_distance_dot(
    vector32('[1.0, 2.0, 3.0]'),
    vector32('[4.0, 5.0, 6.0]')
);
-- -32.0 (dot product is 32)
```

### vector\_distance\_jaccard

Computes the Jaccard distance between two binary vectors. Best suited for use with `vector1bit` vectors.

```sql theme={null}
vector_distance_jaccard(v1, v2)
```

| Parameter | Type | Description                                                     |
| --------- | ---- | --------------------------------------------------------------- |
| `v1`      | BLOB | First vector                                                    |
| `v2`      | BLOB | Second vector (must have the same number of dimensions as `v1`) |

**Returns:** REAL -- the Jaccard distance between the two vectors.

***

## Utility Functions

### vector\_extract

Converts a vector BLOB back to a JSON text representation.

```sql theme={null}
vector_extract(vector_blob)
```

| Parameter     | Type | Description                                                            |
| ------------- | ---- | ---------------------------------------------------------------------- |
| `vector_blob` | BLOB | A vector created by `vector32`, `vector64`, or another vector function |

**Returns:** TEXT -- a JSON array string of the vector's components.

```sql theme={null}
SELECT vector_extract(vector32('[1.5, 2.5, 3.5]'));
-- [1.500000,2.500000,3.500000]
```

### vector\_concat

Concatenates two or more vectors into a single vector.

```sql theme={null}
vector_concat(v1, v2, ...)
```

| Parameter     | Type | Description                                                            |
| ------------- | ---- | ---------------------------------------------------------------------- |
| `v1, v2, ...` | BLOB | Two or more vectors to concatenate. All vectors must be the same type. |

**Returns:** BLOB -- a new vector containing the dimensions of all input vectors in order.

```sql theme={null}
SELECT vector_extract(
    vector_concat(
        vector32('[1.0, 2.0]'),
        vector32('[3.0, 4.0]')
    )
);
-- [1.000000,2.000000,3.000000,4.000000]
```

### vector\_slice

Extracts a contiguous portion of a vector.

```sql theme={null}
vector_slice(vector_blob, start, end)
```

| Parameter     | Type    | Description                         |
| ------------- | ------- | ----------------------------------- |
| `vector_blob` | BLOB    | The source vector                   |
| `start`       | INTEGER | Start index (zero-based, inclusive) |
| `end`         | INTEGER | End index (exclusive)               |

**Returns:** BLOB -- a new vector containing dimensions from `start` to `end - 1`.

```sql theme={null}
SELECT vector_extract(
    vector_slice(vector32('[10.0, 20.0, 30.0, 40.0, 50.0]'), 1, 4)
);
-- [20.000000,30.000000,40.000000]
```

***

## Examples

### Creating a table with vector embeddings

```sql theme={null}
CREATE TABLE articles (
    id INTEGER PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT,
    embedding BLOB
);

INSERT INTO articles VALUES (
    1,
    'Introduction to Machine Learning',
    'Machine learning is a subset of artificial intelligence...',
    vector32('[0.12, -0.34, 0.56, 0.78, -0.11, 0.45, -0.23, 0.67]')
);

INSERT INTO articles VALUES (
    2,
    'Database Design Patterns',
    'Relational databases organize data into tables...',
    vector32('[0.82, 0.15, -0.44, 0.23, 0.61, -0.33, 0.09, 0.77]')
);

INSERT INTO articles VALUES (
    3,
    'Neural Networks Explained',
    'Neural networks are computing systems inspired by biological neural networks...',
    vector32('[0.14, -0.28, 0.62, 0.71, -0.09, 0.51, -0.18, 0.59]')
);
```

### Similarity search with ORDER BY distance

```sql theme={null}
-- Find articles most similar to a query embedding using cosine distance
SELECT
    id,
    title,
    vector_distance_cos(embedding, vector32('[0.1, -0.3, 0.5, 0.8, -0.1, 0.4, -0.2, 0.6]')) AS distance
FROM articles
ORDER BY distance ASC
LIMIT 5;
```

### Cosine distance vs. L2 distance

Cosine distance measures the angle between two vectors, ignoring magnitude. L2 distance measures the absolute distance in space.

```sql theme={null}
-- Cosine distance: direction matters, not magnitude
SELECT vector_distance_cos(
    vector32('[1.0, 0.0]'),
    vector32('[100.0, 0.0]')
);
-- 0.0 (same direction, cosine distance is 0)

-- L2 distance: magnitude matters
SELECT vector_distance_l2(
    vector32('[1.0, 0.0]'),
    vector32('[100.0, 0.0]')
);
-- 99.0 (very far apart in absolute terms)
```

Use cosine distance when you care about the direction of the vector (e.g., semantic similarity of text embeddings). Use L2 distance when absolute position matters (e.g., geospatial coordinates or image feature vectors).

### Working with vector data

```sql theme={null}
-- Inspect the contents of a stored vector
SELECT vector_extract(embedding) FROM articles WHERE id = 1;

-- Concatenate vectors from two different models
SELECT vector_extract(
    vector_concat(
        (SELECT embedding FROM articles WHERE id = 1),
        (SELECT embedding FROM articles WHERE id = 2)
    )
);

-- Extract the first 4 dimensions of an embedding
SELECT vector_extract(
    vector_slice(embedding, 0, 4)
) FROM articles WHERE id = 1;
-- [0.120000,-0.340000,0.560000,0.780000]
```

<Note>
  By default, similarity searches use a linear scan over the table. An experimental sparse vector index method is available behind the `--experimental-index-method` flag; without it, consider limiting search to a subset of rows with a WHERE clause for large datasets.
</Note>

## See Also

* [CREATE INDEX](/sql-reference/statements/create-index) for index types including FTS
* [Data Types](/sql-reference/data-types) for how BLOBs are handled in Turso


# Window Functions
Source: https://docs.turso.tech/sql-reference/functions/window

Perform calculations across sets of rows related to the current row without collapsing them

# Window Functions

Window functions perform calculations across a set of rows that are related to the current row. Unlike aggregate functions with GROUP BY, window functions do not collapse rows into a single output row. Every input row produces a corresponding output row, with the window function result appended.

<Info>
  Turso supports aggregate functions used as window functions with the default frame definition, the `row_number()` ranking function, and the `FILTER (WHERE ...)` clause. The remaining dedicated window functions (`rank`, `dense_rank`, `ntile`, `lag`, `lead`, `first_value`, `last_value`, `nth_value`) and custom frame specifications (ROWS, RANGE, or GROUPS with explicit bounds) are not yet supported.
</Info>

## Syntax

```sql theme={null}
aggregate_function(expression) OVER (
    [PARTITION BY expression [, ...]]
    [ORDER BY expression [ASC | DESC] [, ...]]
)
```

| Clause               | Description                                                                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aggregate_function` | Any supported aggregate function: `count`, `sum`, `avg`, `min`, `max`, `total`, `group_concat`                                                          |
| `OVER (...)`         | Defines the window over which the function operates                                                                                                     |
| `PARTITION BY`       | Divides the result set into partitions. The function is applied independently within each partition. If omitted, the entire result set is one partition |
| `ORDER BY`           | Defines the order of rows within each partition. This determines which rows are included in the frame for each calculation                              |

### Default Frame

When ORDER BY is specified, the default frame is:

```
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
```

This means the function considers all rows from the start of the partition up to and including the current row (and any rows with equal ORDER BY values, since the frame mode is RANGE).

When ORDER BY is omitted, the default frame covers the entire partition.

## Supported Aggregate Functions as Window Functions

Any aggregate function can be used as a window function by adding an OVER clause.

| Function                              | Description                                                |
| ------------------------------------- | ---------------------------------------------------------- |
| `count(*)`                            | Number of rows in the frame                                |
| `count(expression)`                   | Number of non-NULL values in the frame                     |
| `sum(expression)`                     | Sum of non-NULL values in the frame                        |
| `avg(expression)`                     | Average of non-NULL values in the frame                    |
| `min(expression)`                     | Minimum value in the frame                                 |
| `max(expression)`                     | Maximum value in the frame                                 |
| `total(expression)`                   | Sum as REAL (returns 0.0 for empty frames instead of NULL) |
| `group_concat(expression, separator)` | Concatenation of values in the frame                       |

## Ranking Functions

### row\_number()

Assigns a sequential integer (starting at 1) to each row within its partition, in the order defined by the window's `ORDER BY`. This is the one dedicated ranking function supported in Turso.

```sql theme={null}
SELECT
    department,
    name,
    salary,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_in_dept
FROM employees;
```

| department  | name  | salary | rank\_in\_dept |
| ----------- | ----- | ------ | -------------- |
| Engineering | Alice | 90000  | 1              |
| Engineering | Bob   | 85000  | 2              |
| Engineering | Carol | 75000  | 3              |
| Marketing   | Dave  | 70000  | 1              |
| Marketing   | Eve   | 60000  | 2              |

## FILTER (WHERE ...)

A `FILTER (WHERE ...)` clause restricts which rows an aggregate window function includes, without affecting the rows returned by the query.

```sql theme={null}
SELECT
    department,
    name,
    salary,
    SUM(salary) FILTER (WHERE salary > 70000) OVER (PARTITION BY department) AS high_earner_total
FROM employees;
```

## PARTITION BY

PARTITION BY divides the rows into groups. The window function resets and recalculates independently for each partition.

```sql theme={null}
SELECT
    department,
    name,
    salary,
    SUM(salary) OVER (PARTITION BY department) AS dept_total
FROM employees;
```

| department  | name  | salary | dept\_total |
| ----------- | ----- | ------ | ----------- |
| Engineering | Alice | 90000  | 250000      |
| Engineering | Bob   | 85000  | 250000      |
| Engineering | Carol | 75000  | 250000      |
| Marketing   | Dave  | 70000  | 130000      |
| Marketing   | Eve   | 60000  | 130000      |

Without PARTITION BY, the function treats the entire result set as one partition:

```sql theme={null}
SELECT
    name,
    salary,
    SUM(salary) OVER () AS company_total
FROM employees;
```

## ORDER BY

ORDER BY within the OVER clause determines row ordering within each partition. Combined with the default frame (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), this produces running calculations.

```sql theme={null}
SELECT
    name,
    salary,
    SUM(salary) OVER (ORDER BY salary) AS running_total
FROM employees;
```

| name  | salary | running\_total |
| ----- | ------ | -------------- |
| Eve   | 60000  | 60000          |
| Dave  | 70000  | 130000         |
| Carol | 75000  | 205000         |
| Bob   | 85000  | 290000         |
| Alice | 90000  | 380000         |

### PARTITION BY with ORDER BY

Use both clauses together for running calculations within groups:

```sql theme={null}
SELECT
    department,
    name,
    salary,
    SUM(salary) OVER (
        PARTITION BY department
        ORDER BY salary
    ) AS dept_running_total
FROM employees;
```

| department  | name  | salary | dept\_running\_total |
| ----------- | ----- | ------ | -------------------- |
| Engineering | Carol | 75000  | 75000                |
| Engineering | Bob   | 85000  | 160000               |
| Engineering | Alice | 90000  | 250000               |
| Marketing   | Eve   | 60000  | 60000                |
| Marketing   | Dave  | 70000  | 130000               |

## Named Windows

The WINDOW clause defines a reusable window specification that can be referenced by multiple window functions in the same query. This avoids repeating the same OVER definition.

```sql theme={null}
SELECT
    department,
    name,
    salary,
    SUM(salary) OVER w AS running_total,
    AVG(salary) OVER w AS running_avg,
    COUNT(*) OVER w AS running_count
FROM employees
WINDOW w AS (PARTITION BY department ORDER BY salary)
ORDER BY department, salary;
```

Multiple named windows can be defined:

```sql theme={null}
SELECT
    department,
    name,
    salary,
    SUM(salary) OVER dept AS dept_total,
    SUM(salary) OVER company AS company_total
FROM employees
WINDOW
    dept AS (PARTITION BY department),
    company AS ()
ORDER BY department, name;
```

## Examples

### Running Total

```sql theme={null}
SELECT
    order_date,
    amount,
    SUM(amount) OVER (ORDER BY order_date) AS running_total
FROM orders;
```

### Count per Group

```sql theme={null}
-- Show each employee alongside their department headcount
SELECT
    name,
    department,
    COUNT(*) OVER (PARTITION BY department) AS dept_size
FROM employees
ORDER BY department, name;
```

### Running Average

```sql theme={null}
SELECT
    date,
    temperature,
    AVG(temperature) OVER (ORDER BY date) AS running_avg_temp
FROM weather_readings
ORDER BY date;
```

### Percentage of Total

```sql theme={null}
SELECT
    product_name,
    revenue,
    ROUND(100.0 * revenue / SUM(revenue) OVER (), 2) AS pct_of_total
FROM products
ORDER BY revenue DESC;
```

### Multiple Window Functions in One Query

```sql theme={null}
SELECT
    department,
    name,
    salary,
    MIN(salary) OVER (PARTITION BY department) AS dept_min,
    MAX(salary) OVER (PARTITION BY department) AS dept_max,
    AVG(salary) OVER (PARTITION BY department) AS dept_avg,
    salary - AVG(salary) OVER (PARTITION BY department) AS diff_from_avg
FROM employees
ORDER BY department, salary DESC;
```

### Group Concatenation over a Window

```sql theme={null}
SELECT
    department,
    name,
    GROUP_CONCAT(name, ', ') OVER (PARTITION BY department ORDER BY name) AS names_so_far
FROM employees;
```

## Limitations

The following window function features are not yet supported in Turso:

| Feature                                   | Status        |
| ----------------------------------------- | ------------- |
| `rank()`                                  | Not supported |
| `dense_rank()`                            | Not supported |
| `ntile(N)`                                | Not supported |
| `lag(expr, offset, default)`              | Not supported |
| `lead(expr, offset, default)`             | Not supported |
| `first_value(expr)`                       | Not supported |
| `last_value(expr)`                        | Not supported |
| `nth_value(expr, N)`                      | Not supported |
| `cume_dist()`                             | Not supported |
| `percent_rank()`                          | Not supported |
| Custom frame: `ROWS BETWEEN ...`          | Not supported |
| Custom frame: `RANGE BETWEEN ... AND ...` | Not supported |
| Custom frame: `GROUPS BETWEEN ...`        | Not supported |
| `EXCLUDE` clause                          | Not supported |

`row_number()` and `FILTER (WHERE ...)` are supported — see the sections above.

## See Also

* [SELECT](/sql-reference/statements/select) for the full SELECT syntax including the WINDOW clause
* [Aggregate Functions](/sql-reference/functions/aggregate) for aggregate function reference
* [Expressions](/sql-reference/expressions) for using window function results in expressions


# Multi-Process Access
Source: https://docs.turso.tech/sql-reference/multiprocess-access

Share a Turso database file between multiple OS processes using the experimental multiprocess WAL coordinator

# Multi-Process Access

<Warning>
  Multi-process access is **experimental**. The on-disk coordination format and the public API may change between releases. Do not rely on the format for long-term storage across Turso versions.
</Warning>

By default, a Turso database file is opened by a single OS process. Multiple threads and connections within that process can safely share the database, but opening the same file from a second process is rejected with a locking error.

Multi-process access removes that restriction. When the `multiprocess_wal` feature is enabled, several processes can open the same `.db` file concurrently and coordinate WAL reads, writes, and checkpoints through a shared memory file.

## When to Use It

Enable multi-process access when you need:

* Multiple independent OS processes (workers, sidecars, CLI + embedded app) to read and write the same database file without a server tier.
* Zero-downtime deploys where an old process and a new process briefly overlap on the same database.
* Process-per-connection deployments that need stronger isolation than threads.

For single-process applications — including multi-threaded servers using a connection pool — the default mode is faster and requires no configuration.

## How It Works

When multi-process access is enabled, Turso creates an additional sibling file next to the database:

| File           | Purpose                                                                                                                                                           |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mydb.db`      | The database file (unchanged).                                                                                                                                    |
| `mydb.db-wal`  | The write-ahead log (unchanged).                                                                                                                                  |
| `mydb.db-tshm` | Turso shared memory. Memory-mapped coordinator that tracks WAL state, the active writer, the active checkpointer, reader slots, and a shared page-to-frame index. |

All participating processes mmap the `.tshm` file and coordinate through:

* A **single-writer** slot. At most one process across the cluster holds the writer lock at any time.
* A **single-checkpointer** slot. Checkpointing is serialized across processes.
* A bounded set of **reader slots**. Each active read transaction pins a WAL frame so it is not overwritten by concurrent writers or reclaimed by the checkpointer.
* A shared **frame index** so readers in any process can resolve a page number to the latest WAL frame without scanning the WAL from the beginning.

Cross-process byte-range locks on the `.tshm` file (OFD locks on Linux, `fcntl` on macOS) gate ownership transitions; the mmap regions provide the fast path for metadata lookups.

## Requirements

Multi-process WAL is only available when all of the following hold:

* **Platform**:  64-bit Unix-like OS (Linux, macOS, Android, other supported Unix systems). On Windows, WASM, and 32-bit targets the flag is accepted but has no effect, and the database opens in single-process mode.

* **IO backend**: The active IO backend must implement shared WAL coordination. The default file-backed backends do; memory-only and some custom VFSes do not.

* **Filesystem**: The database must live on a local filesystem that implements POSIX byte-range locks and mmap correctly. The following filesystems are **explicitly rejected** because shared mmap coordination is not safe on them:

  | Filesystem                | Reason                                                         |
  | ------------------------- | -------------------------------------------------------------- |
  | NFS                       | Advisory lock semantics diverge across clients                 |
  | CIFS / SMB2               | Same as above; mmap coherence is not guaranteed                |
  | CephFS                    | Distributed cache semantics                                    |
  | GFS2                      | Cluster filesystem; not validated                              |
  | Lustre                    | Distributed; not validated                                     |
  | OCFS2                     | Cluster filesystem; not validated                              |
  | AFS, CODA, NCP, 9P (v9fs) | Network/legacy filesystems with unreliable mmap+lock semantics |

  Opening a database on any of these filesystems with `multiprocess_wal` enabled returns `InvalidArgument`.

* **Not in-memory**: `:memory:` and `file::memory:` paths are rejected — shared coordination requires a durable file.

## Enabling Multi-Process Access

### CLI

Pass `--experimental-multiprocess-wal`:

```bash theme={null}
tursodb --experimental-multiprocess-wal mydb.db
```

Every `tursodb` (or SDK) process that opens `mydb.db` at the same time must pass the flag. Mixing modes is rejected — see [Mixing Modes](#mixing-modes).

### Rust SDK

```rust theme={null}
use turso::Builder;

let db = Builder::new_local("mydb.db")
    .experimental_multiprocess_wal(true)
    .build()?;
```

### JavaScript / Node.js SDK

```typescript theme={null}
import { Database } from "@tursodatabase/libsql";

const db = new Database("file:mydb.db", {
    experimental: ["multiprocess_wal"],
});
```

### Python SDK

```python theme={null}
import turso

conn = turso.connect(
    "mydb.db",
    experimental_features="multiprocess_wal",
)
```

### Go SDK

```go theme={null}
db, err := turso.NewDatabase(turso.TursoDatabaseConfig{
    Path:                 "mydb.db",
    ExperimentalFeatures: "multiprocess_wal",
})
```

Or via DSN:

```go theme={null}
db, err := sql.Open("turso", "mydb.db?experimental=multiprocess_wal")
```

## Concurrency Model

Multi-process access preserves Turso's WAL concurrency guarantees across processes:

* **Writers are serialized across processes.** At any instant, at most one process (and one connection within that process) holds the writer slot. Writers in other processes block until it is released.
* **Readers never block writers.** Each active read transaction registers a reader slot that pins its snapshot's max-frame. A reader in one process cannot be invalidated by a writer in another.
* **Checkpointing is serialized across processes.** A single checkpointer truncates the WAL; readers pinning older frames prevent premature reclamation regardless of which process owns them.
* **Snapshots are stable.** Each read transaction observes a consistent snapshot, even if a concurrent process commits new frames.

Schema changes (DDL) made in one process are picked up by other processes on their next statement via the existing schema refresh path — prepared statements in sibling processes may return `SchemaUpdated` and re-prepare, just as they do across connections within a single process.

## Mixing Modes

The database cannot be open simultaneously in single-process mode from one process and multi-process mode from another. The open path probes for an incompatible live opener and fails fast:

* Opening without `multiprocess_wal` while another process holds a live `.tshm` authority fails with:
  > Database is already open with experimental multiprocess WAL in another process
* Opening with `multiprocess_wal` while another process holds the legacy exclusive DB-file lock fails with:
  > Database is already open without experimental multiprocess WAL in another process

To switch modes, close all existing connections to the database, then reopen with the desired configuration. The `.tshm` file may be left in place — Turso reuses it on the next multi-process open and rebuilds its state from the WAL if necessary.

Read-only opens are advisory: if `multiprocess_wal` is requested on a read-only open and no `.tshm` file exists, Turso falls back to the legacy read-only WAL path rather than failing. This lets read-only tooling work against a database that was closed cleanly by a single-process writer.

## Limitations

* The `.tshm` file format is versioned and may change in future releases. A format bump invalidates existing `.tshm` files — Turso recreates them on open; it does not attempt cross-version migration.
* Attached databases (via [ATTACH DATABASE](/sql-reference/statements/attach-database)) inherit the multi-process setting of the main connection.
* The feature is not yet supported under MVCC (`BEGIN CONCURRENT`). Use either MVCC within a single process or multi-process WAL with the default isolation model, not both.
* The concurrent-simulator and stress tests for multi-process mode run only on 64-bit Unix targets. Windows, WASM, and 32-bit builds silently ignore the flag.

## See Also

* [Experimental Features](/sql-reference/experimental-features) — how to enable experimental features across all surfaces
* [Transactions](/sql-reference/statements/transactions) — WAL concurrency model and `BEGIN CONCURRENT`
* [Compatibility](/sql-reference/compatibility) — SQLite compatibility matrix


# PRAGMA Statements
Source: https://docs.turso.tech/sql-reference/pragmas

Database configuration and metadata commands in Turso

# PRAGMA Statements

PRAGMA statements are special commands used to query or modify database configuration, retrieve metadata, and control database behavior. Unlike standard SQL, PRAGMAs are specific to SQLite and Turso.

## Syntax

```sql theme={null}
PRAGMA pragma-name;
PRAGMA pragma-name = value;
PRAGMA pragma-name(value);
```

## Database Metadata

### database\_list

Returns one row for each attached database.

```sql theme={null}
PRAGMA database_list;
-- seq | name | file
-- 0   | main | /path/to/database.db
```

### page\_count

Returns the total number of pages in the database file.

```sql theme={null}
PRAGMA page_count;
-- 42
```

### page\_size

Returns or sets the page size of the database. The page size can only be set before any tables are created.

```sql theme={null}
PRAGMA page_size;
-- 4096

PRAGMA page_size = 8192;
```

### max\_page\_count

Returns or sets the maximum number of pages allowed in the database file.

```sql theme={null}
PRAGMA max_page_count;
PRAGMA max_page_count = 1000000;
```

### freelist\_count

Returns the number of unused pages in the database file.

```sql theme={null}
PRAGMA freelist_count;
```

### encoding

Returns the text encoding used by the database.

```sql theme={null}
PRAGMA encoding;
-- UTF-8
```

### schema\_version

Returns the schema version number. This value is incremented each time the schema changes.

```sql theme={null}
PRAGMA schema_version;
-- 5
```

### application\_id

Returns or sets the application ID stored in the database header. Applications can use this 32-bit integer to identify the database file format.

```sql theme={null}
PRAGMA application_id;
PRAGMA application_id = 12345;
```

### user\_version

Returns or sets the user version number. This is a 32-bit integer available for application use.

```sql theme={null}
PRAGMA user_version;
PRAGMA user_version = 3;
```

## Schema Introspection

### table\_info

Returns one row for each column in the named table.

```sql theme={null}
PRAGMA table_info(table-name);
```

| Column      | Type    | Description                            |
| ----------- | ------- | -------------------------------------- |
| cid         | INTEGER | Column index (0-based)                 |
| name        | TEXT    | Column name                            |
| type        | TEXT    | Declared type name                     |
| notnull     | INTEGER | 1 if NOT NULL constraint exists        |
| dflt\_value | TEXT    | Default value expression, or NULL      |
| pk          | INTEGER | 1 if column is part of the PRIMARY KEY |

```sql theme={null}
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT);
PRAGMA table_info(users);
-- cid | name  | type    | notnull | dflt_value | pk
-- 0   | id    | INTEGER | 0       |            | 1
-- 1   | name  | TEXT    | 1       |            | 0
-- 2   | email | TEXT    | 0       |            | 0
```

### table\_xinfo

Similar to `table_info` but also includes hidden columns and additional metadata.

```sql theme={null}
PRAGMA table_xinfo(table-name);
```

Returns the same columns as `table_info` plus a `hidden` column (0 for normal columns, non-zero for hidden columns in virtual tables).

### table\_list

Returns one row for each table and view in the database.

```sql theme={null}
PRAGMA table_list;
-- schema | name    | type  | ncol | wr | strict
-- main   | users   | table | 3    | 0  | 0
-- main   | orders  | table | 4    | 0  | 0
```

### index\_list

Returns one row for each index on the named table.

```sql theme={null}
PRAGMA index_list(table-name);
-- seq | name           | unique | origin | partial
-- 0   | idx_email      | 1      | c      | 0
```

| Column  | Type    | Description                                                           |
| ------- | ------- | --------------------------------------------------------------------- |
| seq     | INTEGER | Index sequence number                                                 |
| name    | TEXT    | Index name                                                            |
| unique  | INTEGER | 1 if the index is UNIQUE                                              |
| origin  | TEXT    | `c` for CREATE INDEX, `u` for UNIQUE constraint, `pk` for PRIMARY KEY |
| partial | INTEGER | 1 if the index is a partial index                                     |

### index\_info

Returns one row for each column in the named index.

```sql theme={null}
PRAGMA index_info(index-name);
-- seqno | cid | name
-- 0     | 2   | email
```

### index\_xinfo

Similar to `index_info` but includes additional columns.

```sql theme={null}
PRAGMA index_xinfo(index-name);
```

### function\_list

Returns one row for each SQL function available.

```sql theme={null}
PRAGMA function_list;
-- name | builtin | type | enc | narg | flags
```

### pragma\_list

Returns the list of all supported PRAGMA commands.

```sql theme={null}
PRAGMA pragma_list;
```

## Database Configuration

### journal\_mode

Returns or sets the journal mode.

```sql theme={null}
PRAGMA journal_mode;
-- wal

PRAGMA journal_mode = wal;
```

Turso supports WAL (Write-Ahead Logging) mode. Rollback journal modes (DELETE, TRUNCATE, PERSIST, MEMORY) are not supported.

<Info>
  **Turso Extension**: Turso supports an experimental MVCC journal mode for concurrent writes:

  ```sql theme={null}
  PRAGMA journal_mode = mvcc;
  ```

  When MVCC mode is active, you can use `BEGIN CONCURRENT` for optimistic concurrent write transactions. See [Transactions](/sql-reference/statements/transactions#begin-concurrent) for details.
</Info>

### cache\_size

Returns or sets the suggested maximum number of database pages held in memory.

```sql theme={null}
PRAGMA cache_size;
PRAGMA cache_size = 2000;          -- positive: number of pages
PRAGMA cache_size = -2000;         -- negative: kilobytes of memory
```

### cache\_spill

Enables or disables cache spilling (writing dirty pages to the WAL before the cache is full).

```sql theme={null}
PRAGMA cache_spill;
PRAGMA cache_spill = 0;    -- disable
PRAGMA cache_spill = 1;    -- enable
```

### synchronous

Controls the fsync behavior for durability guarantees.

```sql theme={null}
PRAGMA synchronous;
PRAGMA synchronous = OFF;    -- no fsync (fastest, risk of corruption on crash)
PRAGMA synchronous = FULL;   -- fsync after every transaction (safest)
```

Only `OFF` and `FULL` are supported in Turso.

### temp\_store

Controls where temporary tables and indexes are stored.

```sql theme={null}
PRAGMA temp_store;
PRAGMA temp_store = 0;    -- DEFAULT
PRAGMA temp_store = 1;    -- FILE
PRAGMA temp_store = 2;    -- MEMORY
```

### busy\_timeout

Sets the busy timeout in milliseconds. When a table is locked, Turso waits up to this many milliseconds before returning SQLITE\_BUSY.

```sql theme={null}
PRAGMA busy_timeout;
PRAGMA busy_timeout = 5000;   -- 5 seconds
```

### query\_only

When enabled, prevents any changes to the database.

```sql theme={null}
PRAGMA query_only = 1;    -- enable read-only mode
PRAGMA query_only = 0;    -- disable read-only mode
```

### foreign\_keys

Enables or disables foreign key constraint enforcement.

```sql theme={null}
PRAGMA foreign_keys;
PRAGMA foreign_keys = ON;
PRAGMA foreign_keys = OFF;
```

Foreign key enforcement is off by default for SQLite compatibility.

### legacy\_file\_format

Returns the legacy file format flag.

```sql theme={null}
PRAGMA legacy_file_format;
```

### ignore\_check\_constraints

When enabled, CHECK constraints are not enforced.

```sql theme={null}
PRAGMA ignore_check_constraints = 1;   -- disable CHECK constraints
PRAGMA ignore_check_constraints = 0;   -- enable CHECK constraints
```

### data\_sync\_retry

Controls whether Turso retries a disk sync (fsync) after a failure instead of treating it as fatal.

```sql theme={null}
PRAGMA data_sync_retry = 1;   -- retry on sync failure
PRAGMA data_sync_retry = 0;   -- do not retry (default)
```

### require\_where

<Info>
  **Turso Extension**: a safety pragma with no SQLite equivalent.
</Info>

When enabled, Turso rejects any `UPDATE` or `DELETE` that does not include a `WHERE` clause, guarding against accidental full-table modifications. `i_am_a_dummy` is an alias that enables the same behavior.

```sql theme={null}
PRAGMA require_where = 1;
DELETE FROM users;            -- rejected: no WHERE clause
DELETE FROM users WHERE id = 1;  -- allowed

PRAGMA i_am_a_dummy = 1;      -- equivalent to require_where = 1
```

## Integrity Checks

### integrity\_check

Performs a thorough integrity check of the entire database.

```sql theme={null}
PRAGMA integrity_check;
-- ok

PRAGMA integrity_check(N);    -- check only the first N errors
```

Returns `ok` if no problems are found, otherwise returns one row per error.

### quick\_check

Performs a faster but less thorough integrity check than `integrity_check`.

```sql theme={null}
PRAGMA quick_check;
```

## WAL Operations

### wal\_checkpoint

Forces a WAL checkpoint.

```sql theme={null}
PRAGMA wal_checkpoint;
```

A checkpoint writes pages from the WAL file back to the database file.

## MVCC Tuning

<Info>
  **Turso Extension**: these pragmas apply only when MVCC mode is enabled (`PRAGMA journal_mode = mvcc`).
</Info>

### mvcc\_checkpoint\_threshold

Sets the amount of committed work that accumulates before Turso triggers an MVCC checkpoint.

```sql theme={null}
PRAGMA mvcc_checkpoint_threshold = 1000;
```

### mvcc\_gc\_threshold

Sets the threshold that controls how aggressively Turso garbage-collects obsolete MVCC row versions.

```sql theme={null}
PRAGMA mvcc_gc_threshold = 1000;
```

## Change Data Capture

<Info>
  **Turso Extension**: Change Data Capture (CDC) is a Turso-specific feature that tracks all data changes for replication, auditing, and reactive applications.
</Info>

### capture\_data\_changes\_conn

Enables CDC for the current connection. Changes are captured to a designated table.

```sql theme={null}
PRAGMA capture_data_changes_conn('mode');
PRAGMA capture_data_changes_conn('mode,table_name');
```

The legacy name `unstable_capture_data_changes_conn` is still accepted for backwards compatibility.

| Parameter   | Description                                                  |
| ----------- | ------------------------------------------------------------ |
| mode        | Capture mode: `off`, `id`, `before`, `after`, `full`         |
| table\_name | Custom table name for storing changes (default: `turso_cdc`) |

#### Capture Modes

| Mode     | Description                                               |
| -------- | --------------------------------------------------------- |
| `off`    | Disable CDC for this connection                           |
| `id`     | Capture only the primary key/rowid of changed rows        |
| `before` | Capture row state before changes (for updates/deletes)    |
| `after`  | Capture row state after changes (for inserts/updates)     |
| `full`   | Capture both before and after states, plus update details |

#### CDC Table Structure

The CDC table contains the following columns:

| Column       | Type    | Description                                      |
| ------------ | ------- | ------------------------------------------------ |
| change\_id   | INTEGER | Auto-incrementing unique identifier              |
| change\_time | INTEGER | Unix epoch timestamp                             |
| change\_type | INTEGER | 1 (INSERT), 0 (UPDATE), -1 (DELETE)              |
| table\_name  | TEXT    | Name of the changed table                        |
| id           | varies  | Primary key/rowid of the changed row             |
| before       | BLOB    | Row data before the change (modes: before, full) |
| after        | BLOB    | Row data after the change (modes: after, full)   |
| updates      | BLOB    | Updated column details (mode: full)              |

#### CDC Examples

```sql theme={null}
-- Enable full CDC
PRAGMA capture_data_changes_conn('full');

-- Make changes
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO users VALUES (1, 'Alice');
UPDATE users SET name = 'Alicia' WHERE id = 1;
DELETE FROM users WHERE id = 1;

-- Query the changes
SELECT change_type, table_name, id FROM turso_cdc;
-- 1  | users | 1    (INSERT)
-- 0  | users | 1    (UPDATE)
-- -1 | users | 1    (DELETE)

-- Use a custom table name
PRAGMA capture_data_changes_conn('full,audit_log');

-- Disable CDC
PRAGMA capture_data_changes_conn('off');
```

CDC respects transaction boundaries. Changes are only recorded when a transaction commits. If a transaction rolls back, no CDC entries are created.

## Encryption

<Info>
  **Turso Extension**: At-rest encryption is a Turso-specific feature. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features).
</Info>

### cipher

Sets the encryption cipher for the database.

```sql theme={null}
PRAGMA cipher = 'aegis256';
```

Supported ciphers:

| Cipher       | Key Size | Description                       |
| ------------ | -------- | --------------------------------- |
| `aes128gcm`  | 16 bytes | AES-128 in Galois/Counter Mode    |
| `aes256gcm`  | 32 bytes | AES-256 in Galois/Counter Mode    |
| `aegis128l`  | 16 bytes | AEGIS-128L                        |
| `aegis256`   | 32 bytes | AEGIS-256 (recommended)           |
| `aegis128x2` | 16 bytes | AEGIS-128 with 2x parallelization |
| `aegis128x4` | 16 bytes | AEGIS-128 with 4x parallelization |
| `aegis256x2` | 32 bytes | AEGIS-256 with 2x parallelization |
| `aegis256x4` | 32 bytes | AEGIS-256 with 4x parallelization |

### hexkey

Sets the encryption key as a hexadecimal string.

```sql theme={null}
PRAGMA hexkey = '2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d';
```

#### Encryption Example

```sql theme={null}
-- Set cipher and key before creating tables
PRAGMA cipher = 'aegis256';
PRAGMA hexkey = '2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d';

CREATE TABLE secrets (id INTEGER PRIMARY KEY, data TEXT);
INSERT INTO secrets VALUES (1, 'sensitive data');
```

Alternatively, specify encryption parameters in the database URI:

```
file:database.db?cipher=aegis256&hexkey=2d7a30...
```

To open an existing encrypted database, the cipher and key must be provided as URI parameters.

## Custom Types

<Info>
  **Turso Extension**: Custom types are a Turso-specific feature.
</Info>

### list\_types

Lists all available types (built-in and custom) with their metadata.

```sql theme={null}
PRAGMA list_types;
-- type    | parent | encode | decode | default | operators
-- INTEGER |        |        |        |         |
-- REAL    |        |        |        |         |
-- TEXT    |        |        |        |         |
-- BLOB    |        |        |        |         |
-- ANY     |        |        |        |         |
```

See [CREATE TYPE](/sql-reference/statements/create-type) for creating custom types.

## See Also

* [Transactions](/sql-reference/statements/transactions) for transaction control
* [CREATE TYPE](/sql-reference/statements/create-type) for custom type definitions
* [Compatibility](/sql-reference/compatibility) for the full list of supported PRAGMAs


# ALTER TABLE
Source: https://docs.turso.tech/sql-reference/statements/alter-table

Modify the structure of an existing table by renaming, adding, or dropping columns

# ALTER TABLE

Modify the structure of an existing table.

## Syntax

```sql theme={null}
ALTER TABLE [schema-name.]table-name RENAME TO new-table-name;
ALTER TABLE [schema-name.]table-name RENAME COLUMN old-column-name TO new-column-name;
ALTER TABLE [schema-name.]table-name ADD COLUMN column-def;
ALTER TABLE [schema-name.]table-name DROP COLUMN column-name;
```

## Description

`ALTER TABLE` modifies the structure of an existing table without requiring you to recreate the table and copy its data. Turso supports four forms of ALTER TABLE: renaming the table, renaming a column, adding a new column, and dropping an existing column.

### Parameters

| Parameter     | Description                                                                                      |
| ------------- | ------------------------------------------------------------------------------------------------ |
| `schema-name` | The name of an attached database containing the table. Defaults to the main database if omitted. |
| `table-name`  | The name of the table to alter.                                                                  |

## RENAME TO

Rename an existing table.

```sql theme={null}
ALTER TABLE old_name RENAME TO new_name;
```

Renaming a table updates all references to the table in:

* Triggers that reference the table
* Indexes on the table
* Foreign key constraints (both as parent and child table)
* CHECK constraints
* Views that reference the table

<Info>
  The new table name must not collide with an existing table, view, or index name in the same database.
</Info>

### Example

```sql theme={null}
CREATE TABLE customers (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
);

ALTER TABLE customers RENAME TO clients;

-- The table is now accessible as 'clients'
SELECT * FROM clients;
```

## RENAME COLUMN

Rename an existing column in a table.

```sql theme={null}
ALTER TABLE table-name RENAME COLUMN old-column-name TO new-column-name;
```

Renaming a column updates all references to the column in:

* Indexes that reference the column (including expression indexes)
* Triggers that reference the column
* CHECK constraints that reference the column
* Foreign key constraint definitions

### Restrictions

A column rename fails if:

* The column does not exist in the table.
* The new column name conflicts with an existing column in the same table.
* A trigger on the table uses a qualified reference (e.g., `table_name.column_name`) to the column in its body.
* A trigger's WHEN clause references the column.

### Example

```sql theme={null}
CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    product_name TEXT NOT NULL,
    price REAL
);

CREATE INDEX idx_name ON products(product_name);

ALTER TABLE products RENAME COLUMN product_name TO name;

-- The index is automatically updated
-- Queries now use the new column name
SELECT name, price FROM products;
```

## ADD COLUMN

Add a new column to an existing table. The new column is always appended as the last column.

```sql theme={null}
ALTER TABLE table-name ADD COLUMN column-name type [constraints];
```

### Restrictions

A new column added with ADD COLUMN must satisfy these requirements:

| Requirement                                                                         | Reason                                                                                                                                |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| No `PRIMARY KEY` constraint                                                         | The primary key of a table cannot be changed after creation                                                                           |
| No `UNIQUE` constraint                                                              | Adding a UNIQUE column would require scanning all existing rows                                                                       |
| `NOT NULL` requires a non-NULL default                                              | Existing rows must have a valid value for the new column                                                                              |
| Default must be a constant expression                                               | Only literal values, signed literals, and parenthesized constant expressions are allowed. Functions like `random()` are not permitted |
| `CURRENT_TIME`, `CURRENT_DATE`, `CURRENT_TIMESTAMP` defaults require an empty table | These non-deterministic values cannot be retroactively applied to existing rows                                                       |
| CHECK constraints require an empty table                                            | The constraint cannot be validated against existing rows                                                                              |
| No duplicate column name                                                            | The column name must not already exist in the table                                                                                   |
| No `GENERATED ALWAYS AS`                                                            | Generated columns cannot be added via ALTER TABLE                                                                                     |
| STRICT tables require an explicit type                                              | The column type must be a valid STRICT type                                                                                           |

### Supported Column Constraints

The following constraints are supported on added columns:

* `NOT NULL` (with a non-NULL default)
* `DEFAULT expression`
* `CHECK (expression)`
* `REFERENCES` (foreign key)
* `COLLATE`

### Examples

#### Add a simple column

```sql theme={null}
ALTER TABLE users ADD COLUMN phone TEXT;
```

#### Add a column with a default value

```sql theme={null}
ALTER TABLE orders ADD COLUMN priority INTEGER DEFAULT 0;
```

#### Add a NOT NULL column with a default

```sql theme={null}
ALTER TABLE products ADD COLUMN in_stock INTEGER NOT NULL DEFAULT 1;
```

#### Add a column with a foreign key

```sql theme={null}
ALTER TABLE orders ADD COLUMN warehouse_id INTEGER REFERENCES warehouses(id);
```

#### Add a column on a STRICT table

```sql theme={null}
CREATE TABLE metrics (
    id INTEGER PRIMARY KEY,
    value REAL
) STRICT;

-- Must specify a valid STRICT type
ALTER TABLE metrics ADD COLUMN label TEXT DEFAULT 'unknown';
```

## DROP COLUMN

Remove an existing column from a table.

```sql theme={null}
ALTER TABLE table-name DROP COLUMN column-name;
```

### Restrictions

A column cannot be dropped if any of the following conditions are true:

| Condition                                                                               | Error                                            |
| --------------------------------------------------------------------------------------- | ------------------------------------------------ |
| The column is a PRIMARY KEY or part of one                                              | Cannot drop PRIMARY KEY column                   |
| The column has a UNIQUE constraint                                                      | Cannot drop UNIQUE column                        |
| The column is referenced by an index                                                    | Cannot drop indexed column                       |
| The column is referenced in an expression index                                         | Cannot drop column used in expression index      |
| The column is named in a partial index WHERE clause                                     | Cannot drop column used in partial index         |
| The column is referenced in a CHECK constraint belonging to another column or the table | Cannot drop column used in CHECK constraint      |
| The column is referenced by a foreign key in another table                              | Cannot drop column used in foreign key           |
| The table has only one column                                                           | Cannot drop the last remaining column            |
| The column appears in a trigger or view                                                 | Cannot drop column referenced by trigger or view |

### Examples

#### Drop a column

```sql theme={null}
ALTER TABLE users DROP COLUMN phone;
```

#### Verify columns after dropping

```sql theme={null}
CREATE TABLE example (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    old_field TEXT,
    active INTEGER DEFAULT 1
);

ALTER TABLE example DROP COLUMN old_field;

PRAGMA table_info(example);
-- cid | name   | type    | notnull | dflt_value | pk
-- 0   | id     | INTEGER | 0       | NULL       | 1
-- 1   | name   | TEXT    | 1       | NULL       | 0
-- 2   | active | INTEGER | 0       | 1          | 0
```

## Examples

### Complete Schema Evolution

```sql theme={null}
-- Create initial table
CREATE TABLE articles (
    id INTEGER PRIMARY KEY,
    title TEXT NOT NULL,
    body TEXT
);

-- Add an author column with a foreign key
ALTER TABLE articles ADD COLUMN author_id INTEGER REFERENCES users(id);

-- Add a publication status
ALTER TABLE articles ADD COLUMN status TEXT NOT NULL DEFAULT 'draft';

-- Rename a column for clarity
ALTER TABLE articles RENAME COLUMN body TO content;

-- Add a nullable timestamp column
ALTER TABLE articles ADD COLUMN updated_at TEXT;

-- Rename the table
ALTER TABLE articles RENAME TO posts;

-- Final schema
PRAGMA table_info(posts);
-- cid | name       | type    | notnull | dflt_value        | pk
-- 0   | id         | INTEGER | 0       | NULL              | 1
-- 1   | title      | TEXT    | 1       | NULL              | 0
-- 2   | content    | TEXT    | 0       | NULL              | 0
-- 3   | author_id  | INTEGER | 0       | NULL              | 0
-- 4   | status     | TEXT    | 1       | 'draft'           | 0
-- 5   | updated_at | TEXT    | 0       | NULL              | 0
```

## See Also

* [CREATE TABLE](/sql-reference/statements/create-table) for creating tables
* [DROP TABLE](/sql-reference/statements/drop-table) for removing tables
* [CREATE INDEX](/sql-reference/statements/create-index) for indexing columns
* [Data Types](/sql-reference/data-types) for column types and STRICT table types


# ANALYZE
Source: https://docs.turso.tech/sql-reference/statements/analyze

Collect statistics about indexes to help the query optimizer

# ANALYZE

The ANALYZE statement collects statistics about the contents of tables and indexes. The query optimizer uses these statistics to choose better query plans, particularly when deciding which index to use and how to order joins.

## Syntax

```sql theme={null}
ANALYZE;
ANALYZE schema-name;
ANALYZE [schema-name.]table-or-index-name;
```

## Description

The ANALYZE statement gathers statistics about the distribution of values in indexes and stores the results in the `sqlite_stat1` table (and optionally `sqlite_stat4`). The query optimizer reads these statistics to make better decisions about:

* Which index to use for a query
* The order in which to process tables in a join
* Whether to use an index or a full table scan

### Forms

| Form                  | Description                                              |
| --------------------- | -------------------------------------------------------- |
| `ANALYZE`             | Analyze all tables and indexes in all attached databases |
| `ANALYZE schema-name` | Analyze all tables and indexes in the named database     |
| `ANALYZE table-name`  | Analyze all indexes on the named table                   |
| `ANALYZE index-name`  | Analyze the named index                                  |

## Examples

### Analyze All Tables

```sql theme={null}
-- Collect statistics for the entire database
ANALYZE;
```

### Analyze a Specific Table

```sql theme={null}
CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    customer_id INTEGER,
    product_id INTEGER,
    amount REAL
);

CREATE INDEX idx_customer ON orders(customer_id);
CREATE INDEX idx_product ON orders(product_id);

-- Collect statistics for the orders table and its indexes
ANALYZE orders;
```

### Viewing Statistics

The statistics are stored in the `sqlite_stat1` table:

```sql theme={null}
SELECT * FROM sqlite_stat1;
-- tbl     | idx           | stat
-- orders  | idx_customer  | 1000 50
-- orders  | idx_product   | 1000 10
```

The `stat` column contains space-separated integers. The first integer is the total number of rows in the table. Subsequent integers estimate the average number of rows that share the same value for the leftmost N columns of the index.

## See Also

* [CREATE INDEX](/sql-reference/statements/create-index) for creating indexes
* [EXPLAIN](/sql-reference/statements/explain) for examining query plans


# ATTACH DATABASE
Source: https://docs.turso.tech/sql-reference/statements/attach-database

Attach an additional database file to the current connection

# ATTACH DATABASE

The ATTACH DATABASE statement adds another database file to the current connection under a schema name. Once attached, tables in the external database can be queried alongside tables in the main database.

## Syntax

```sql theme={null}
ATTACH [DATABASE] filename AS schema-name;
```

## Description

ATTACH opens the database file at `filename` and makes its tables accessible through the given `schema-name`. Tables in the attached database are referenced as `schema-name.table-name`.

### Clauses

| Clause        | Description                                                                                                                                    |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `DATABASE`    | Optional keyword. Has no effect on behavior.                                                                                                   |
| `filename`    | A string expression that evaluates to the path of the database file to attach. Use `':memory:'` to attach an in-memory database.               |
| `schema-name` | The name used to qualify table references from the attached database. This name must be unique among all attached databases on the connection. |

Every connection has a built-in schema named `main` for its primary database. The names `main` and `temp` are reserved and cannot be used as schema names for attached databases.

<Info>
  This feature is experimental and must be [enabled before use](/sql-reference/experimental-features). Attached databases support both read and write operations (INSERT, UPDATE, DELETE, CREATE TABLE, etc.). The attached database's journal mode must match the main database's journal mode (e.g., both WAL or both MVCC).
</Info>

## Examples

### Attach and Query an External Database

```sql theme={null}
-- Attach another database file
ATTACH DATABASE 'archive.db' AS archive;

-- Query a table in the attached database
SELECT * FROM archive.events WHERE event_date > '2025-01-01';
```

### Cross-Database Queries

```sql theme={null}
ATTACH 'customers.db' AS customers_db;

-- Join tables across the main and attached databases
SELECT o.id, o.total, c.name
FROM main.orders o
JOIN customers_db.customers c ON o.customer_id = c.id;
```

### Attach an In-Memory Database

```sql theme={null}
ATTACH ':memory:' AS scratch;
```

## See Also

* [DETACH DATABASE](/sql-reference/statements/detach-database) for removing an attached database
* [SELECT](/sql-reference/statements/select) for querying tables across databases


# CREATE DOMAIN
Source: https://docs.turso.tech/sql-reference/statements/create-domain

Define a named constraint wrapper over a base type for use in STRICT tables

# CREATE DOMAIN

<Info>
  **Turso Extension**: CREATE DOMAIN is a Turso-specific statement not available in standard SQLite. Domains require STRICT tables and the custom types experimental feature. This feature must be [enabled before use](/sql-reference/experimental-features).
</Info>

The CREATE DOMAIN statement defines a named type alias with optional constraints. Unlike [CREATE TYPE](/sql-reference/statements/create-type), a domain does not specify custom ENCODE/DECODE logic or OPERATORs. Instead, a domain adds NOT NULL and CHECK constraints on top of a base type, and values are stored using the base type's native storage format.

## Syntax

```sql theme={null}
CREATE DOMAIN [IF NOT EXISTS] domain-name AS base-type
    [DEFAULT default-expr]
    [NOT NULL]
    [[CONSTRAINT constraint-name] CHECK (expr)]
    ...;
```

## Description

A domain wraps a base type with validation constraints. When a value is written to a column of a domain type, the CHECK constraints and NOT NULL restriction (if any) are verified. If validation passes, the value is stored using the base type's native format. When a value is read, it is returned unchanged.

Domains are transparent to the query engine for operations like ORDER BY, indexing, arithmetic, and aggregation. A domain column behaves exactly like a column of its base type, with the addition of input validation.

Domains work only with STRICT tables. Using a domain name in a non-STRICT table has no effect.

## Clauses

### IF NOT EXISTS

Suppresses the error that would occur if a domain with the same name already exists. The existing domain is left unchanged.

```sql theme={null}
CREATE DOMAIN IF NOT EXISTS positive_int AS integer CHECK (value > 0);
```

### AS base-type

Specifies the underlying type. The base type can be a primitive type (`integer`, `real`, `text`, `blob`) or another domain, allowing domains to be layered.

```sql theme={null}
CREATE DOMAIN percentage AS integer
    CHECK (value >= 0)
    CHECK (value <= 100);
```

### DEFAULT

Sets a default value for columns of this domain type. A column-level DEFAULT overrides the domain-level DEFAULT.

```sql theme={null}
CREATE DOMAIN status AS text DEFAULT 'active';

CREATE TABLE accounts (
    id INTEGER PRIMARY KEY,
    state status
) STRICT;

INSERT INTO accounts(id) VALUES (1);
SELECT state FROM accounts;
-- active
```

### NOT NULL

Adds a NOT NULL constraint to the domain. Any attempt to insert or update a NULL value into a column of this domain type will fail, even if the column definition does not specify NOT NULL.

```sql theme={null}
CREATE DOMAIN required_text AS text NOT NULL;

CREATE TABLE contacts (
    id INTEGER PRIMARY KEY,
    name required_text
) STRICT;

INSERT INTO contacts VALUES (1, 'Alice');
-- OK

INSERT INTO contacts VALUES (2, NULL);
-- Error: domain required_text does not allow null values
```

A column-level NULL declaration does not override the domain's NOT NULL constraint.

### CHECK

Adds a validation constraint. The expression can reference `value` to refer to the input value being checked. Multiple CHECK constraints can be specified and all must pass.

```sql theme={null}
CREATE DOMAIN positive_int AS integer CHECK (value > 0);

CREATE TABLE measurements (
    id INTEGER PRIMARY KEY,
    reading positive_int
) STRICT;

INSERT INTO measurements VALUES (1, 42);
-- OK

INSERT INTO measurements VALUES (2, -5);
-- Error: domain positive_int constraint violation
```

### CONSTRAINT name CHECK

CHECK constraints can optionally be given a name for documentation purposes.

```sql theme={null}
CREATE DOMAIN valid_score AS integer
    CONSTRAINT non_negative CHECK (value >= 0)
    CONSTRAINT max_hundred CHECK (value <= 100);
```

## Domain Chaining

A domain can use another domain as its base type. When domains are chained, all constraints in the chain are enforced from child to ancestor.

```sql theme={null}
CREATE DOMAIN base_amount AS integer CHECK (value > 0);
CREATE DOMAIN small_amount AS base_amount CHECK (value < 1000);

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    quantity small_amount
) STRICT;

INSERT INTO orders VALUES (1, 50);
-- OK: passes both value > 0 and value < 1000

INSERT INTO orders VALUES (2, -1);
-- Error: fails base_amount's CHECK (value > 0)

INSERT INTO orders VALUES (3, 5000);
-- Error: fails small_amount's CHECK (value < 1000)
```

Three or more levels of chaining are supported:

```sql theme={null}
CREATE DOMAIN text_val AS text;
CREATE DOMAIN nonempty AS text_val CHECK (length(value) > 0);
CREATE DOMAIN short_text AS nonempty CHECK (length(value) < 50);

CREATE TABLE labels (
    id INTEGER PRIMARY KEY,
    name short_text
) STRICT;

INSERT INTO labels VALUES (1, 'OK');
-- OK

INSERT INTO labels VALUES (2, '');
-- Error: fails nonempty's CHECK (length(value) > 0)
```

## UPDATE Enforcement

Domain constraints are enforced on UPDATE as well as INSERT.

```sql theme={null}
CREATE DOMAIN positive_int AS integer CHECK (value > 0);

CREATE TABLE items (
    id INTEGER PRIMARY KEY,
    stock positive_int
) STRICT;

INSERT INTO items VALUES (1, 10);
UPDATE items SET stock = 20 WHERE id = 1;
SELECT stock FROM items;
-- 20

UPDATE items SET stock = -1 WHERE id = 1;
-- Error: domain positive_int constraint violation
```

## CAST Support

CAST applies the domain's validation constraints:

```sql theme={null}
CREATE DOMAIN positive_int AS integer CHECK (value > 0);

SELECT CAST(42 AS positive_int);
-- 42

SELECT CAST(-1 AS positive_int);
-- Error: domain positive_int constraint violation
```

Casting NULL to a NOT NULL domain is rejected:

```sql theme={null}
CREATE DOMAIN notnull_int AS integer NOT NULL;

SELECT CAST(NULL AS notnull_int);
-- Error: domain notnull_int does not allow null values
```

## CHECK Constraints with Table-Level Checks

Domain CHECK constraints and table-level CHECK constraints both apply. The domain constraint is checked during encoding and the table CHECK is checked separately.

```sql theme={null}
CREATE DOMAIN positive_int AS integer CHECK (value > 0);

CREATE TABLE bounded (
    id INTEGER PRIMARY KEY,
    val positive_int CHECK (val < 100)
) STRICT;

INSERT INTO bounded VALUES (1, 50);
-- OK: passes domain CHECK (> 0) and table CHECK (< 100)

INSERT INTO bounded VALUES (2, -1);
-- Error: fails domain CHECK

INSERT INTO bounded VALUES (3, 200);
-- Error: fails table CHECK
```

## Operations on Domain Columns

Domain columns support the same operations as their base type: arithmetic, comparisons, ordering, and aggregation.

```sql theme={null}
CREATE DOMAIN myint AS integer;

CREATE TABLE data (
    id INTEGER PRIMARY KEY,
    a myint,
    b myint
) STRICT;

INSERT INTO data VALUES (1, 10, 3);
SELECT a + b, a - b, a * b FROM data;
-- 13|7|30
```

```sql theme={null}
CREATE DOMAIN myint AS integer;

CREATE TABLE scores (
    id INTEGER PRIMARY KEY,
    val myint
) STRICT;

INSERT INTO scores VALUES (1, 30);
INSERT INTO scores VALUES (2, 10);
INSERT INTO scores VALUES (3, 20);
SELECT val FROM scores ORDER BY val;
-- 10
-- 20
-- 30
```

## Dropping Domains

Domains are dropped with [DROP DOMAIN](/sql-reference/statements/drop-domain), not DROP TYPE. Attempting to use DROP TYPE on a domain (or DROP DOMAIN on a type) results in an error.

A domain cannot be dropped while any table column or another domain references it.

```sql theme={null}
CREATE DOMAIN my_domain AS integer;
CREATE TABLE t(x my_domain) STRICT;

DROP DOMAIN my_domain;
-- Error: type 'my_domain' is in use

DROP TABLE t;
DROP DOMAIN my_domain;
-- OK
```

## Restrictions

* **STRICT tables only**: Using a domain type on a non-STRICT table is rejected at CREATE TABLE and ALTER TABLE ADD COLUMN time. Non-STRICT tables allow arbitrary type names as affinity hints, but domain constraints (CHECK, NOT NULL, DEFAULT) are only enforced on STRICT tables, so permitting them would be misleading.
* **Cannot drop while in use**: A domain cannot be dropped while any table column or another domain references it.

<Note>
  If a non-STRICT table was created with a type name that did not exist at the time (e.g., `CREATE TABLE t(x mydom)`), and a domain with that name is created afterwards (`CREATE DOMAIN mydom AS ...`), the domain constraints will **not** be retroactively enforced on the existing table. This is not a bug: non-STRICT tables treat all type names as plain affinity hints regardless of whether a matching domain exists. STRICT tables prevent this scenario because they reject unknown type names at CREATE TABLE time.
</Note>

* **No UNIQUE, PRIMARY KEY, or REFERENCES**: These constraints are not supported in domain definitions. Use table-level constraints instead.
* **No duplicate clauses**: Multiple DEFAULT or NOT NULL clauses in the same definition are rejected. Conflicting NULL and NOT NULL clauses are also rejected.

## See Also

* [DROP DOMAIN](/sql-reference/statements/drop-domain) for removing domains
* [CREATE TYPE](/sql-reference/statements/create-type) for defining custom types with ENCODE/DECODE logic
* [Data Types](/sql-reference/data-types) for an overview of the type system
* [CREATE TABLE](/sql-reference/statements/create-table) for STRICT table definitions


# CREATE INDEX
Source: https://docs.turso.tech/sql-reference/statements/create-index

Create an index on one or more columns or expressions to accelerate queries

# CREATE INDEX

Create an index on a table to improve query performance for lookups, joins, and ordering.

## Syntax

```sql theme={null}
CREATE [UNIQUE] INDEX [IF NOT EXISTS] [schema-name.]index-name
    ON table-name (column-or-expr [COLLATE collation-name] [ASC|DESC], ...)
    [WHERE filter-expression];
```

## Description

`CREATE INDEX` builds an index on one or more columns or expressions of a table. Turso uses B-tree indexes in the same format as SQLite. The query planner automatically uses indexes when they can speed up a query -- you do not need to reference an index explicitly in your SQL statements.

### Parameters

| Parameter                 | Description                                                                                                                     |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `UNIQUE`                  | Enforces a uniqueness constraint. Turso rejects any INSERT or UPDATE that would create duplicate values in the indexed columns. |
| `IF NOT EXISTS`           | Prevents an error if an index with the same name already exists. The statement is a no-op when the index is present.            |
| `schema-name`             | The name of the attached database containing the table. Defaults to the main database if omitted.                               |
| `index-name`              | A unique name for the index within the database.                                                                                |
| `table-name`              | The table to index.                                                                                                             |
| `column-or-expr`          | A column name or an expression to include in the index. Multiple entries are separated by commas.                               |
| `COLLATE collation-name`  | An optional collation sequence applied to the indexed column (e.g. `NOCASE`).                                                   |
| `ASC` / `DESC`            | Sort direction for the indexed column. Default is `ASC`.                                                                        |
| `WHERE filter-expression` | An optional filter that creates a partial index. Only rows matching the expression are included in the index.                   |

## Column Indexes

The most common form indexes one or more columns by name.

```sql theme={null}
-- Single-column index
CREATE INDEX idx_users_email ON users (email);

-- Multi-column (composite) index
CREATE INDEX idx_orders_customer_date ON orders (customer_id, order_date DESC);
```

A composite index is useful when queries filter or sort by multiple columns. The order of columns matters -- an index on `(a, b)` can accelerate queries filtering on `a` alone, but not queries filtering only on `b`.

## UNIQUE Indexes

A `UNIQUE` index enforces that no two rows contain the same combination of values in the indexed columns. NULL values are considered distinct from each other, so a `UNIQUE` index permits multiple rows with NULL in the indexed columns.

```sql theme={null}
CREATE UNIQUE INDEX idx_users_email_unique ON users (email);

-- This succeeds:
INSERT INTO users (email) VALUES ('alice@example.com');

-- This fails with a UNIQUE constraint violation:
INSERT INTO users (email) VALUES ('alice@example.com');
```

## Partial Indexes

A partial index includes only the rows that satisfy the `WHERE` clause. Partial indexes are smaller than full indexes and are more efficient for queries that always include the same filter condition.

```sql theme={null}
-- Index only active orders
CREATE INDEX idx_active_orders ON orders (customer_id)
    WHERE status = 'active';

-- The query planner uses this index when the WHERE clause matches
SELECT * FROM orders WHERE status = 'active' AND customer_id = 42;
```

The `WHERE` clause of a partial index can reference any column of the table and may use operators, literal values, and built-in functions. Subqueries are not allowed.

## Expression Indexes

An expression index stores the result of an expression rather than a raw column value. Use expression indexes when queries frequently filter or sort by a computed value.

```sql theme={null}
-- Index on lowercase email for case-insensitive lookups
CREATE INDEX idx_users_email_lower ON users (lower(email));

-- The query planner can use this index for:
SELECT * FROM users WHERE lower(email) = 'alice@example.com';
```

```sql theme={null}
-- Index on an arithmetic expression
CREATE INDEX idx_items_total ON items (quantity * unit_price);
```

Each expression in the index must be a deterministic expression that references only columns of the indexed table. Aggregate functions and subqueries are not allowed.

## Custom Index Methods

<Info>
  **Turso Extension**: Custom index methods extend indexing beyond B-trees. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features).
</Info>

Turso supports a `USING` clause to specify an alternative index method.

```sql theme={null}
CREATE INDEX index-name ON table-name USING method-name (columns...);
```

### Full-Text Search with `USING fts`

The `fts` index method creates a full-text search index powered by Tantivy. FTS indexes support tokenizer configuration through the `WITH` clause.

```sql theme={null}
-- Basic FTS index
CREATE INDEX idx_articles_search ON articles USING fts (title, body);

-- FTS index with custom tokenizers
CREATE INDEX idx_articles_search ON articles USING fts (
    title WITH tokenizer=simple,
    body WITH tokenizer=ngram
);
```

Once an FTS index exists, use the `search()` function to query it:

```sql theme={null}
SELECT * FROM articles WHERE search(articles, 'database performance');
```

## Examples

### Index for a Common Lookup Pattern

```sql theme={null}
CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    category TEXT,
    price REAL,
    in_stock INTEGER DEFAULT 1
);

-- Speed up lookups by category
CREATE INDEX idx_products_category ON products (category);

-- Speed up price range queries within a category
CREATE INDEX idx_products_cat_price ON products (category, price);
```

### Unique Index to Enforce a Business Rule

```sql theme={null}
CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    email TEXT NOT NULL,
    department TEXT
);

CREATE UNIQUE INDEX idx_employees_email ON employees (email);
```

### Partial Index for a Status Filter

```sql theme={null}
CREATE TABLE tasks (
    id INTEGER PRIMARY KEY,
    assignee TEXT,
    status TEXT DEFAULT 'pending',
    due_date TEXT
);

-- Only index pending tasks -- completed tasks are rarely queried
CREATE INDEX idx_pending_tasks ON tasks (assignee, due_date)
    WHERE status = 'pending';
```

## See Also

* [DROP INDEX](/sql-reference/statements/drop-index) for removing indexes
* [REINDEX](/sql-reference/statements/reindex) for rebuilding indexes
* [CREATE TABLE](/sql-reference/statements/create-table) for inline UNIQUE and PRIMARY KEY constraints
* [EXPLAIN](/sql-reference/statements/explain) for verifying index usage in query plans


# CREATE MATERIALIZED VIEW
Source: https://docs.turso.tech/sql-reference/statements/create-materialized-view

Create a materialized view that stores precomputed results and updates automatically

# CREATE MATERIALIZED VIEW

<Info>
  **Turso Extension**: Materialized views are a Turso-specific feature not available in SQLite. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features).
</Info>

A materialized view stores precomputed query results on disk and automatically keeps them up to date using Incremental View Maintenance (IVM). Unlike standard views, reading from a materialized view does not re-execute the underlying SELECT.

## Syntax

```sql theme={null}
CREATE MATERIALIZED VIEW [IF NOT EXISTS] view-name AS select-statement;
```

### Parameters

| Parameter          | Description                                                                                                                    |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `IF NOT EXISTS`    | Prevents an error if a materialized view with the same name already exists. The statement is a no-op when the view is present. |
| `view-name`        | A unique name for the materialized view within the database.                                                                   |
| `select-statement` | The SELECT query that defines the materialized view's contents.                                                                |

## How Incremental View Maintenance Works

When you modify data in a table that underlies a materialized view, Turso incrementally updates the materialized view within the same transaction. There is no manual `REFRESH` step. The materialized view is always consistent with the base tables.

* Changes to the materialized view are committed or rolled back together with the changes to the base tables.
* Aggregations like `COUNT`, `SUM`, and `AVG` are maintained efficiently — Turso does not recompute the entire result set on each change.

## Current Limitations

* Not all SQL functions are supported in materialized view definitions.
* A materialized view cannot reference another view or materialized view.
* The `TEMPORARY` modifier is not supported for materialized views.

## Examples

### Precomputed Aggregations

```sql theme={null}
CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    customer_id INTEGER,
    amount REAL,
    created_at TEXT
);

-- Precompute per-customer totals
CREATE MATERIALIZED VIEW customer_totals AS
SELECT
    customer_id,
    COUNT(*) AS order_count,
    SUM(amount) AS total_spent
FROM orders
GROUP BY customer_id;

-- Fast read — no aggregation at query time
SELECT * FROM customer_totals WHERE total_spent > 1000;
```

### Automatic Updates

```sql theme={null}
-- When new orders are inserted, customer_totals updates automatically
INSERT INTO orders VALUES (1, 100, 59.99, '2025-01-15');
INSERT INTO orders VALUES (2, 100, 24.50, '2025-01-16');

-- Reflects both inserts without a manual refresh
SELECT * FROM customer_totals WHERE customer_id = 100;
-- customer_id: 100, order_count: 2, total_spent: 84.49
```

### Filtered Summary

```sql theme={null}
CREATE MATERIALIZED VIEW recent_activity AS
SELECT
    customer_id,
    COUNT(*) AS recent_orders
FROM orders
WHERE created_at >= '2025-01-01'
GROUP BY customer_id;
```

## See Also

* [CREATE VIEW](/sql-reference/statements/create-view) for standard (non-materialized) views
* [DROP VIEW](/sql-reference/statements/drop-view) for removing materialized views
* [SELECT](/sql-reference/statements/select) for the query syntax used in view definitions


# CREATE SEQUENCE
Source: https://docs.turso.tech/sql-reference/statements/create-sequence

Define a named sequence generator for producing auto-incrementing numeric values

# CREATE SEQUENCE

<Info>
  **Turso Extension**: CREATE SEQUENCE is a Turso-specific statement not available in standard SQLite.
</Info>

The CREATE SEQUENCE statement defines a named sequence object that generates a series of numeric values. Sequences are independent of any table and can be used across multiple tables and queries via the `nextval()`, `currval()`, and `setval()` functions.

## Syntax

```sql theme={null}
CREATE SEQUENCE [IF NOT EXISTS] [schema-name.]sequence-name
    [START [WITH] value]
    [INCREMENT [BY] value]
    [MINVALUE value]
    [MAXVALUE value]
    [CYCLE | NO CYCLE];
```

## Description

A sequence generates a series of integer values according to its configuration. Each call to `nextval()` produces the next value in the series. Sequences are persisted in the database schema and survive restarts.

Unlike `AUTOINCREMENT` on an `INTEGER PRIMARY KEY`, sequences are standalone objects. A single sequence can supply values to multiple tables, or multiple sequences can feed into different columns of the same table.

### Parameters

| Parameter        | Description                                                                                                               | Default                                                                        |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `IF NOT EXISTS`  | Prevents an error if a sequence with the same name already exists. The statement is a no-op when the sequence is present. |                                                                                |
| `schema-name`    | The name of an attached database. Defaults to the main database if omitted.                                               |                                                                                |
| `sequence-name`  | The name of the sequence to create.                                                                                       |                                                                                |
| `START [WITH]`   | The first value returned by `nextval()`.                                                                                  | `MINVALUE` for ascending sequences, `MAXVALUE` for descending sequences.       |
| `INCREMENT [BY]` | The step between consecutive values. Use a negative value for descending sequences.                                       | `1`                                                                            |
| `MINVALUE`       | The lower bound of the sequence.                                                                                          | `1` for ascending sequences, minimum 64-bit integer for descending sequences.  |
| `MAXVALUE`       | The upper bound of the sequence.                                                                                          | Maximum 64-bit integer for ascending sequences, `-1` for descending sequences. |
| `CYCLE`          | When the sequence reaches its boundary, wrap around to the opposite end instead of raising an error.                      | `NO CYCLE`                                                                     |

### Validation

The following rules are enforced at creation time:

* `INCREMENT BY` must not be zero.
* `MINVALUE` must be less than `MAXVALUE`.
* For ascending sequences (`INCREMENT > 0`): `START` must be greater than or equal to `MINVALUE`.
* For descending sequences (`INCREMENT < 0`): `START` must be less than or equal to `MAXVALUE`.

## Sequence Functions

### nextval()

Returns the next value from the sequence and advances it.

```sql theme={null}
SELECT nextval('counter');
-- 1
SELECT nextval('counter');
-- 2
```

On the first call, `nextval()` returns the `START` value. Each subsequent call increments by `INCREMENT BY`.

Sequence persistence under WAL mode is transactional: the sequence backing-table update is committed or rolled back with the transaction that called `nextval()`. Under MVCC mode (`journal_mode=experimental_mvcc`) inside `BEGIN CONCURRENT`, the backing-table write is autonomous and survives an outer `ROLLBACK` (postgres-like). For BEGIN / BEGIN IMMEDIATE / autocommit, the inner write is bundled with the outer and rolls back with it (SQLite-like).

Turso does not guarantee gap-free sequences, and it also does not guarantee PostgreSQL-style permanent burning of values from rolled-back transactions in WAL mode or under an exclusive (BEGIN IMMEDIATE / BEGIN DEFERRED) MVCC outer.

If a transaction calls `nextval()` and rolls back, that value may be generated again later if no committed transaction used that value or a later value. If another concurrent transaction commits after allocating a later value, the rolled-back value can remain a gap. The guarantee is that persisted sequence state remains consistent with committed database state.

### currval()

Returns the last value returned by `nextval()` in the current connection. Raises an error if `nextval()` has not been called on this sequence in the current session.

```sql theme={null}
CREATE SEQUENCE my_seq;
SELECT nextval('my_seq');
-- 1
SELECT nextval('my_seq');
-- 2
SELECT currval('my_seq');
-- 2
```

### setval()

Sets the sequence to an explicit value.

```sql theme={null}
setval(sequence_name, value)
setval(sequence_name, value, is_called)
```

| Parameter       | Description                                                                                                                                     |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `sequence_name` | The name of the sequence.                                                                                                                       |
| `value`         | The value to set. Must be within the sequence's `MINVALUE`..`MAXVALUE` range.                                                                   |
| `is_called`     | When `true` (the default), the next `nextval()` will return `value + increment`. When `false`, the next `nextval()` will return `value` itself. |

```sql theme={null}
CREATE SEQUENCE my_seq;
SELECT setval('my_seq', 100);
-- 100
SELECT nextval('my_seq');
-- 101

SELECT setval('my_seq', 200, false);
-- 200
SELECT nextval('my_seq');
-- 200 (returns the set value since is_called was false)
SELECT nextval('my_seq');
-- 201
```

## Examples

### Basic Sequence

```sql theme={null}
CREATE SEQUENCE counter;
SELECT nextval('counter');
-- 1
SELECT nextval('counter');
-- 2
SELECT nextval('counter');
-- 3
```

### Custom Start and Increment

```sql theme={null}
CREATE SEQUENCE order_seq START WITH 1000 INCREMENT BY 10;
SELECT nextval('order_seq');
-- 1000
SELECT nextval('order_seq');
-- 1010
SELECT nextval('order_seq');
-- 1020
```

### Descending Sequence

```sql theme={null}
CREATE SEQUENCE countdown START WITH 10 INCREMENT BY -1 MINVALUE 1 MAXVALUE 10;
SELECT nextval('countdown');
-- 10
SELECT nextval('countdown');
-- 9
SELECT nextval('countdown');
-- 8
```

### Cycling Sequence

When the sequence reaches its boundary, it wraps around:

```sql theme={null}
CREATE SEQUENCE color_cycle MINVALUE 1 MAXVALUE 3 CYCLE;
SELECT nextval('color_cycle');
-- 1
SELECT nextval('color_cycle');
-- 2
SELECT nextval('color_cycle');
-- 3
SELECT nextval('color_cycle');
-- 1 (wraps around)
SELECT nextval('color_cycle');
-- 2
```

Without `CYCLE`, exceeding the boundary raises an error:

```sql theme={null}
CREATE SEQUENCE bounded MINVALUE 1 MAXVALUE 3;
SELECT nextval('bounded');
-- 1
SELECT nextval('bounded');
-- 2
SELECT nextval('bounded');
-- 3
SELECT nextval('bounded');
-- Error: nextval: reached maximum value of sequence "bounded"
```

### Sequence as Column Default

A sequence can supply default values for a table column:

```sql theme={null}
CREATE SEQUENCE invoice_seq START WITH 1000;

CREATE TABLE invoices (
    id INTEGER PRIMARY KEY,
    invoice_number INTEGER DEFAULT (nextval('invoice_seq')),
    description TEXT
);

INSERT INTO invoices (description) VALUES ('First order');
INSERT INTO invoices (description) VALUES ('Second order');
INSERT INTO invoices (description) VALUES ('Third order');

SELECT * FROM invoices;
-- 1|1000|First order
-- 2|1001|Second order
-- 3|1002|Third order
```

### IF NOT EXISTS

```sql theme={null}
-- Safe to run multiple times
CREATE SEQUENCE IF NOT EXISTS my_seq;
CREATE SEQUENCE IF NOT EXISTS my_seq;
SELECT nextval('my_seq');
-- 1
```

## Concurrency

`nextval()` and `setval()` persist sequence state to a backing table.

In **WAL mode** (the default), the backing-table write rides on the caller's
write transaction. Only one connection can hold the write lock at a time; if
another connection is already writing, `SELECT nextval(...)` will return
`SQLITE_BUSY` ("Database is busy"). This is different from `AUTOINCREMENT`,
which uses a process-local allocator and writes the committed watermark with
the table insert.

In **MVCC mode** (`journal_mode=experimental_mvcc`), `nextval()` inside a
`BEGIN CONCURRENT` outer wraps the backing-table write in an autonomous inner
transaction that commits immediately and independently of the outer's eventual
commit or rollback. This lets multiple concurrent `BEGIN CONCURRENT` callers
each get a fresh value (serialized by the standard MVCC PK conflict + bounded
retry on the backing table), and matches PostgreSQL's "burnt value" semantics
for this case. For autocommit, `BEGIN`, `BEGIN IMMEDIATE`, and `BEGIN DEFERRED`,
the outer holds the exclusive lock and the inner write runs inline — under
those modes a `ROLLBACK` un-burns the value (SQLite-like).

In rollback cases, generated values are not guaranteed to be reused and are
not guaranteed to remain gaps. Reuse depends on the outer transaction mode
and on whether another committed transaction advanced the durable watermark
past that value.

If your workload calls `nextval()` from many concurrent connections in WAL
mode, consider using `AUTOINCREMENT` instead, or serializing sequence access
at the application level.

## Recovering the current value

The "current value" reachable from a fresh connection depends on mode:

* **WAL mode**: read `seq` from `sqlite_sequence` (for AUTOINCREMENT tables)
  or query the sequence backing table directly:

  ```sql theme={null}
  SELECT MAX(value) FROM "__turso_internal_seq_<sequence_name>";
  ```

  Both are kept in sync at commit time.

* **MVCC mode**: `sqlite_sequence` is updated **only at checkpoint time** —
  between checkpoints it may lag the actual high-water mark or be empty for a
  freshly-created AUTOINCREMENT table. Do **not** rely on `sqlite_sequence`
  to recover the live value in MVCC mode. Instead, query the internal
  sequence backing table directly:

  ```sql theme={null}
  SELECT MAX(value) FROM "__turso_internal_seq_<sequence_name>";
  ```

  For AUTOINCREMENT, the sequence name is `__turso_internal_autoincrement_<table>`,
  so the backing-table query becomes:

  ```sql theme={null}
  SELECT MAX(value) FROM "__turso_internal_seq___turso_internal_autoincrement_<table>";
  ```

  After `PRAGMA wal_checkpoint(TRUNCATE)`, the backing table is coalesced to
  a single row and `sqlite_sequence` is also brought up to date with exactly
  one row per AUTOINCREMENT table — at that point both are safe to read with
  normal SQLite-compatible queries.

## Errors

| Error                                                 | Cause                                                                           |
| ----------------------------------------------------- | ------------------------------------------------------------------------------- |
| `sequence "name" already exists`                      | A sequence with that name already exists and `IF NOT EXISTS` was not specified. |
| `INCREMENT must not be zero`                          | `INCREMENT BY 0` was specified.                                                 |
| `MINVALUE (x) must be less than MAXVALUE (y)`         | `MINVALUE` is greater than or equal to `MAXVALUE`.                              |
| `START value (x) cannot be less than MINVALUE (y)`    | For ascending sequences, `START` is below `MINVALUE`.                           |
| `START value (x) cannot be greater than MAXVALUE (y)` | For descending sequences, `START` is above `MAXVALUE`.                          |

## See Also

* [DROP SEQUENCE](/sql-reference/statements/drop-sequence) for removing sequences
* [CREATE TABLE](/sql-reference/statements/create-table) for `AUTOINCREMENT` on `INTEGER PRIMARY KEY` columns


# CREATE TABLE
Source: https://docs.turso.tech/sql-reference/statements/create-table

Create a new table in the database with column definitions, constraints, and table options

# CREATE TABLE

Create a new table in the database with column definitions, data types, and constraints.

## Syntax

```sql theme={null}
CREATE TABLE [IF NOT EXISTS] [schema-name.]table-name (
    column-def [, column-def ...] [, table-constraint [, table-constraint ...]]
) [STRICT];
```

### Column Definition

```sql theme={null}
column-name [type-name] [column-constraint ...]
```

### Column Constraints

```sql theme={null}
PRIMARY KEY [ASC | DESC] [AUTOINCREMENT]
NOT NULL
UNIQUE
DEFAULT expression
CHECK (expression)
REFERENCES foreign-table [(column-name)] [foreign-key-actions]
COLLATE collation-name
```

### Table Constraints

```sql theme={null}
PRIMARY KEY (column-name [, column-name ...])
UNIQUE (column-name [, column-name ...])
CHECK (expression)
FOREIGN KEY (column-name [, column-name ...]) REFERENCES foreign-table [(column-name [, column-name ...])] [foreign-key-actions]
```

## Description

`CREATE TABLE` defines a new table in the database. The table name must be unique within the database schema. Each table consists of one or more column definitions and optional table-level constraints.

### Parameters

| Parameter       | Description                                                                                                         |
| --------------- | ------------------------------------------------------------------------------------------------------------------- |
| `IF NOT EXISTS` | Prevents an error if a table with the same name already exists. The statement is a no-op when the table is present. |
| `schema-name`   | The name of an attached database. Defaults to the main database if omitted.                                         |
| `table-name`    | The name of the table to create.                                                                                    |
| `STRICT`        | Enables strict type checking on the table. See [STRICT Tables](#strict-tables).                                     |

## Column Definitions

Each column definition specifies a column name, an optional type, and zero or more column constraints.

When no type is specified, the column accepts any storage class. When a type name is provided, it determines the column's [type affinity](/sql-reference/data-types#type-affinity), which influences how inserted values are stored.

```sql theme={null}
CREATE TABLE measurements (
    id INTEGER PRIMARY KEY,
    sensor_name TEXT NOT NULL,
    reading REAL,
    raw_data BLOB,
    notes                    -- no type: accepts any storage class
);
```

## Column Constraints

### PRIMARY KEY

Designates a column as the table's primary key. A table can have at most one primary key.

```sql theme={null}
CREATE TABLE users (
    id INTEGER PRIMARY KEY
);
```

When a column is declared as `INTEGER PRIMARY KEY`, it becomes an alias for the internal `rowid`, and inserted integer values are used as the row identifier directly. This is the most efficient primary key form.

#### AUTOINCREMENT

The `AUTOINCREMENT` keyword can only be used with `INTEGER PRIMARY KEY`. It prevents the reuse of rowid values from previously deleted rows by maintaining a counter in the `sqlite_sequence` system table.

```sql theme={null}
CREATE TABLE events (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    description TEXT
);
```

Without `AUTOINCREMENT`, Turso may reuse rowid values after rows are deleted. With `AUTOINCREMENT`, automatically generated rowids are greater than any rowid that has ever existed in a committed row for the table, even if rows have been deleted.

<Info>
  `AUTOINCREMENT` adds minor overhead because it maintains a committed high-water mark. Rolled-back inserts do not have to permanently burn their generated rowids, but gaps are still possible when concurrent transactions commit later generated values.
</Info>

<Warning>
  **AUTOINCREMENT under MVCC (`journal_mode=experimental_mvcc`)**: `sqlite_sequence` is updated lazily — only when the WAL is checkpointed. Between checkpoints it may lag the actual high-water mark or be empty for a freshly-created AUTOINCREMENT table. Do **not** rely on `sqlite_sequence` to recover the current value in MVCC mode. Instead, query the internal sequence backing table directly:

  ```sql theme={null}
  SELECT MAX(value) FROM "__turso_internal_seq___turso_internal_autoincrement_<table_name>";
  ```

  This row is updated immediately by every committed (and durably-burnt) `nextval`. After `PRAGMA wal_checkpoint(TRUNCATE)`, `sqlite_sequence` is coalesced to a single row per AUTOINCREMENT table and is then safe to read with normal SQLite-compatible queries.

  Note also that MVCC autoincrement does **not** un-burn rolled-back rowids — a `ROLLBACK` after `INSERT` leaves a gap rather than recycling the rowid. SQLite WAL mode and Turso's WAL mode both un-burn on rollback; only MVCC mode keeps them burnt.
</Warning>

### NOT NULL

Prevents the column from containing NULL values. Any INSERT or UPDATE that would set the column to NULL raises a constraint error.

```sql theme={null}
CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price REAL NOT NULL
);
```

### UNIQUE

Ensures that all values in the column are distinct. NULL values are considered distinct from each other (multiple NULL values are allowed in a UNIQUE column).

```sql theme={null}
CREATE TABLE accounts (
    id INTEGER PRIMARY KEY,
    email TEXT UNIQUE
);
```

### DEFAULT

Specifies a default value for the column when an INSERT statement does not provide one.

```sql theme={null}
CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    status TEXT DEFAULT 'pending',
    created_at TEXT DEFAULT CURRENT_TIMESTAMP,
    quantity INTEGER DEFAULT 1
);
```

The default expression can be:

| Expression                 | Example                                   |
| -------------------------- | ----------------------------------------- |
| A literal value            | `DEFAULT 42`, `DEFAULT 'active'`          |
| NULL                       | `DEFAULT NULL`                            |
| `CURRENT_TIME`             | Current time as `HH:MM:SS`                |
| `CURRENT_DATE`             | Current date as `YYYY-MM-DD`              |
| `CURRENT_TIMESTAMP`        | Current datetime as `YYYY-MM-DD HH:MM:SS` |
| A parenthesized expression | `DEFAULT (1 + 1)`                         |

### CHECK

Defines a boolean expression that must evaluate to true (or NULL) for every row in the table. The expression can reference any column in the same row.

```sql theme={null}
CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    age INTEGER CHECK (age >= 18),
    salary REAL CHECK (salary > 0)
);
```

### REFERENCES (Foreign Key)

Establishes a foreign key relationship between a column and a column in another table. See [Foreign Key Constraints](#foreign-key-constraints) for the full syntax.

```sql theme={null}
CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    customer_id INTEGER REFERENCES customers(id)
);
```

### COLLATE

Specifies the collation sequence used for text comparisons and sorting on the column.

| Collation | Description                                       |
| --------- | ------------------------------------------------- |
| `BINARY`  | Compares text using the raw byte values (default) |
| `NOCASE`  | Case-insensitive comparison for ASCII characters  |
| `RTRIM`   | Like `BINARY`, but ignores trailing spaces        |

Turso also resolves locale-aware collations and custom collations registered on the connection. An unknown collation name now raises a `no such collation sequence` error rather than being silently ignored.

```sql theme={null}
CREATE TABLE contacts (
    id INTEGER PRIMARY KEY,
    name TEXT COLLATE NOCASE,
    code TEXT COLLATE RTRIM
);
```

## Table Constraints

Table constraints apply to one or more columns and are specified after all column definitions.

### Composite PRIMARY KEY

Defines a primary key spanning multiple columns. Each combination of values across the key columns must be unique.

```sql theme={null}
CREATE TABLE enrollment (
    student_id INTEGER,
    course_id INTEGER,
    semester TEXT,
    grade REAL,
    PRIMARY KEY (student_id, course_id, semester)
);
```

### Composite UNIQUE

Ensures that each combination of values across the specified columns is unique.

```sql theme={null}
CREATE TABLE assignments (
    employee_id INTEGER,
    project_id INTEGER,
    role TEXT,
    UNIQUE (employee_id, project_id)
);
```

### Table-Level CHECK

A CHECK constraint at the table level can reference multiple columns.

```sql theme={null}
CREATE TABLE reservations (
    id INTEGER PRIMARY KEY,
    start_date TEXT NOT NULL,
    end_date TEXT NOT NULL,
    CHECK (end_date > start_date)
);
```

### Foreign Key Constraints

Foreign key constraints enforce referential integrity between tables. They ensure that values in the child table correspond to existing values in the parent table.

<Info>
  Foreign key enforcement is off by default. Enable it with `PRAGMA foreign_keys = ON;` before performing operations that should be checked.
</Info>

#### Syntax

```sql theme={null}
-- Column constraint form (single column)
column-name type REFERENCES parent-table(parent-column)
    [ON DELETE action] [ON UPDATE action]
    [DEFERRABLE INITIALLY DEFERRED]

-- Table constraint form (single or multiple columns)
FOREIGN KEY (column-name [, column-name ...])
    REFERENCES parent-table(parent-column [, parent-column ...])
    [ON DELETE action] [ON UPDATE action]
    [DEFERRABLE INITIALLY DEFERRED]
```

#### Foreign Key Actions

The `ON DELETE` and `ON UPDATE` clauses specify what happens to child rows when a referenced parent row is deleted or updated.

| Action        | Description                                                               |
| ------------- | ------------------------------------------------------------------------- |
| `NO ACTION`   | Raises an error if the foreign key constraint is violated (default)       |
| `RESTRICT`    | Same as `NO ACTION`, but checked immediately rather than at statement end |
| `CASCADE`     | Deletes or updates all child rows that reference the parent row           |
| `SET NULL`    | Sets the foreign key columns in child rows to NULL                        |
| `SET DEFAULT` | Sets the foreign key columns in child rows to their default values        |

```sql theme={null}
CREATE TABLE departments (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    dept_id INTEGER,
    manager_id INTEGER,
    FOREIGN KEY (dept_id) REFERENCES departments(id) ON DELETE CASCADE,
    FOREIGN KEY (manager_id) REFERENCES employees(id) ON DELETE SET NULL
);
```

## STRICT Tables

STRICT tables enforce type checking at the storage layer. Every value inserted into a STRICT column must match the declared column type or be losslessly convertible to that type.

```sql theme={null}
CREATE TABLE sensor_data (
    id INTEGER PRIMARY KEY,
    reading REAL NOT NULL,
    label TEXT,
    payload BLOB
) STRICT;
```

### Allowed Column Types

STRICT tables only allow these base column types:

| Type      | Description                                                |
| --------- | ---------------------------------------------------------- |
| `INTEGER` | Signed integer                                             |
| `REAL`    | Floating-point number                                      |
| `TEXT`    | UTF-8 string                                               |
| `BLOB`    | Raw binary data                                            |
| `ANY`     | Any storage class (disables type checking for this column) |

Every column in a STRICT table must have an explicit type declaration.

```sql theme={null}
-- This fails: STRICT tables require explicit types on every column
CREATE TABLE bad (
    id INTEGER PRIMARY KEY,
    data  -- error: missing type
) STRICT;
```

<Info>
  **Turso Extension**: STRICT tables also support custom types defined with [CREATE TYPE](/sql-reference/statements/create-type) and [array types](/sql-reference/data-types#array-types). Custom types extend the type system with user-defined encoding, decoding, validation, and operator overloading. Array columns are declared by appending `[]` to a base type.
</Info>

```sql theme={null}
CREATE TABLE events (
    id uuid PRIMARY KEY,
    name varchar(100) NOT NULL,
    event_date date,
    is_active boolean DEFAULT 1,
    tags TEXT[]
) STRICT;
```

## Unsupported Features

The following CREATE TABLE features are not yet supported:

| Feature                                    | Notes                                                                                                                                                                          |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `CREATE TEMPORARY TABLE`                   | Temporary tables are not supported                                                                                                                                             |
| `CREATE TABLE ... AS SELECT`               | Creating a table from a query result is not supported                                                                                                                          |
| `ON CONFLICT` clause on column constraints | The column-level ON CONFLICT clause (e.g., `NOT NULL ON CONFLICT REPLACE`) is not supported. Use the [INSERT ... ON CONFLICT](/sql-reference/statements/upsert) syntax instead |

### Experimental Features

The following are available behind [experimental feature flags](/sql-reference/experimental-features):

| Feature               | Flag                               | Notes                                                                           |
| --------------------- | ---------------------------------- | ------------------------------------------------------------------------------- |
| `WITHOUT ROWID`       | `--experimental-without-rowid`     | WITHOUT ROWID tables have limitations; see the experimental features page       |
| `GENERATED ALWAYS AS` | `--experimental-generated-columns` | Virtual generated (computed) columns only; stored columns are not yet supported |

## Examples

### Basic Table with Constraints

```sql theme={null}
CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username TEXT NOT NULL UNIQUE,
    email TEXT NOT NULL UNIQUE,
    display_name TEXT,
    bio TEXT DEFAULT '',
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
```

### Table with Foreign Keys and CHECK Constraints

```sql theme={null}
CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    total_amount REAL NOT NULL CHECK (total_amount >= 0),
    status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'shipped', 'delivered', 'cancelled')),
    order_date TEXT NOT NULL DEFAULT CURRENT_DATE,
    FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE RESTRICT
);
```

### STRICT Table

```sql theme={null}
CREATE TABLE inventory (
    product_id INTEGER PRIMARY KEY,
    sku TEXT NOT NULL UNIQUE,
    quantity INTEGER NOT NULL DEFAULT 0 CHECK (quantity >= 0),
    price REAL NOT NULL CHECK (price > 0),
    category TEXT NOT NULL
) STRICT;

-- This succeeds: '42' is losslessly convertible to INTEGER
INSERT INTO inventory VALUES (1, 'WIDGET-001', '42', 9.99, 'widgets');

-- This fails: 'abc' cannot be converted to INTEGER
INSERT INTO inventory VALUES (2, 'GADGET-001', 'abc', 19.99, 'gadgets');
-- Error: cannot store TEXT value in INTEGER column
```

### Composite Primary Key with Foreign Keys

```sql theme={null}
CREATE TABLE course_enrollment (
    student_id INTEGER NOT NULL,
    course_id INTEGER NOT NULL,
    enrolled_date TEXT DEFAULT CURRENT_DATE,
    grade REAL CHECK (grade >= 0.0 AND grade <= 4.0),
    PRIMARY KEY (student_id, course_id),
    FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
    FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE
);
```

### IF NOT EXISTS

```sql theme={null}
-- Safe to run multiple times
CREATE TABLE IF NOT EXISTS sessions (
    id INTEGER PRIMARY KEY,
    user_id INTEGER NOT NULL,
    token TEXT NOT NULL UNIQUE,
    expires_at TEXT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
```

## See Also

* [ALTER TABLE](/sql-reference/statements/alter-table) for modifying existing tables
* [DROP TABLE](/sql-reference/statements/drop-table) for removing tables
* [Data Types](/sql-reference/data-types) for type affinity and STRICT table types
* [CREATE INDEX](/sql-reference/statements/create-index) for indexing table columns
* [CREATE TYPE](/sql-reference/statements/create-type) for custom types in STRICT tables
* [INSERT](/sql-reference/statements/insert) for adding rows to a table


# CREATE TRIGGER
Source: https://docs.turso.tech/sql-reference/statements/create-trigger

Create a trigger that executes statements automatically in response to table changes

# CREATE TRIGGER

Create a trigger that automatically executes one or more SQL statements when a row is inserted into, updated in, or deleted from a table.

## Syntax

```sql theme={null}
CREATE [TEMPORARY] TRIGGER [IF NOT EXISTS] [schema-name.]trigger-name
    [BEFORE | AFTER | INSTEAD OF]
    {INSERT | UPDATE [OF column-name, ...] | DELETE}
    ON table-name
    [FOR EACH ROW]
    [WHEN expression]
BEGIN
    statement;
    [statement; ...]
END;
```

## Description

A trigger defines a set of SQL statements that run automatically when a specified data modification event occurs on a table or view. Triggers execute within the same transaction as the statement that fired them -- if the transaction is rolled back, the trigger's effects are also rolled back.

### Parameters

| Parameter                         | Description                                                                                                                                                                    |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `TEMPORARY`                       | Creates the trigger in a temporary database. The trigger is visible only to the current connection and is dropped when the connection closes. `TEMP` is accepted as a synonym. |
| `IF NOT EXISTS`                   | Prevents an error if a trigger with the same name already exists.                                                                                                              |
| `schema-name`                     | The name of the attached database containing the trigger. Defaults to the main database if omitted. Cannot be used with `TEMPORARY`.                                           |
| `trigger-name`                    | A unique name for the trigger within the database.                                                                                                                             |
| `BEFORE` / `AFTER` / `INSTEAD OF` | When the trigger fires relative to the triggering event. Default is `BEFORE`. `INSTEAD OF` triggers are not yet supported.                                                     |
| `INSERT` / `UPDATE` / `DELETE`    | The data modification event that fires the trigger.                                                                                                                            |
| `OF column-name, ...`             | For UPDATE triggers only. Restricts the trigger to fire only when the specified columns are modified.                                                                          |
| `table-name`                      | The table (or view, for INSTEAD OF triggers) that the trigger monitors.                                                                                                        |
| `FOR EACH ROW`                    | The trigger fires once per modified row. This is the only supported mode and is the default even if omitted.                                                                   |
| `WHEN expression`                 | An optional condition. The trigger body executes only for rows where the expression evaluates to true.                                                                         |

## Trigger Timing

### BEFORE Triggers

A `BEFORE` trigger fires before the triggering statement modifies the row. Use BEFORE triggers to validate or transform data before it is written.

* The `NEW` row reference contains the values that are about to be written. For INSERT triggers, `NEW` is the row being inserted. For UPDATE triggers, `NEW` contains the updated values.
* The `OLD` row reference is available in UPDATE and DELETE triggers and contains the current values before modification.
* If a BEFORE trigger raises an error, the triggering operation is aborted for that row.

### AFTER Triggers

An `AFTER` trigger fires after the triggering statement has modified the row. Use AFTER triggers for logging, auditing, or cascading changes to other tables.

* The `NEW` and `OLD` row references are available with the same semantics as BEFORE triggers.
* The row has already been written when the trigger body executes.

### INSTEAD OF Triggers

<Warning>
  `INSTEAD OF` triggers are not yet supported in Turso. `CREATE TRIGGER ... INSTEAD OF ...` returns an error.
</Warning>

In SQLite, an `INSTEAD OF` trigger can only be created on a view and fires in place of the triggering INSERT, UPDATE, or DELETE, allowing you to make views writable. Turso parses this syntax but does not yet execute it.

## Row References

Inside a trigger body, `NEW` and `OLD` are special row references that provide access to column values.

| Reference    | INSERT               | UPDATE              | DELETE              |
| ------------ | -------------------- | ------------------- | ------------------- |
| `NEW.column` | Value being inserted | Updated value       | Not available       |
| `OLD.column` | Not available        | Value before update | Value being deleted |

```sql theme={null}
-- Access individual columns
NEW.email
OLD.status
```

## WHEN Clause

The optional `WHEN` clause filters which rows cause the trigger body to execute. The expression can reference `NEW` and `OLD` columns.

```sql theme={null}
-- Only fire when the status column actually changes
CREATE TRIGGER log_status_change
    AFTER UPDATE OF status ON orders
    WHEN OLD.status != NEW.status
BEGIN
    INSERT INTO order_log (order_id, old_status, new_status, changed_at)
    VALUES (NEW.id, OLD.status, NEW.status, datetime('now'));
END;
```

## UPDATE OF Columns

For UPDATE triggers, you can restrict the trigger to fire only when specific columns are modified. Without the `OF` clause, the trigger fires on any UPDATE to the table.

```sql theme={null}
-- Only fires when price or quantity changes, not when name changes
CREATE TRIGGER recalc_total
    BEFORE UPDATE OF price, quantity ON line_items
BEGIN
    UPDATE line_items SET total = NEW.price * NEW.quantity WHERE id = NEW.id;
END;
```

## RAISE Function

The `RAISE` function is used inside trigger bodies (and other contexts) to interrupt execution and signal an error. It takes one of four forms:

| Form                       | Behavior                                                                                                                                                                     |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `RAISE(IGNORE)`            | Skips the remainder of the trigger body and the triggering statement for the current row. Processing continues with the next row.                                            |
| `RAISE(ABORT, message)`    | Aborts the current statement and rolls back any changes made by that statement, but preserves prior changes in the transaction. This is the default error handling behavior. |
| `RAISE(ROLLBACK, message)` | Aborts the current statement and rolls back the entire transaction.                                                                                                          |
| `RAISE(FAIL, message)`     | Aborts the current statement. Changes already made by the statement (previous rows) are preserved, but the current row and subsequent rows are not processed.                |

```sql theme={null}
CREATE TRIGGER validate_age
    BEFORE INSERT ON users
BEGIN
    SELECT RAISE(ABORT, 'age must be positive')
    WHERE NEW.age <= 0;
END;
```

## Multiple Statements

A trigger body can contain multiple SQL statements separated by semicolons. The statements execute in order within the same transaction.

```sql theme={null}
CREATE TRIGGER on_user_delete
    AFTER DELETE ON users
BEGIN
    DELETE FROM user_preferences WHERE user_id = OLD.id;
    DELETE FROM user_sessions WHERE user_id = OLD.id;
    INSERT INTO audit_log (action, entity, entity_id, performed_at)
    VALUES ('delete', 'user', OLD.id, datetime('now'));
END;
```

## Examples

### Audit Logging Trigger

Track all changes to a table with an audit log.

```sql theme={null}
CREATE TABLE accounts (
    id INTEGER PRIMARY KEY,
    owner TEXT NOT NULL,
    balance REAL NOT NULL DEFAULT 0
);

CREATE TABLE account_audit (
    id INTEGER PRIMARY KEY,
    account_id INTEGER NOT NULL,
    action TEXT NOT NULL,
    old_balance REAL,
    new_balance REAL,
    changed_at TEXT NOT NULL
);

CREATE TRIGGER audit_balance_change
    AFTER UPDATE OF balance ON accounts
BEGIN
    INSERT INTO account_audit (account_id, action, old_balance, new_balance, changed_at)
    VALUES (NEW.id, 'update', OLD.balance, NEW.balance, datetime('now'));
END;

-- This UPDATE automatically creates an audit row
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
```

### Validation Trigger

Enforce business rules before data is written.

```sql theme={null}
CREATE TABLE reservations (
    id INTEGER PRIMARY KEY,
    room_id INTEGER NOT NULL,
    check_in TEXT NOT NULL,
    check_out TEXT NOT NULL
);

CREATE TRIGGER validate_reservation
    BEFORE INSERT ON reservations
BEGIN
    SELECT RAISE(ABORT, 'check_out must be after check_in')
    WHERE NEW.check_out <= NEW.check_in;
END;
```

### Cascading Update Trigger

Propagate changes to related tables.

```sql theme={null}
CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    category_id INTEGER
);

CREATE TABLE categories (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
);

-- When a category is deleted, unset the category on all associated products
CREATE TRIGGER clear_product_category
    BEFORE DELETE ON categories
BEGIN
    UPDATE products SET category_id = NULL WHERE category_id = OLD.id;
END;
```

## See Also

* [DROP TRIGGER](/sql-reference/statements/drop-trigger) for removing triggers
* [CREATE VIEW](/sql-reference/statements/create-view) for views that can use INSTEAD OF triggers
* [INSERT](/sql-reference/statements/insert), [UPDATE](/sql-reference/statements/update), [DELETE](/sql-reference/statements/delete) for the statements that fire triggers


# CREATE TYPE
Source: https://docs.turso.tech/sql-reference/statements/create-type

Define a custom type with encoding, decoding, validation, operators, and defaults for use in STRICT tables

# CREATE TYPE

<Info>
  **Turso Extension**: CREATE TYPE is a Turso-specific statement not available in standard SQLite. Custom types require STRICT tables. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features).
</Info>

The CREATE TYPE statement defines a user-defined type that controls how values are encoded before storage, decoded when read, validated on input, compared for ordering, and what default value to use. Custom types extend the STRICT table type system beyond the five built-in SQLite storage classes.

## Syntax

```sql theme={null}
CREATE TYPE [IF NOT EXISTS] type-name [(parameters)]
    BASE base-type
    ENCODE encode-expr
    DECODE decode-expr
    [OPERATOR 'op' [function-name] ...]
    [DEFAULT default-expr];
```

## Description

A custom type wraps one of the four base storage types with user-defined logic. When a value is written to a column of a custom type, the ENCODE expression transforms it before storage. When a value is read, the DECODE expression transforms it back. This lets you store data in an efficient on-disk representation while presenting a different form to queries.

Custom types work only with STRICT tables. Using a custom type name in a non-STRICT table has no effect.

## Clauses

### IF NOT EXISTS

Suppresses the error that would occur if a type with the same name already exists. The existing type is left unchanged.

```sql theme={null}
CREATE TYPE IF NOT EXISTS cents BASE integer ENCODE value * 100 DECODE value / 100;
```

### BASE

Specifies the underlying SQLite storage class used to store values on disk. Every custom type must have a BASE clause.

| Base Type | Description                                                |
| --------- | ---------------------------------------------------------- |
| `integer` | Signed integer, stored in 1-8 bytes depending on magnitude |
| `real`    | 8-byte IEEE 754 floating-point number                      |
| `text`    | UTF-8 encoded string                                       |
| `blob`    | Raw binary data                                            |

```sql theme={null}
CREATE TYPE cents BASE integer
    ENCODE value * 100
    DECODE value / 100;
```

### ENCODE / DECODE

The ENCODE expression is evaluated whenever a value is written to a column of this type. The DECODE expression is evaluated whenever a value is read. Both expressions use the identifier `value` to refer to the input.

* **ENCODE**: Transforms the input value into the base storage form. Runs on INSERT, UPDATE, and CAST.
* **DECODE**: Transforms the stored value back into the presentation form. Runs on SELECT.
* **NULL handling**: NULL values bypass both ENCODE and DECODE. A NULL input produces a NULL output without evaluating the expression.

```sql theme={null}
-- Store text in reversed form
CREATE TYPE reversed_text BASE text
    ENCODE turso_reverse(value)
    DECODE turso_reverse(value);
```

The ENCODE expression is the place to put validation logic. If the ENCODE expression raises an error, the INSERT or UPDATE is aborted.

### Parameters

Custom types can accept parameters that are available in the ENCODE and DECODE expressions. Parameters are declared as a parenthesized list after the type name. Each parameter has a name and an optional type annotation.

```sql theme={null}
CREATE TYPE varchar(value text, maxlen integer) BASE text
    ENCODE CASE
        WHEN length(value) <= maxlen THEN value
        ELSE RAISE(ABORT, 'value too long for varchar')
    END
    DECODE value;
```

When a column uses a parametric type, the arguments are supplied in parentheses after the type name:

```sql theme={null}
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name varchar(100),
    bio varchar(500)
) STRICT;
```

The first parameter is always the input `value`. Additional parameters are the arguments provided in the column type declaration, in order.

### OPERATOR

The OPERATOR clause maps SQL operators to functions. This allows columns of a custom type to participate in comparisons, arithmetic, and ordering.

```sql theme={null}
OPERATOR 'op' function-name
```

| Component       | Description                                                                                                   |
| --------------- | ------------------------------------------------------------------------------------------------------------- |
| `'op'`          | The operator symbol as a string literal: `'+'`, `'-'`, `'*'`, `'/'`, `'<'`, `'='`, etc.                       |
| `function-name` | The name of a SQL function to call when this operator is used. Omit to use the base type's native comparison. |

When an operator is defined, expressions like `column + 1` are rewritten into `function_name(column, 1)`.

#### Ordering with OPERATOR '\<'

The `<` operator is special: it controls how values of this type are sorted in ORDER BY, MIN, MAX, and CREATE INDEX.

* **`OPERATOR '<'`** (no function name): Uses the base type's native comparison for ordering. This is sufficient for types where the encoded form sorts correctly (e.g., text dates in ISO 8601 format).
* **`OPERATOR '<' my_compare`**: Uses `my_compare(a, b)` as a custom comparator. The function must return a negative integer if `a < b`, zero if `a = b`, and a positive integer if `a > b`.
* **No `<` operator**: The type cannot be used in ORDER BY or CREATE INDEX.

```sql theme={null}
-- Use base type comparison for ordering
CREATE TYPE cents BASE integer
    ENCODE value * 100
    DECODE value / 100
    OPERATOR '<';

-- Full operator set for a numeric type
CREATE TYPE uint BASE integer
    ENCODE CASE WHEN value >= 0 THEN value ELSE RAISE(ABORT, 'uint must be non-negative') END
    DECODE value
    OPERATOR '+' uint_add
    OPERATOR '-' uint_sub
    OPERATOR '*' uint_mul
    OPERATOR '<'
    OPERATOR '=';
```

The `=` operator, when defined, is also used to derive `!=`. The `<` operator is used to derive `>`, `>=`, and `<=` automatically by swapping arguments or negating the result.

### DEFAULT

The DEFAULT clause sets a type-level default value. When a column of this type does not specify its own DEFAULT, this value is used.

```sql theme={null}
CREATE TYPE boolean BASE integer
    ENCODE boolean_to_int(value)
    DECODE CASE WHEN value THEN 1 ELSE 0 END
    OPERATOR '<'
    DEFAULT 0;
```

A column-level DEFAULT overrides the type-level DEFAULT:

```sql theme={null}
CREATE TABLE flags (
    id INTEGER PRIMARY KEY,
    is_active boolean,              -- uses type default (0)
    is_admin boolean DEFAULT 1      -- overrides type default
) STRICT;
```

## Validation with RAISE

Use CASE/WHEN with RAISE in the ENCODE expression to validate input at write time. If the condition fails, the statement is aborted with the specified error message.

```sql theme={null}
CREATE TYPE positive_int BASE integer
    ENCODE CASE
        WHEN value > 0 THEN value
        ELSE RAISE(ABORT, 'value must be positive')
    END
    DECODE value;
```

```sql theme={null}
CREATE TYPE email BASE text
    ENCODE CASE
        WHEN value LIKE '%_@_%.__%' THEN lower(value)
        ELSE RAISE(ABORT, 'invalid email address')
    END
    DECODE value;
```

## CAST Support

The CAST expression applies a custom type's ENCODE logic:

```sql theme={null}
CREATE TYPE cents BASE integer
    ENCODE value * 100
    DECODE value / 100;

SELECT CAST(42.5 AS cents);
-- 4250
```

This is useful for converting values to the encoded form outside of INSERT/UPDATE, such as in WHERE clauses and CHECK constraints.

## CHECK Constraints with Custom Types

In STRICT tables with custom types, CHECK constraints operate on the **decoded** (presentation) values. When comparing a column of a custom type against a literal, use CAST to encode the literal:

```sql theme={null}
CREATE TYPE cents BASE integer
    ENCODE value * 100
    DECODE value / 100;

CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    price cents CHECK(price >= CAST(0 AS cents))
) STRICT;
```

## Inspecting Types

### PRAGMA list\_types

Lists all available types, including built-in and user-defined:

```sql theme={null}
PRAGMA list_types;
-- name
-- boolean
-- varchar
-- date
-- time
-- timestamp
-- smallint
-- numeric
-- ...
```

### sqlite\_turso\_types Virtual Table

The `sqlite_turso_types` virtual table provides the name and SQL definition of each type:

```sql theme={null}
SELECT name, sql FROM sqlite_turso_types;
-- name    | sql
-- boolean | CREATE TYPE boolean(value any) BASE integer ENCODE ...
-- ...
```

## Using with ALTER TABLE

Custom types can be used when adding columns with ALTER TABLE ADD COLUMN:

```sql theme={null}
ALTER TABLE users ADD COLUMN email varchar(255);
```

The added column follows the same STRICT type-checking rules as columns defined in the original CREATE TABLE.

## Built-in Types

Turso provides the following built-in custom types in STRICT tables:

| Type           | Base    | Parameters           | Description                                                                                  |
| -------------- | ------- | -------------------- | -------------------------------------------------------------------------------------------- |
| `boolean`      | integer | --                   | Constrained to 0 or 1. Aliases: `bool`.                                                      |
| `smallint`     | integer | --                   | Integer constrained to -32768 through 32767. Aliases: `int2`.                                |
| `bigint`       | integer | --                   | Integer (no range constraint). Aliases: `int8`.                                              |
| `varchar(N)`   | text    | `maxlen`             | Text with a maximum length of N characters.                                                  |
| `date`         | text    | --                   | ISO 8601 date (YYYY-MM-DD), validated on insert.                                             |
| `time`         | text    | --                   | ISO 8601 time (HH:MM:SS), validated on insert.                                               |
| `timestamp`    | text    | --                   | ISO 8601 datetime, validated on insert.                                                      |
| `numeric(P,S)` | blob    | `precision`, `scale` | Fixed-point decimal with P total digits and S decimal places. Supports arithmetic operators. |
| `uuid`         | blob    | --                   | UUID stored as 16-byte blob, displayed as string. Default: `uuid4_str()`.                    |
| `inet`         | text    | --                   | Validated IP address (IPv4 or IPv6).                                                         |
| `bytea`        | blob    | --                   | Binary data (PostgreSQL-compatible alias).                                                   |
| `json`         | text    | --                   | Validated JSON text.                                                                         |
| `jsonb`        | blob    | --                   | JSON stored in binary format, returned as JSON text on read.                                 |

<Info>
  The `uuid`, `json`, and `jsonb` types require their respective extensions to be compiled in. They are available by default in standard Turso builds.
</Info>

## Composite Types: STRUCT and UNION

In addition to encode/decode custom types, CREATE TYPE supports two composite type forms: **STRUCT** (named product type) and **UNION** (discriminated union / tagged variant). Both store data as blobs on disk and require STRICT tables.

### STRUCT

A STRUCT groups multiple named fields into a single column. Use dot notation to read and filter on individual fields:

```sql theme={null}
CREATE TYPE point AS STRUCT(x INT, y INT);

CREATE TABLE locations (
    id INTEGER PRIMARY KEY,
    pos point
) STRICT;

INSERT INTO locations VALUES (1, struct_pack(10, 20));
INSERT INTO locations VALUES (2, struct_pack(30, 40));

-- Read fields with dot notation
SELECT pos.x, pos.y FROM locations WHERE id = 1;
-- 10|20

-- Filter and order by fields
SELECT id, pos.x FROM locations WHERE pos.y > 25 ORDER BY pos.x;
-- 2|30
```

Dot notation works everywhere a column expression does — SELECT, WHERE, ORDER BY, GROUP BY, HAVING, and aggregate functions:

```sql theme={null}
CREATE TYPE address AS STRUCT(street TEXT, city TEXT, zip TEXT);
CREATE TABLE people(name TEXT, home address) STRICT;

INSERT INTO people VALUES ('Alice', struct_pack('123 Main St', 'Springfield', '62704'));
INSERT INTO people VALUES ('Bob', struct_pack('456 Oak Ave', 'Springfield', '62701'));

SELECT home.city, COUNT(*) FROM people GROUP BY home.city;
-- Springfield|2
```

When a table name and column name collide, table references always win. Use an alias to reach the struct field:

```sql theme={null}
-- t.x here is table.column, not column.field
SELECT t.x FROM t;

-- Use an alias to access the struct field
SELECT s.pos.x FROM locations AS s;
```

#### struct\_pack()

Creates a struct value for INSERT and UPDATE. Arguments are positional, matching the field order in the type definition.

```sql theme={null}
INSERT INTO locations VALUES (1, struct_pack(10, 20));
UPDATE locations SET pos = struct_pack(99, 88) WHERE id = 1;
```

#### struct\_extract()

Function form of dot notation — `struct_extract(col, 'field')` is equivalent to `col.field`. Primarily useful in expression indexes, where dot notation cannot be used:

```sql theme={null}
CREATE INDEX idx_x ON locations(struct_extract(pos, 'x'));
```

### UNION

A UNION is a discriminated union (tagged variant). Each value carries exactly one of the declared variants, identified by a tag. Use dot notation to extract a variant's value — it returns NULL when the active variant doesn't match:

```sql theme={null}
CREATE TYPE platform_id AS UNION(telegram INT, slack TEXT, signal TEXT);

CREATE TABLE contacts (
    id INTEGER PRIMARY KEY,
    platform platform_id
) STRICT;

INSERT INTO contacts VALUES (1, union_value('telegram', 12345));
INSERT INTO contacts VALUES (2, union_value('slack', 'U0ABC'));
INSERT INTO contacts VALUES (3, union_value('signal', '+1555'));

-- Dot notation extracts the variant value (NULL if tag doesn't match)
SELECT id, platform.telegram, platform.slack FROM contacts ORDER BY id;
-- 1|12345|
-- 2||U0ABC
-- 3||

-- Combine with union_tag() to filter by variant
SELECT id, platform.slack FROM contacts WHERE union_tag(platform) = 'slack';
-- 2|U0ABC
```

For unions whose variants are struct types, chain dot access to reach nested fields:

```sql theme={null}
CREATE TYPE telegram_msg AS STRUCT(chat_id INT, text TEXT);
CREATE TYPE msg AS UNION(telegram telegram_msg, slack TEXT);
CREATE TABLE messages(id INT, data msg) STRICT;

INSERT INTO messages VALUES (1, union_value('telegram', struct_pack(100, 'hello')));

-- col.variant.field
SELECT data.telegram.chat_id, data.telegram.text FROM messages;
-- 100|hello
```

#### union\_value()

Creates a union value for INSERT and UPDATE. The first argument is the variant tag (a string literal), the second is the value. The tag is resolved against the target column's union type.

```sql theme={null}
INSERT INTO contacts VALUES (1, union_value('telegram', 12345));
UPDATE contacts SET platform = union_value('slack', 'U0XYZ') WHERE id = 1;
```

#### union\_tag()

Returns the tag name of the active variant as text.

```sql theme={null}
SELECT id, union_tag(platform) FROM contacts ORDER BY id;
-- 1|telegram
-- 2|slack
-- 3|signal
```

#### union\_extract()

Function form of dot notation — `union_extract(col, 'variant')` is equivalent to `col.variant`. Primarily useful in expression indexes:

```sql theme={null}
CREATE INDEX idx_tg ON contacts(union_extract(platform, 'telegram'));
```

### NULL handling

NULL values propagate through all struct and union operations:

```sql theme={null}
CREATE TYPE number AS UNION(i INT, f REAL);
CREATE TABLE values_t(id INT, val number) STRICT;

INSERT INTO values_t VALUES (1, union_value('i', 42));
INSERT INTO values_t VALUES (2, union_value('f', 3.14));
INSERT INTO values_t VALUES (3, NULL);

SELECT id, val.i, val.f FROM values_t ORDER BY id;
-- 1|42|
-- 2||3.14
-- 3||
```

### Dot notation precedence

When a dot expression `a.b` is ambiguous (e.g., `a` could be a table name or a column name), table references always take priority over struct field access. This follows DuckDB's resolution rules.

```sql theme={null}
CREATE TYPE point AS STRUCT(x INT, y INT);
CREATE TABLE t(x INT, t point) STRICT;
INSERT INTO t VALUES (99, struct_pack(10, 20));

-- t.x resolves as table.column (99), not column.field (10)
SELECT t.x FROM t;
-- 99

-- Use an alias to reach the struct field
SELECT s.t.x FROM t AS s;
-- 10
```

For `a.b.c`, the resolution order is: database.table.column, then table.column.field.

### Inline types not supported

Inline STRUCT/UNION declarations in column definitions are not supported. Always use CREATE TYPE first:

```sql theme={null}
-- This will error:
CREATE TABLE t(data STRUCT(x INT, y INT)) STRICT;

-- Do this instead:
CREATE TYPE point AS STRUCT(x INT, y INT);
CREATE TABLE t(data point) STRICT;
```

## Array Types

Array columns store ordered collections of values. Unlike STRUCT and UNION, arrays don't require an explicit CREATE TYPE — declare them by appending `[]` to any base type name in a STRICT table column definition:

```sql theme={null}
CREATE TABLE sensors (
    id INTEGER PRIMARY KEY,
    readings REAL[],
    labels TEXT[],
    flags INTEGER[]
) STRICT;

INSERT INTO sensors VALUES (1, ARRAY[1.5, 2.5, 3.5], ARRAY['temp','humidity'], '[0, 1, 1]');

SELECT readings[0], labels[1], array_length(flags) FROM sensors;
-- 1.5|humidity|3
```

Multi-dimensional arrays use multiple bracket pairs:

```sql theme={null}
CREATE TABLE matrices (
    id INTEGER PRIMARY KEY,
    data INTEGER[][]
) STRICT;

INSERT INTO matrices VALUES (1, ARRAY[ARRAY[1,2], ARRAY[3,4]]);
SELECT data[1][0] FROM matrices;
-- 3
```

Element types are validated on insert — an `INTEGER[]` column rejects values that cannot be stored as integers. Arrays are stored internally as compact record-format BLOBs and displayed as JSON arrays on output.

For the full set of array functions (`array_agg`, `array_append`, `array_contains`, `array_slice`, etc.), operators (`@>`, `&&`, `||`), and subscript syntax, see [Array Functions](/sql-reference/functions/array).

## Restrictions

* **STRICT tables only**: Custom type names are ignored in non-STRICT tables. The column uses standard type affinity rules instead.
* **Cannot drop while in use**: A type cannot be dropped with DROP TYPE while any table has a column of that type.

<Note>
  If a non-STRICT table was created with a type name that did not exist at the time (e.g., `CREATE TABLE t(x mytype)`), and a custom type with that name is created afterwards (`CREATE TYPE mytype ...`), the type's ENCODE/DECODE logic will **not** be retroactively applied to the existing table. This is not a bug: non-STRICT tables treat all type names as plain affinity hints regardless of whether a matching custom type exists. STRICT tables prevent this scenario because they reject unknown type names at CREATE TABLE time.
</Note>

* **No subqueries in expressions**: The ENCODE, DECODE, and DEFAULT expressions cannot contain subqueries, aggregate functions, or window functions.
* **No indexes on STRUCT/UNION columns**: CREATE INDEX on a STRUCT or UNION column is not supported.

## Examples

### Identity Type (Passthrough)

A minimal type that stores and retrieves values without transformation:

```sql theme={null}
CREATE TYPE my_text BASE text ENCODE value DECODE value;

CREATE TABLE notes (
    id INTEGER PRIMARY KEY,
    body my_text
) STRICT;
```

### Monetary Values as Cents

Store dollar amounts as integer cents for exact arithmetic, present them as the original value:

```sql theme={null}
CREATE TYPE cents BASE integer
    ENCODE value * 100
    DECODE value / 100
    OPERATOR '<';

CREATE TABLE invoices (
    id INTEGER PRIMARY KEY,
    amount cents
) STRICT;

INSERT INTO invoices VALUES (1, 42);
-- Stored as 4200 on disk, queried as 42

SELECT amount FROM invoices;
-- 42
```

### JSON Validation

Validate that inserted values are valid JSON:

```sql theme={null}
CREATE TYPE json BASE text
    ENCODE json(value)
    DECODE value;

CREATE TABLE configs (
    id INTEGER PRIMARY KEY,
    data json
) STRICT;

INSERT INTO configs VALUES (1, '{"key": "value"}');
-- succeeds

INSERT INTO configs VALUES (2, 'not json');
-- Error: malformed JSON
```

### Unsigned Integer with Operators

A non-negative integer type with arithmetic and comparison operators:

```sql theme={null}
CREATE TYPE uint BASE integer
    ENCODE CASE
        WHEN value >= 0 THEN value
        ELSE RAISE(ABORT, 'uint must be non-negative')
    END
    DECODE value
    OPERATOR '<'
    OPERATOR '=';

CREATE TABLE counters (
    id INTEGER PRIMARY KEY,
    count uint DEFAULT 0
) STRICT;

INSERT INTO counters VALUES (1, 5);
SELECT * FROM counters WHERE count > 0 ORDER BY count;
```

### Fixed-Point Decimal

Use the built-in `numeric` type for precise decimal arithmetic:

```sql theme={null}
CREATE TABLE transactions (
    id INTEGER PRIMARY KEY,
    amount numeric(10, 2),
    tax numeric(10, 2)
) STRICT;

INSERT INTO transactions VALUES (1, 99.99, 8.25);
SELECT amount + tax FROM transactions;
-- 108.24
```

## See Also

* [DROP TYPE](/sql-reference/statements/drop-type) for removing custom types
* [CREATE DOMAIN](/sql-reference/statements/create-domain) for defining constrained type aliases without ENCODE/DECODE logic
* [Data Types](/sql-reference/data-types) for an overview of the type system and type affinity
* [Array Types](/sql-reference/data-types#array-types) for native array columns (`INTEGER[]`, `TEXT[]`, etc.)
* [Array Functions](/sql-reference/functions/array) for array construction, manipulation, and operators
* [CREATE TABLE](/sql-reference/statements/create-table) for STRICT table definitions


# CREATE VIEW
Source: https://docs.turso.tech/sql-reference/statements/create-view

Create a named view based on a SELECT statement

# CREATE VIEW

Create a named view that encapsulates a SELECT statement.

## Syntax

```sql theme={null}
CREATE [TEMPORARY] VIEW [IF NOT EXISTS] [schema-name.]view-name
    AS select-statement;
```

## Description

A view is a virtual table defined by a SELECT statement. It does not store data on disk -- every time you query the view, Turso executes the underlying SELECT. Views simplify complex queries, provide a stable interface over evolving table schemas, and restrict which columns or rows are visible.

### Parameters

| Parameter          | Description                                                                                                                                                              |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `TEMPORARY`        | Creates the view in a temporary database. The view is visible only to the current connection and is dropped when the connection closes. `TEMP` is accepted as a synonym. |
| `IF NOT EXISTS`    | Prevents an error if a view with the same name already exists. The statement is a no-op when the view is present.                                                        |
| `schema-name`      | The name of the attached database in which to create the view. Defaults to the main database if omitted. Cannot be used with `TEMPORARY`.                                |
| `view-name`        | A unique name for the view within the database.                                                                                                                          |
| `select-statement` | The SELECT query that defines the view's contents.                                                                                                                       |

### Behavior

* Views are read-only. INSERT, UPDATE, and DELETE on a view are not supported unless an INSTEAD OF trigger is defined on the view.
* The SELECT statement is stored in the `sqlite_schema` table and parsed each time the view is referenced.
* If a table referenced by the view is dropped or altered in an incompatible way, queries against the view produce an error.

## Examples

### Simplify a Common Query

```sql theme={null}
CREATE VIEW active_users AS
SELECT id, name, email
FROM users
WHERE status = 'active';

-- Use the view like a table
SELECT * FROM active_users WHERE name LIKE 'A%';
```

### Aggregate View

```sql theme={null}
CREATE VIEW department_stats AS
SELECT
    department,
    COUNT(*) AS employee_count,
    AVG(salary) AS avg_salary
FROM employees
GROUP BY department;

SELECT * FROM department_stats ORDER BY avg_salary DESC;
```

### Temporary View for a Session

```sql theme={null}
CREATE TEMP VIEW my_tasks AS
SELECT id, title, due_date
FROM tasks
WHERE assignee = 'alice';
```

## See Also

* [CREATE MATERIALIZED VIEW](/sql-reference/statements/create-materialized-view) for views that store precomputed results on disk
* [DROP VIEW](/sql-reference/statements/drop-view) for removing views
* [SELECT](/sql-reference/statements/select) for the query syntax used in view definitions
* [CREATE TRIGGER](/sql-reference/statements/create-trigger) for INSTEAD OF triggers that make views writable


# CREATE VIRTUAL TABLE
Source: https://docs.turso.tech/sql-reference/statements/create-virtual-table

Create a virtual table backed by a module that provides custom data access

# CREATE VIRTUAL TABLE

The CREATE VIRTUAL TABLE statement creates a table whose contents are computed on-the-fly by a module rather than stored in the database file. Virtual tables provide a SQL interface over external data sources, custom generators, and table-valued functions.

## Syntax

```sql theme={null}
CREATE VIRTUAL TABLE [IF NOT EXISTS] [schema-name.]table-name
    USING module-name [(module-arguments)];
```

## Description

A virtual table delegates row storage and retrieval to a module. The module determines what columns the table has, what data it returns, and how queries against it are optimized. Virtual tables can be queried with SELECT just like regular tables, but they cannot be targets of INSERT, UPDATE, or DELETE unless the module implements write support.

### Clauses

| Clause             | Description                                                                                                    |
| ------------------ | -------------------------------------------------------------------------------------------------------------- |
| `IF NOT EXISTS`    | Suppresses the error that would occur if a table with the same name already exists.                            |
| `schema-name`      | The name of the attached database in which to create the virtual table. Defaults to `main`.                    |
| `table-name`       | The name for the new virtual table.                                                                            |
| `module-name`      | The name of the module that provides the virtual table implementation.                                         |
| `module-arguments` | Comma-separated arguments passed to the module. The format and meaning of these arguments are module-specific. |

## Available Modules

### csv

The `csv` module reads data from a CSV file and exposes its contents as a read-only table. Load the csv extension first with `load_extension('csv')`.

| Argument   | Description                                                            |
| ---------- | ---------------------------------------------------------------------- |
| `filename` | Path to the CSV file to read.                                          |
| `header`   | Set to `yes` if the first row contains column names. Defaults to `no`. |
| `columns`  | Number of columns to expect when `header` is `no`.                     |
| `schema`   | Explicit CREATE TABLE schema for column names and types.               |

### generate\_series

The `generate_series` module is a built-in table-valued function that produces a sequence of integer values. It does not require an extension to be loaded.

| Argument | Description                                    |
| -------- | ---------------------------------------------- |
| `start`  | The first value in the sequence (inclusive).   |
| `stop`   | The last value in the sequence (inclusive).    |
| `step`   | The increment between values. Defaults to `1`. |

The `generate_series` module can be used either as a virtual table or as a table-valued function in the FROM clause.

## Examples

### Read a CSV File

```sql theme={null}
-- Load the csv extension
SELECT load_extension('csv');

-- Create a virtual table from a CSV file with a header row
CREATE VIRTUAL TABLE employees USING csv(
    filename='employees.csv',
    header=yes
);

-- Query the CSV data with SQL
SELECT * FROM employees WHERE department = 'Engineering';
```

### Generate a Sequence of Integers

```sql theme={null}
-- Use generate_series as a table-valued function
SELECT value FROM generate_series(1, 10);
-- value
-- 1
-- 2
-- ...
-- 10

-- Use with a step argument
SELECT value FROM generate_series(0, 100, 10);
-- value
-- 0
-- 10
-- 20
-- ...
-- 100
```

### Generate a Date Series with generate\_series

```sql theme={null}
-- Generate dates for the next 7 days
SELECT date('now', '+' || value || ' days') AS day
FROM generate_series(0, 6);
```

## See Also

* [SELECT](/sql-reference/statements/select) for querying virtual tables
* [Compatibility](/sql-reference/compatibility) for the full list of supported modules


# DELETE
Source: https://docs.turso.tech/sql-reference/statements/delete

Remove rows from a table

# DELETE

The `DELETE` statement removes rows from a table.

## Syntax

```sql theme={null}
DELETE FROM table-name
[WHERE expression]
[RETURNING result-column [, result-column ...]]
[ORDER BY sort-expression [, sort-expression ...]]
[LIMIT expression [OFFSET expression]]
```

## Description

`DELETE` removes rows that match the `WHERE` condition. If no `WHERE` clause is provided, all rows in the table are deleted. The table itself is not removed; use [DROP TABLE](/sql-reference/statements/drop-table) to remove a table entirely.

### WHERE Clause

The `WHERE` clause restricts which rows are deleted. Only rows where the expression evaluates to true are removed. Without a `WHERE` clause, every row in the table is deleted.

```sql theme={null}
DELETE FROM logs WHERE created_at < date('now', '-90 days');
```

### RETURNING Clause

The `RETURNING` clause causes the `DELETE` statement to return the values of each deleted row. The result columns can be any expression referencing the deleted row's columns.

```sql theme={null}
DELETE FROM sessions
WHERE expires_at < datetime('now')
RETURNING session_id, user_id;
```

### ORDER BY and LIMIT

The `ORDER BY` and `LIMIT` clauses restrict which rows are deleted. `ORDER BY` determines the order in which rows are considered, and `LIMIT` caps the number of rows that are actually removed. `ORDER BY` requires `LIMIT` to be present.

```sql theme={null}
DELETE FROM logs
ORDER BY created_at ASC
LIMIT 1000;
```

`LIMIT` accepts an optional `OFFSET` to skip a number of rows before applying the limit:

```sql theme={null}
DELETE FROM logs
ORDER BY created_at ASC
LIMIT 500 OFFSET 100;
```

<Info>
  `ORDER BY` without `LIMIT` is not allowed on `DELETE` statements. If you need to delete all rows matching a condition in a specific order, provide a `LIMIT` value larger than the expected number of matching rows.
</Info>

## Examples

### Delete rows matching a condition

```sql theme={null}
CREATE TABLE tasks (id INTEGER PRIMARY KEY, title TEXT, completed INTEGER);
INSERT INTO tasks VALUES (1, 'Write docs', 1);
INSERT INTO tasks VALUES (2, 'Review PR', 0);
INSERT INTO tasks VALUES (3, 'Fix bug', 1);

DELETE FROM tasks WHERE completed = 1;

SELECT * FROM tasks;
-- 2|Review PR|0
```

### Delete with RETURNING

```sql theme={null}
CREATE TABLE queue (id INTEGER PRIMARY KEY, payload TEXT, priority INTEGER);
INSERT INTO queue VALUES (1, 'job_a', 10);
INSERT INTO queue VALUES (2, 'job_b', 5);
INSERT INTO queue VALUES (3, 'job_c', 10);

DELETE FROM queue
WHERE id = (SELECT id FROM queue ORDER BY priority DESC, id ASC LIMIT 1)
RETURNING id, payload;
-- 1|job_a
```

### Delete the oldest rows with ORDER BY and LIMIT

```sql theme={null}
CREATE TABLE events (id INTEGER PRIMARY KEY, message TEXT, created_at TEXT);
INSERT INTO events VALUES (1, 'startup', '2025-01-01');
INSERT INTO events VALUES (2, 'request', '2025-01-02');
INSERT INTO events VALUES (3, 'shutdown', '2025-01-03');
INSERT INTO events VALUES (4, 'startup', '2025-01-04');

DELETE FROM events
ORDER BY created_at ASC
LIMIT 2;

SELECT * FROM events;
-- 3|shutdown|2025-01-03
-- 4|startup|2025-01-04
```

## See Also

* [UPDATE](/sql-reference/statements/update) for modifying existing rows
* [INSERT](/sql-reference/statements/insert) for adding new rows
* [DROP TABLE](/sql-reference/statements/drop-table) for removing an entire table
* [SELECT](/sql-reference/statements/select) for querying data


# DETACH DATABASE
Source: https://docs.turso.tech/sql-reference/statements/detach-database

Remove an attached database from the current connection

# DETACH DATABASE

The DETACH DATABASE statement removes a previously attached database from the current connection. After detaching, tables in that database are no longer accessible through the schema name.

## Syntax

```sql theme={null}
DETACH [DATABASE] schema-name;
```

## Description

DETACH closes the connection to the attached database identified by `schema-name` and frees its resources. Any subsequent references to `schema-name.table-name` will fail.

### Clauses

| Clause        | Description                                              |
| ------------- | -------------------------------------------------------- |
| `DATABASE`    | Optional keyword. Has no effect on behavior.             |
| `schema-name` | The schema name assigned when the database was attached. |

The `main` and `temp` schemas cannot be detached.

<Info>
  DETACH cannot be used while a transaction is active. Commit or roll back any open transaction before detaching a database.
</Info>

## Examples

### Detach a Database

```sql theme={null}
ATTACH 'archive.db' AS archive;

-- Use the attached database
SELECT count(*) FROM archive.events;

-- Detach when done
DETACH DATABASE archive;
```

### Detach Without the DATABASE Keyword

```sql theme={null}
DETACH archive;
```

## See Also

* [ATTACH DATABASE](/sql-reference/statements/attach-database) for attaching a database


# DROP DOMAIN
Source: https://docs.turso.tech/sql-reference/statements/drop-domain

Remove a user-defined domain from the database

# DROP DOMAIN

<Info>
  **Turso Extension**: DROP DOMAIN is a Turso-specific statement not available in standard SQLite. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features).
</Info>

The DROP DOMAIN statement removes a user-defined domain from the database. Once dropped, the domain name can no longer be used in new column definitions.

## Syntax

```sql theme={null}
DROP DOMAIN [IF EXISTS] domain-name;
```

## Description

DROP DOMAIN removes the named domain from the database. The domain must exist unless the `IF EXISTS` clause is specified.

### Clauses

| Clause        | Description                                                                                                                |
| ------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `IF EXISTS`   | Suppresses the error that would occur if the domain does not exist. The statement is a no-op when the domain is not found. |
| `domain-name` | The name of the domain to remove.                                                                                          |

### Restrictions

A domain cannot be dropped while any table column uses it or while another domain references it as a base type. Remove all dependents before dropping.

```sql theme={null}
CREATE DOMAIN base_d AS integer;
CREATE DOMAIN child_d AS base_d CHECK (value > 0);

-- Fails: child_d depends on base_d
DROP DOMAIN base_d;

-- Drop child first, then base
DROP DOMAIN child_d;
DROP DOMAIN base_d;
```

### DROP DOMAIN vs DROP TYPE

DROP DOMAIN can only remove domains created with CREATE DOMAIN. Attempting to use DROP DOMAIN on a type created with CREATE TYPE results in an error, and vice versa.

```sql theme={null}
CREATE TYPE my_type BASE integer ENCODE value DECODE value;
DROP DOMAIN my_type;
-- Error: 'my_type' is a type, not a domain. Use DROP TYPE instead

CREATE DOMAIN my_domain AS integer;
DROP TYPE my_domain;
-- Error: 'my_domain' is a domain, not a type. Use DROP DOMAIN instead
```

## Examples

### Drop a Domain

```sql theme={null}
CREATE DOMAIN positive_int AS integer CHECK (value > 0);
DROP DOMAIN positive_int;
```

### Drop with IF EXISTS

```sql theme={null}
-- Safe to run even if the domain does not exist
DROP DOMAIN IF EXISTS nonexistent_domain;
```

### Drop After Removing Table Dependency

```sql theme={null}
CREATE DOMAIN score AS integer CHECK (value >= 0);
CREATE TABLE results (id INTEGER PRIMARY KEY, val score) STRICT;

-- Fails: table 'results' uses domain 'score'
-- DROP DOMAIN score;

DROP TABLE results;
DROP DOMAIN score;
-- OK
```

## See Also

* [CREATE DOMAIN](/sql-reference/statements/create-domain) for defining domains
* [DROP TYPE](/sql-reference/statements/drop-type) for removing custom types


# DROP INDEX
Source: https://docs.turso.tech/sql-reference/statements/drop-index

Remove an index from the database

# DROP INDEX

Remove an index from the database. The underlying table and its data are not affected.

## Syntax

```sql theme={null}
DROP INDEX [IF EXISTS] [schema-name.]index-name;
```

## Description

`DROP INDEX` removes a previously created index. After the index is dropped, the query planner can no longer use it to optimize queries. The table data is unchanged.

### Parameters

| Parameter     | Description                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------------- |
| `IF EXISTS`   | Prevents an error if the index does not exist. The statement is a no-op when the index is absent. |
| `schema-name` | The name of the attached database containing the index. Defaults to the main database if omitted. |
| `index-name`  | The name of the index to drop.                                                                    |

### Behavior

* Only explicitly created indexes can be dropped. Indexes automatically created for PRIMARY KEY and UNIQUE constraints cannot be dropped directly -- drop the table or use ALTER TABLE instead.
* Dropping an index may degrade query performance for queries that relied on it, but does not affect correctness.

## Examples

### Drop an Index

```sql theme={null}
DROP INDEX idx_users_email;
```

### Drop an Index Only if It Exists

```sql theme={null}
DROP INDEX IF EXISTS idx_old_search;
```

### Drop an Index in an Attached Database

```sql theme={null}
DROP INDEX aux.idx_sessions_token;
```

## See Also

* [CREATE INDEX](/sql-reference/statements/create-index) for creating indexes
* [EXPLAIN](/sql-reference/statements/explain) for verifying query plans after dropping an index


# DROP SEQUENCE
Source: https://docs.turso.tech/sql-reference/statements/drop-sequence

Remove a named sequence generator from the database

# DROP SEQUENCE

<Info>
  **Turso Extension**: DROP SEQUENCE is a Turso-specific statement not available in standard SQLite.
</Info>

The DROP SEQUENCE statement removes a sequence object from the database. Once dropped, the sequence name can no longer be used with `nextval()`, `currval()`, or `setval()`.

## Syntax

```sql theme={null}
DROP SEQUENCE [IF EXISTS] [schema-name.]sequence-name;
```

## Description

DROP SEQUENCE removes the named sequence from the database schema. The sequence must exist unless the `IF EXISTS` clause is specified.

### Clauses

| Clause          | Description                                                                                                                    |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `IF EXISTS`     | Suppresses the error that would occur if the sequence does not exist. The statement is a no-op when the sequence is not found. |
| `schema-name`   | The name of an attached database. Defaults to the main database if omitted.                                                    |
| `sequence-name` | The name of the sequence to remove.                                                                                            |

## Examples

### Drop a Sequence

```sql theme={null}
CREATE SEQUENCE my_seq;
SELECT nextval('my_seq');
-- 1

DROP SEQUENCE my_seq;

SELECT nextval('my_seq');
-- Error: sequence "my_seq" does not exist
```

### Drop with IF EXISTS

```sql theme={null}
-- Safe to run even if the sequence does not exist
DROP SEQUENCE IF EXISTS nonexistent;
```

### Recreate After Drop

Dropping and recreating a sequence resets it to its initial state:

```sql theme={null}
CREATE SEQUENCE counter;
SELECT nextval('counter');
-- 1
SELECT nextval('counter');
-- 2

DROP SEQUENCE counter;
CREATE SEQUENCE counter;

SELECT nextval('counter');
-- 1
```

## Errors

| Error                            | Cause                                                                |
| -------------------------------- | -------------------------------------------------------------------- |
| `sequence "name" does not exist` | No sequence with that name exists and `IF EXISTS` was not specified. |

## See Also

* [CREATE SEQUENCE](/sql-reference/statements/create-sequence) for defining sequences


# DROP TABLE
Source: https://docs.turso.tech/sql-reference/statements/drop-table

Remove a table and all its data from the database

# DROP TABLE

Remove a table and all its data, indexes, triggers, and constraints from the database.

## Syntax

```sql theme={null}
DROP TABLE [IF EXISTS] [schema-name.]table-name;
```

## Description

`DROP TABLE` permanently removes a table definition and all data stored in the table. All indexes, triggers, and constraints associated with the table are also removed.

### Parameters

| Parameter     | Description                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------------- |
| `IF EXISTS`   | Prevents an error if the table does not exist. The statement is a no-op when the table is absent. |
| `schema-name` | The name of the attached database containing the table. Defaults to the main database if omitted. |
| `table-name`  | The name of the table to drop.                                                                    |

### Behavior

* All rows in the table are deleted.
* All indexes built on the table are removed.
* All triggers associated with the table are removed.
* The table entry is removed from the `sqlite_schema` system table.
* If foreign key constraints reference the dropped table, those references become invalid. Turso does not prevent dropping a table that is referenced by foreign keys in other tables.
* `DROP TABLE` is not allowed while the table is being read or written by another statement in the same connection.

## Examples

### Drop a Table

```sql theme={null}
DROP TABLE users;
```

### Drop a Table Only if It Exists

```sql theme={null}
DROP TABLE IF EXISTS temporary_results;
```

### Drop a Table in an Attached Database

```sql theme={null}
DROP TABLE aux.sessions;
```

## See Also

* [CREATE TABLE](/sql-reference/statements/create-table) for creating tables
* [ALTER TABLE](/sql-reference/statements/alter-table) for modifying tables
* [DELETE](/sql-reference/statements/delete) for removing rows without dropping the table


# DROP TRIGGER
Source: https://docs.turso.tech/sql-reference/statements/drop-trigger

Remove a trigger from the database

# DROP TRIGGER

The DROP TRIGGER statement removes a trigger from the database schema. Once dropped, the trigger no longer fires in response to its associated table events.

## Syntax

```sql theme={null}
DROP TRIGGER [IF EXISTS] [schema-name.]trigger-name;
```

## Description

DROP TRIGGER removes the named trigger from the database. The trigger must exist unless the `IF EXISTS` clause is specified.

### Clauses

| Clause         | Description                                                                                                                  |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `IF EXISTS`    | Suppresses the error that would occur if the trigger does not exist. The statement is a no-op when the trigger is not found. |
| `schema-name`  | The name of the attached database containing the trigger. When omitted, Turso searches for the trigger in the main database. |
| `trigger-name` | The name of the trigger to remove.                                                                                           |

Dropping a trigger does not affect any data in the table the trigger was associated with. It only removes the trigger definition from the schema.

## Examples

### Drop a Trigger

```sql theme={null}
CREATE TRIGGER log_insert AFTER INSERT ON users
BEGIN
    INSERT INTO audit_log (action, user_id) VALUES ('insert', NEW.id);
END;

-- Remove the trigger
DROP TRIGGER log_insert;
```

### Drop a Trigger with IF EXISTS

```sql theme={null}
-- Safe to run even if the trigger does not exist
DROP TRIGGER IF EXISTS log_insert;
```

### Drop a Trigger in an Attached Database

```sql theme={null}
DROP TRIGGER IF EXISTS aux.log_insert;
```

## See Also

* [CREATE TRIGGER](/sql-reference/statements/create-trigger) for creating triggers
* [CREATE TABLE](/sql-reference/statements/create-table) for table definitions


# DROP TYPE
Source: https://docs.turso.tech/sql-reference/statements/drop-type

Remove a user-defined custom type from the database

# DROP TYPE

<Info>
  **Turso Extension**: DROP TYPE is a Turso-specific statement not available in standard SQLite. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features).
</Info>

The DROP TYPE statement removes a user-defined custom type from the database. Once dropped, the type name can no longer be used in new column definitions.

## Syntax

```sql theme={null}
DROP TYPE [IF EXISTS] type-name;
```

## Description

DROP TYPE removes the named custom type from the database. The type must exist unless the `IF EXISTS` clause is specified.

### Clauses

| Clause      | Description                                                                                                            |
| ----------- | ---------------------------------------------------------------------------------------------------------------------- |
| `IF EXISTS` | Suppresses the error that would occur if the type does not exist. The statement is a no-op when the type is not found. |
| `type-name` | The name of the custom type to remove.                                                                                 |

### Restrictions

A type cannot be dropped while any table column uses it. All columns of that type must be removed or the table must be dropped before the type can be dropped.

```sql theme={null}
CREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100;

CREATE TABLE prices (
    id INTEGER PRIMARY KEY,
    amount cents
) STRICT;

-- This fails because the 'prices' table has a column of type 'cents'
DROP TYPE cents;
-- Error: type 'cents' is in use

-- Drop the table first, then the type
DROP TABLE prices;
DROP TYPE cents;
```

## Examples

### Drop a Custom Type

```sql theme={null}
CREATE TYPE reversed_text BASE text
    ENCODE turso_reverse(value)
    DECODE turso_reverse(value);

DROP TYPE reversed_text;
```

### Drop a Type with IF EXISTS

```sql theme={null}
-- Safe to run even if the type does not exist
DROP TYPE IF EXISTS reversed_text;
```

## See Also

* [CREATE TYPE](/sql-reference/statements/create-type) for defining custom types
* [DROP DOMAIN](/sql-reference/statements/drop-domain) for removing domains
* [Data Types](/sql-reference/data-types) for an overview of the type system


# DROP VIEW
Source: https://docs.turso.tech/sql-reference/statements/drop-view

Remove a view from the database

# DROP VIEW

Remove a view definition from the database. The underlying tables and their data are not affected.

## Syntax

```sql theme={null}
DROP VIEW [IF EXISTS] [schema-name.]view-name;
```

## Description

`DROP VIEW` removes a previously created view. After the view is dropped, queries referencing the view name produce an error. The tables referenced by the view are unaffected.

### Parameters

| Parameter     | Description                                                                                      |
| ------------- | ------------------------------------------------------------------------------------------------ |
| `IF EXISTS`   | Prevents an error if the view does not exist. The statement is a no-op when the view is absent.  |
| `schema-name` | The name of the attached database containing the view. Defaults to the main database if omitted. |
| `view-name`   | The name of the view to drop.                                                                    |

### Behavior

* Any triggers defined as INSTEAD OF on the dropped view are also removed.
* If the view is a materialized view, the stored data is also removed.

## Examples

### Drop a View

```sql theme={null}
DROP VIEW active_users;
```

### Drop a View Only if It Exists

```sql theme={null}
DROP VIEW IF EXISTS old_report;
```

### Drop a View in an Attached Database

```sql theme={null}
DROP VIEW aux.cached_stats;
```

## See Also

* [CREATE VIEW](/sql-reference/statements/create-view) for creating views and materialized views


# EXPLAIN
Source: https://docs.turso.tech/sql-reference/statements/explain

Display the execution plan or bytecode for a SQL statement

# EXPLAIN

The EXPLAIN statement displays information about how Turso executes a SQL statement. There are two forms: `EXPLAIN` shows the virtual machine bytecode, and `EXPLAIN QUERY PLAN` shows the high-level query execution strategy.

## Syntax

```sql theme={null}
EXPLAIN statement;
EXPLAIN QUERY PLAN statement;
```

| Parameter | Type | Description                                              |
| --------- | ---- | -------------------------------------------------------- |
| statement | SQL  | Any SQL statement (SELECT, INSERT, UPDATE, DELETE, etc.) |

## EXPLAIN

The `EXPLAIN` prefix causes Turso to return the sequence of virtual machine (VDBE) opcodes that would be used to execute the statement, rather than executing the statement itself.

```sql theme={null}
EXPLAIN SELECT * FROM users WHERE id = 1;
-- addr | opcode     | p1 | p2 | p3 | p4       | p5
-- 0    | Init       | 0  | 9  | 0  |          | 0
-- 1    | OpenRead   | 0  | 2  | 0  | 3        | 0
-- 2    | SeekRowid  | 0  | 8  | 1  |          | 0
-- ...
```

### Output Columns

| Column | Description                                                        |
| ------ | ------------------------------------------------------------------ |
| addr   | Instruction address (sequential integer)                           |
| opcode | The operation name (e.g., OpenRead, SeekRowid, Column, ResultRow)  |
| p1     | First operand                                                      |
| p2     | Second operand                                                     |
| p3     | Third operand                                                      |
| p4     | Fourth operand (often a string value like table name or collation) |
| p5     | Fifth operand (flags)                                              |

## EXPLAIN QUERY PLAN

The `EXPLAIN QUERY PLAN` prefix provides a high-level description of the strategy the query optimizer chose for executing a statement. This output is more useful than raw EXPLAIN for understanding query performance.

```sql theme={null}
EXPLAIN QUERY PLAN SELECT * FROM users WHERE name = 'Alice';
-- id | parent | notused | detail
-- 2  | 0      | 0       | SCAN users
```

```sql theme={null}
CREATE INDEX idx_name ON users(name);
EXPLAIN QUERY PLAN SELECT * FROM users WHERE name = 'Alice';
-- id | parent | notused | detail
-- 3  | 0      | 0       | SEARCH users USING INDEX idx_name (name=?)
```

### Output Columns

| Column  | Description                                      |
| ------- | ------------------------------------------------ |
| id      | A unique identifier for this step                |
| parent  | The id of the parent step (0 for top-level)      |
| notused | Reserved for future use (always 0)               |
| detail  | Human-readable description of the execution step |

### Common Detail Messages

| Detail Pattern                                     | Meaning                                           |
| -------------------------------------------------- | ------------------------------------------------- |
| `SCAN table`                                       | Full table scan (no index used)                   |
| `SEARCH table USING INDEX idx (col=?)`             | Index lookup on the specified column              |
| `SEARCH table USING INTEGER PRIMARY KEY (rowid=?)` | Direct rowid lookup                               |
| `USE TEMP B-TREE FOR ORDER BY`                     | A temporary B-tree is used for sorting            |
| `USE TEMP B-TREE FOR DISTINCT`                     | A temporary B-tree is used for deduplication      |
| `COMPOUND SUBQUERY`                                | Indicates a UNION, INTERSECT, or EXCEPT           |
| `CORRELATED SCALAR SUBQUERY`                       | A correlated subquery that returns a single value |

## Examples

### Comparing Query Plans

```sql theme={null}
CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    customer_id INTEGER,
    total REAL
);

-- Without index: full table scan
EXPLAIN QUERY PLAN SELECT * FROM orders WHERE customer_id = 42;
-- SCAN orders

-- After creating an index
CREATE INDEX idx_customer ON orders(customer_id);
EXPLAIN QUERY PLAN SELECT * FROM orders WHERE customer_id = 42;
-- SEARCH orders USING INDEX idx_customer (customer_id=?)
```

### Join Order

```sql theme={null}
EXPLAIN QUERY PLAN
SELECT o.id, c.name
FROM orders o
INNER JOIN customers c ON c.id = o.customer_id
WHERE c.name = 'Alice';
-- SCAN orders AS o
-- SEARCH customers AS c USING INTEGER PRIMARY KEY (rowid=?)
```

### Subqueries

```sql theme={null}
EXPLAIN QUERY PLAN
SELECT * FROM orders
WHERE customer_id IN (SELECT id FROM customers WHERE active = 1);
-- SCAN orders
-- LIST SUBQUERY
--   SCAN customers
```

## See Also

* [ANALYZE](/sql-reference/statements/analyze) for collecting statistics that improve query plans
* [CREATE INDEX](/sql-reference/statements/create-index) for creating indexes to speed up queries


# INSERT
Source: https://docs.turso.tech/sql-reference/statements/insert

Add new rows to a table using literal values, query results, or default values

# INSERT

Adds one or more rows to a table. Rows can be specified as literal values, copied from another query, or generated from column defaults.

## Syntax

```sql theme={null}
INSERT [OR conflict_action] INTO table_name [(column_name [, ...])]
  VALUES (expression [, ...]) [, (expression [, ...]) ...]
  [RETURNING result_column [, ...]];

INSERT [OR conflict_action] INTO table_name [(column_name [, ...])]
  select_statement
  [RETURNING result_column [, ...]];

INSERT [OR conflict_action] INTO table_name [(column_name [, ...])]
  DEFAULT VALUES
  [RETURNING result_column [, ...]];
```

### Parameters

| Parameter          | Type         | Description                                                                                                                         |
| ------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `conflict_action`  | keyword      | One of REPLACE, IGNORE, ABORT, ROLLBACK, or FAIL. Controls behavior on constraint violations                                        |
| `table_name`       | identifier   | The table to insert rows into                                                                                                       |
| `column_name`      | identifier   | Column to assign a value to. Unlisted columns receive their default value or NULL                                                   |
| `expression`       | expression   | A value to insert, or the keyword `DEFAULT` to use the column's default value. Must match the position or name of the target column |
| `select_statement` | SELECT query | A query whose result rows are inserted into the table                                                                               |
| `result_column`    | expression   | Column or expression to return for each inserted row                                                                                |

## Description

INSERT adds new rows to the specified table. There are three forms:

* **VALUES**: Insert one or more rows with explicit values.
* **SELECT**: Insert the result set of a query.
* **DEFAULT VALUES**: Insert a single row where every column uses its default value (or NULL if no default is defined).

When a column list is provided, only those columns receive explicit values. All other columns use their default value or NULL. When no column list is provided, values must be supplied for every column in the table, in the order the columns were defined.

## Inserting Rows

### Single Row

```sql theme={null}
INSERT INTO employees (name, department, salary)
VALUES ('Alice', 'Engineering', 95000);
```

### Multiple Rows

Supply multiple parenthesized groups separated by commas.

```sql theme={null}
INSERT INTO employees (name, department, salary) VALUES
  ('Bob', 'Design', 82000),
  ('Carol', 'Engineering', 97000),
  ('Dave', 'Marketing', 74000);
```

### All Columns

When the column list is omitted, provide a value for every column in table-definition order.

```sql theme={null}
-- Assuming employees has columns: id, name, department, salary
INSERT INTO employees VALUES (1, 'Alice', 'Engineering', 95000);
```

## INSERT INTO ... SELECT

Inserts the result of a SELECT statement. The number of columns returned by the SELECT must match the number of target columns.

```sql theme={null}
INSERT INTO archive_employees (name, department, salary)
SELECT name, department, salary
FROM employees
WHERE termination_date IS NOT NULL;
```

The SELECT can include any valid clause: WHERE, JOIN, GROUP BY, ORDER BY, LIMIT, UNION, or subqueries.

```sql theme={null}
INSERT INTO monthly_summary (department, headcount, total_salary)
SELECT department, COUNT(*), SUM(salary)
FROM employees
GROUP BY department;
```

## DEFAULT VALUES

Inserts a single row where every column receives its default value. Columns without an explicit DEFAULT definition receive NULL. INTEGER PRIMARY KEY columns receive an auto-generated rowid.

```sql theme={null}
CREATE TABLE audit_log (
  id INTEGER PRIMARY KEY,
  created_at TEXT DEFAULT (datetime('now')),
  action TEXT DEFAULT 'unknown'
);

INSERT INTO audit_log DEFAULT VALUES;
-- Inserts: (auto_id, current_timestamp, 'unknown')
```

## Column Default Values

When a column list is provided, omitted columns use their default values.

```sql theme={null}
CREATE TABLE tasks (
  id INTEGER PRIMARY KEY,
  title TEXT NOT NULL,
  status TEXT DEFAULT 'pending',
  priority INTEGER DEFAULT 0,
  created_at TEXT DEFAULT (datetime('now'))
);

-- status, priority, and created_at use their defaults
INSERT INTO tasks (title) VALUES ('Review pull request');
```

## DEFAULT Keyword in VALUES

<Info>
  **Turso extension** -- This feature follows the SQL standard (SQL:2016) but is not supported by SQLite.
</Info>

The `DEFAULT` keyword can be used in place of any value expression in a VALUES list. It resolves to the column's default value as defined in the CREATE TABLE statement, or NULL if no default is defined. This is particularly useful when inserting multiple rows where different rows need defaults for different columns -- something that cannot be achieved by simply omitting columns from the column list.

```sql theme={null}
CREATE TABLE tasks (
  id INTEGER PRIMARY KEY,
  title TEXT NOT NULL,
  status TEXT DEFAULT 'pending',
  priority INTEGER DEFAULT 0
);

-- Use DEFAULT for specific columns
INSERT INTO tasks (id, title, status, priority)
VALUES (1, 'Fix bug', DEFAULT, 5);
-- Inserts: (1, 'Fix bug', 'pending', 5)

-- Different columns use DEFAULT in different rows
INSERT INTO tasks (id, title, status, priority) VALUES
  (2, 'Write docs', DEFAULT, 3),
  (3, 'Code review', 'in_progress', DEFAULT);
-- Inserts: (2, 'Write docs', 'pending', 3)
--          (3, 'Code review', 'in_progress', 0)
```

The `DEFAULT` keyword is only valid inside INSERT VALUES lists. Using it in other contexts (SELECT, WHERE, UPDATE) produces an error.

## Conflict Handling

The `OR` clause specifies what happens when an INSERT violates a uniqueness or NOT NULL constraint.

| Action     | Behavior                                                                                                                             |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `ABORT`    | Roll back the current statement and return an error. Previously inserted rows within the same INSERT are undone. This is the default |
| `ROLLBACK` | Roll back the entire transaction and return an error                                                                                 |
| `FAIL`     | Stop execution and return an error. Rows already inserted by this statement before the failure are kept                              |
| `IGNORE`   | Skip the row that caused the violation and continue with the next row                                                                |
| `REPLACE`  | Delete the conflicting row and insert the new row in its place                                                                       |

```sql theme={null}
-- Skip rows that violate a UNIQUE constraint
INSERT OR IGNORE INTO employees (id, name, department, salary)
VALUES (1, 'Alice', 'Engineering', 95000);

-- Replace the existing row on conflict
INSERT OR REPLACE INTO employees (id, name, department, salary)
VALUES (1, 'Alice', 'Engineering', 105000);

-- Roll back only this statement on conflict
INSERT OR ABORT INTO employees (id, name, department, salary)
VALUES (1, 'Alice', 'Engineering', 95000);
```

<Info>
  For more granular conflict handling based on specific constraints, use the [ON CONFLICT](/sql-reference/statements/upsert) clause (UPSERT), which allows different actions depending on which constraint was violated.
</Info>

## RETURNING Clause

Returns data from the rows that were actually inserted. This is useful for retrieving generated values such as auto-incremented IDs or evaluated defaults.

```sql theme={null}
INSERT INTO employees (name, department, salary)
VALUES ('Eve', 'Engineering', 91000)
RETURNING id, name;
-- Returns: 5|Eve
```

RETURNING accepts any expression that can reference the inserted row's columns.

```sql theme={null}
INSERT INTO employees (name, department, salary)
VALUES ('Frank', 'Design', 78000)
RETURNING id, name, salary * 12 AS annual_salary;
-- Returns: 6|Frank|936000
```

### RETURNING with Multiple Rows

```sql theme={null}
INSERT INTO employees (name, department, salary) VALUES
  ('Grace', 'Engineering', 102000),
  ('Hank', 'Marketing', 68000)
RETURNING id, name, department;
-- Returns one row per inserted row
```

### RETURNING \*

Return all columns of the inserted rows.

```sql theme={null}
INSERT INTO employees (name, department, salary)
VALUES ('Ivy', 'Product', 88000)
RETURNING *;
```

## Examples

### Insert with Subquery Values

```sql theme={null}
INSERT INTO department_stats (department, avg_salary)
SELECT department, AVG(salary)
FROM employees
GROUP BY department
HAVING COUNT(*) >= 3;
```

### Insert with Conflict Handling and RETURNING

```sql theme={null}
INSERT OR REPLACE INTO settings (key, value)
VALUES ('theme', 'dark'), ('locale', 'en-US')
RETURNING key, value;
```

### Archival Pattern

```sql theme={null}
-- Copy rows to archive, then delete originals
INSERT INTO orders_archive
SELECT * FROM orders WHERE order_date < '2023-01-01';

DELETE FROM orders WHERE order_date < '2023-01-01';
```

## See Also

* [INSERT ... ON CONFLICT (UPSERT)](/sql-reference/statements/upsert) for conditional insert-or-update behavior
* [REPLACE](/sql-reference/statements/replace) for shorthand INSERT OR REPLACE
* [SELECT](/sql-reference/statements/select) for the query syntax used with INSERT INTO ... SELECT
* [CREATE TABLE](/sql-reference/statements/create-table) for column definitions and default values


# REINDEX
Source: https://docs.turso.tech/sql-reference/statements/reindex

Rebuild indexes from scratch

# REINDEX

The REINDEX statement deletes and recreates indexes from scratch. It is useful when a collation sequence definition has changed, or to rebuild an index that may have become out of date.

## Syntax

```sql theme={null}
REINDEX;
REINDEX collation-name;
REINDEX [schema-name.]table-name;
REINDEX [schema-name.]index-name;
```

## Description

| Form                     | Description                                                |
| ------------------------ | ---------------------------------------------------------- |
| `REINDEX`                | Rebuild all indexes in all attached databases              |
| `REINDEX collation-name` | Rebuild every index that uses the named collation sequence |
| `REINDEX table-name`     | Rebuild all indexes associated with the named table        |
| `REINDEX index-name`     | Rebuild the named index                                    |

When a single unqualified name is given, Turso resolves it in the order **collation → table → index**: if the name matches a collation sequence, all indexes using that collation are rebuilt; otherwise it is treated as a table or index name.

## Examples

```sql theme={null}
-- Rebuild every index in the database
REINDEX;

-- Rebuild all indexes on a table
REINDEX orders;

-- Rebuild a single index
REINDEX idx_customer;

-- Rebuild all indexes that use a collation
REINDEX NOCASE;
```

## Limitations

REINDEX returns an error in the following cases:

* When the connection is in MVCC mode (`journal_mode=mvcc`).
* On `WITHOUT ROWID` tables.
* For custom index methods that have no backing B-tree, such as FTS and vector indexes.
* When `PRAGMA query_only` is enabled.

## See Also

* [CREATE INDEX](/sql-reference/statements/create-index) for creating indexes
* [DROP INDEX](/sql-reference/statements/drop-index) for removing indexes
* [ANALYZE](/sql-reference/statements/analyze) for collecting index statistics


# REPLACE
Source: https://docs.turso.tech/sql-reference/statements/replace

Insert a row, replacing any existing row that conflicts on a unique constraint

# REPLACE

The `REPLACE` statement inserts a row into a table. If the row violates a `UNIQUE` or `PRIMARY KEY` constraint, the existing conflicting row is deleted before the new row is inserted.

## Syntax

```sql theme={null}
REPLACE INTO table-name [(column-name [, column-name ...])]
VALUES (expression [, expression ...]) [, (expression [, expression ...]) ...]

REPLACE INTO table-name [(column-name [, column-name ...])]
select-statement
```

## Description

`REPLACE` is equivalent to `INSERT OR REPLACE`. When a `REPLACE` inserts a row that would violate a `UNIQUE` or `PRIMARY KEY` constraint, it deletes the existing row that causes the conflict, then inserts the new row. If no constraint violation occurs, `REPLACE` behaves identically to `INSERT`.

### How REPLACE Differs from INSERT

With a plain `INSERT`, a constraint violation causes the statement to fail with an error. With `REPLACE`, the conflicting row is silently deleted and the new row takes its place.

Because the conflicting row is deleted first, `REPLACE` may fire `DELETE` triggers on the old row and `INSERT` triggers on the new row.

<Info>
  `REPLACE` deletes the entire conflicting row and inserts a new one. This means any columns not specified in the `REPLACE` statement receive their default values, not the values from the deleted row. If you want to keep existing column values while updating specific columns, use [INSERT ... ON CONFLICT DO UPDATE](/sql-reference/statements/upsert) instead.
</Info>

### Constraint Behavior

When multiple `UNIQUE` constraints exist on a table, `REPLACE` deletes every row that conflicts with the new row before inserting it. This can result in more than one row being deleted by a single `REPLACE` operation.

If a `NOT NULL` constraint is violated on a column that has no `DEFAULT` value, the `REPLACE` fails with an error.

## Examples

### Basic REPLACE

```sql theme={null}
CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT);

INSERT INTO settings VALUES ('theme', 'light');
SELECT * FROM settings;
-- theme|light

-- The existing row is deleted and a new row is inserted
REPLACE INTO settings VALUES ('theme', 'dark');
SELECT * FROM settings;
-- theme|dark
```

### REPLACE deletes and re-inserts

Unlike `UPDATE`, `REPLACE` removes the old row entirely. Columns not specified in the statement receive their default values:

```sql theme={null}
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE,
    created_at TEXT DEFAULT (datetime('now'))
);

INSERT INTO users VALUES (1, 'Alice', 'alice@example.com', '2025-01-01');

-- This deletes row with id=1 and inserts a completely new row.
-- The created_at column gets its DEFAULT value, not '2025-01-01'.
REPLACE INTO users (id, name, email) VALUES (1, 'Alice', 'alice@new.com');

SELECT id, name, email FROM users;
-- 1|Alice|alice@new.com
```

### Multi-row REPLACE

```sql theme={null}
CREATE TABLE inventory (sku TEXT PRIMARY KEY, quantity INTEGER);
INSERT INTO inventory VALUES ('A001', 10);
INSERT INTO inventory VALUES ('B002', 20);

REPLACE INTO inventory VALUES
    ('A001', 15),
    ('B002', 25),
    ('C003', 30);

SELECT * FROM inventory;
-- A001|15
-- B002|25
-- C003|30
```

## See Also

* [INSERT](/sql-reference/statements/insert) for adding rows without conflict resolution
* [INSERT ... ON CONFLICT](/sql-reference/statements/upsert) for conditional insert-or-update with control over which columns are updated
* [UPDATE](/sql-reference/statements/update) for modifying existing rows in place


# SELECT
Source: https://docs.turso.tech/sql-reference/statements/select

Query rows from one or more tables using filtering, joining, grouping, sorting, and set operations

# SELECT

Retrieves rows from one or more tables. SELECT is the primary way to read data in Turso and supports filtering, joining, aggregation, sorting, subqueries, and set operations.

## Syntax

```sql theme={null}
[WITH cte_name AS (select_statement) [, ...]]
SELECT [DISTINCT] result_column [, ...]
  [FROM table_or_subquery [join_clause ...]]
  [WHERE expression]
  [GROUP BY expression [, ...]]
  [HAVING expression]
  [WINDOW window_name AS (window_definition) [, ...]]
  [compound_operator select_statement]
  [ORDER BY ordering_term [, ...]]
  [LIMIT expression [OFFSET expression]]
```

### Parameters

| Parameter           | Type                          | Description                                                   |
| ------------------- | ----------------------------- | ------------------------------------------------------------- |
| `result_column`     | expression, `*`, or `table.*` | Columns or expressions to return. Use `*` for all columns     |
| `table_or_subquery` | identifier or subquery        | Table name, aliased table, or parenthesized SELECT            |
| `expression`        | expression                    | Any valid SQL expression                                      |
| `ordering_term`     | expression + direction        | Expression followed by optional ASC/DESC and NULLS FIRST/LAST |
| `compound_operator` | keyword                       | UNION, UNION ALL, INTERSECT, or EXCEPT                        |
| `cte_name`          | identifier                    | Name for a Common Table Expression                            |
| `window_name`       | identifier                    | Name for a reusable window definition                         |

## Basic Queries

### Selecting Columns

```sql theme={null}
-- All columns
SELECT * FROM employees;

-- Specific columns
SELECT name, department FROM employees;

-- Expressions and aliases
SELECT name, salary * 12 AS annual_salary FROM employees;
```

### Column and Table Aliases

Use `AS` to assign aliases to columns or tables. The `AS` keyword is optional for column aliases.

```sql theme={null}
SELECT e.name, e.salary * 12 annual_pay
FROM employees AS e;
```

## FROM Clause

The FROM clause specifies the source tables for the query. It accepts table names, aliased tables, subqueries, and join expressions.

```sql theme={null}
-- Single table
SELECT * FROM employees;

-- Subquery as table source
SELECT * FROM (SELECT name, salary FROM employees WHERE salary > 50000) AS high_earners;
```

## WHERE Clause

Filters rows based on a condition. Only rows where the expression evaluates to true are included in the result.

```sql theme={null}
SELECT * FROM employees WHERE department = 'Engineering';

SELECT * FROM orders WHERE total > 100 AND status != 'cancelled';
```

### Comparison Operators

| Operator               | Description                             |
| ---------------------- | --------------------------------------- |
| `=`                    | Equal                                   |
| `!=` or `<>`           | Not equal                               |
| `<`                    | Less than                               |
| `>`                    | Greater than                            |
| `<=`                   | Less than or equal                      |
| `>=`                   | Greater than or equal                   |
| `IS`                   | Equal (NULL-safe)                       |
| `IS NOT`               | Not equal (NULL-safe)                   |
| `IS DISTINCT FROM`     | Not identical (NULL-safe, SQL standard) |
| `IS NOT DISTINCT FROM` | Identical (NULL-safe, SQL standard)     |

### Logical Operators

Combine conditions with `AND`, `OR`, and `NOT`.

```sql theme={null}
SELECT * FROM products
WHERE (category = 'Electronics' OR category = 'Books')
  AND price < 50
  AND NOT discontinued;
```

### Pattern Matching

```sql theme={null}
-- LIKE: case-insensitive pattern matching (% = any chars, _ = one char)
SELECT * FROM employees WHERE name LIKE 'J%';

-- GLOB: case-sensitive pattern matching (* = any chars, ? = one char)
SELECT * FROM files WHERE path GLOB '*.txt';

-- REGEXP: regular expression matching
SELECT * FROM logs WHERE message REGEXP '^ERROR:';
```

### Range and Membership Tests

```sql theme={null}
-- BETWEEN (inclusive on both ends)
SELECT * FROM orders WHERE total BETWEEN 100 AND 500;

-- IN with a list
SELECT * FROM employees WHERE department IN ('Engineering', 'Design', 'Product');

-- IN with a subquery
SELECT * FROM employees WHERE department_id IN (SELECT id FROM departments WHERE active = 1);
```

### NULL Tests

```sql theme={null}
SELECT * FROM employees WHERE manager_id IS NULL;

SELECT * FROM employees WHERE phone IS NOT NULL;
```

### CASE Expressions

```sql theme={null}
SELECT name,
  CASE
    WHEN salary >= 100000 THEN 'Senior'
    WHEN salary >= 60000 THEN 'Mid'
    ELSE 'Junior'
  END AS level
FROM employees;

-- Simple CASE form
SELECT name,
  CASE department
    WHEN 'Engineering' THEN 'Eng'
    WHEN 'Marketing' THEN 'Mkt'
    ELSE 'Other'
  END AS dept_code
FROM employees;
```

## JOIN Clause

Combines rows from two or more tables based on a related column.

### Supported Join Types

| Join Type                        | Description                                                                          |
| -------------------------------- | ------------------------------------------------------------------------------------ |
| `INNER JOIN` or `JOIN`           | Returns rows that have matching values in both tables                                |
| `LEFT JOIN` or `LEFT OUTER JOIN` | Returns all rows from the left table, with NULLs for unmatched right-side columns    |
| `FULL OUTER JOIN`                | Returns all rows from both tables, with NULLs where there is no match on either side |
| `NATURAL JOIN`                   | Joins on all columns with the same name in both tables                               |
| `JOIN ... USING (column)`        | Joins on the specified column that exists in both tables                             |

### INNER JOIN

Returns only rows where the join condition is satisfied in both tables.

```sql theme={null}
SELECT e.name, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;
```

### LEFT OUTER JOIN

Returns all rows from the left table. When no matching row exists in the right table, the right-side columns contain NULL.

```sql theme={null}
SELECT e.name, m.name AS manager_name
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
```

### FULL OUTER JOIN

Returns all rows from both tables. When a row in either table has no match in the other table, the missing side's columns contain NULL.

```sql theme={null}
SELECT e.name, d.department_name
FROM employees e
FULL OUTER JOIN departments d ON e.department_id = d.id;
-- Returns all employees (even without a department)
-- and all departments (even without employees)
```

### NATURAL JOIN

Automatically joins on all columns with identical names in both tables. Equivalent to JOIN ... USING with every shared column name.

```sql theme={null}
SELECT * FROM orders NATURAL JOIN customers;
-- Joins on every column name shared between orders and customers
```

### JOIN ... USING

Joins on the specified column that must exist in both tables. The shared column appears only once in the result.

```sql theme={null}
SELECT * FROM orders JOIN customers USING (customer_id);
```

### Multi-Table Joins

```sql theme={null}
SELECT o.id, c.name, p.product_name, oi.quantity
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.order_date > '2024-01-01';
```

## GROUP BY and HAVING

### GROUP BY

Groups rows that share the same values in the specified columns. Typically used with aggregate functions.

```sql theme={null}
SELECT department, COUNT(*) AS employee_count, AVG(salary) AS avg_salary
FROM employees
GROUP BY department;
```

Aggregate functions that can be used with GROUP BY:

| Function                              | Description                                                |
| ------------------------------------- | ---------------------------------------------------------- |
| `COUNT(*)`                            | Number of rows in the group                                |
| `COUNT(expression)`                   | Number of non-NULL values                                  |
| `SUM(expression)`                     | Sum of non-NULL values                                     |
| `AVG(expression)`                     | Average of non-NULL values                                 |
| `MIN(expression)`                     | Minimum value                                              |
| `MAX(expression)`                     | Maximum value                                              |
| `TOTAL(expression)`                   | Sum as a REAL (returns 0.0 instead of NULL for empty sets) |
| `GROUP_CONCAT(expression, separator)` | Concatenation of values                                    |
| `STRING_AGG(expression, separator)`   | Alias for GROUP\_CONCAT                                    |

### HAVING

Filters groups after aggregation. WHERE filters individual rows before grouping; HAVING filters groups after.

```sql theme={null}
SELECT department, COUNT(*) AS headcount
FROM employees
GROUP BY department
HAVING COUNT(*) >= 5;
```

### GROUP BY with Expressions

```sql theme={null}
SELECT
  CASE WHEN salary >= 80000 THEN 'High' ELSE 'Standard' END AS band,
  COUNT(*) AS count
FROM employees
GROUP BY CASE WHEN salary >= 80000 THEN 'High' ELSE 'Standard' END;
```

## DISTINCT

Removes duplicate rows from the result set.

```sql theme={null}
SELECT DISTINCT department FROM employees;

SELECT DISTINCT department, title FROM employees;
```

<Info>
  DISTINCT applies to the entire result row, not to a single column. Two rows are considered duplicates only if every column value is identical.
</Info>

## ORDER BY

Sorts the result set. Without ORDER BY, the row order is unspecified.

```sql theme={null}
SELECT * FROM employees ORDER BY salary DESC;

-- Multiple sort keys
SELECT * FROM employees ORDER BY department ASC, salary DESC;

-- Ordering by column position
SELECT name, salary FROM employees ORDER BY 2 DESC;

-- Ordering by alias
SELECT name, salary * 12 AS annual FROM employees ORDER BY annual DESC;
```

### NULLS FIRST / NULLS LAST

Controls where NULL values appear in the sorted result.

```sql theme={null}
-- NULLs at the end (default for ASC is NULLS LAST, for DESC is NULLS FIRST)
SELECT * FROM employees ORDER BY manager_id ASC NULLS LAST;

-- NULLs at the beginning
SELECT * FROM employees ORDER BY manager_id DESC NULLS FIRST;
```

| Direction | Default NULL Placement |
| --------- | ---------------------- |
| ASC       | NULLS LAST             |
| DESC      | NULLS FIRST            |

## LIMIT and OFFSET

Restricts the number of rows returned and optionally skips a number of rows.

```sql theme={null}
-- Return at most 10 rows
SELECT * FROM employees ORDER BY salary DESC LIMIT 10;

-- Skip 20 rows, then return 10
SELECT * FROM employees ORDER BY salary DESC LIMIT 10 OFFSET 20;
```

<Info>
  Always use ORDER BY with LIMIT and OFFSET. Without ORDER BY, the set of rows skipped or returned is arbitrary.
</Info>

## Subqueries

A subquery is a SELECT statement nested inside another query.

### Scalar Subqueries

Returns a single value. Can be used anywhere an expression is expected.

```sql theme={null}
SELECT name, salary,
  salary - (SELECT AVG(salary) FROM employees) AS diff_from_avg
FROM employees;
```

### Subqueries with IN

Tests whether a value matches any row returned by the subquery.

```sql theme={null}
SELECT * FROM products
WHERE category_id IN (SELECT id FROM categories WHERE active = 1);

SELECT * FROM products
WHERE category_id NOT IN (SELECT id FROM categories WHERE discontinued = 1);
```

### Subqueries with EXISTS

Tests whether the subquery returns at least one row. The actual values are ignored.

```sql theme={null}
SELECT * FROM customers c
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.customer_id = c.id AND o.total > 1000
);
```

### Subqueries in FROM

A subquery in the FROM clause acts as a derived table and must have an alias.

```sql theme={null}
SELECT dept, avg_salary
FROM (
  SELECT department AS dept, AVG(salary) AS avg_salary
  FROM employees
  GROUP BY department
) AS dept_stats
WHERE avg_salary > 70000;
```

## Common Table Expressions (CTE)

A WITH clause defines one or more temporary named result sets that exist for the duration of the query.

```sql theme={null}
WITH regional_sales AS (
  SELECT region, SUM(amount) AS total_sales
  FROM orders
  GROUP BY region
)
SELECT region, total_sales
FROM regional_sales
WHERE total_sales > 100000
ORDER BY total_sales DESC;
```

### Multiple CTEs

```sql theme={null}
WITH
  active_customers AS (
    SELECT * FROM customers WHERE active = 1
  ),
  recent_orders AS (
    SELECT * FROM orders WHERE order_date > '2024-01-01'
  )
SELECT ac.name, COUNT(ro.id) AS order_count
FROM active_customers ac
JOIN recent_orders ro ON ac.id = ro.customer_id
GROUP BY ac.name;
```

<Info>
  CTEs in Turso are SELECT-only. RECURSIVE CTEs and the MATERIALIZED/NOT MATERIALIZED hints are not supported.
</Info>

## Window Functions

A window function performs a calculation across a set of rows related to the current row, without collapsing them into a single output row.

```sql theme={null}
SELECT name, department, salary,
  SUM(salary) OVER (PARTITION BY department ORDER BY salary) AS running_total
FROM employees;
```

### Named Windows

Use the WINDOW clause to define a reusable window definition.

```sql theme={null}
SELECT name, department, salary,
  SUM(salary) OVER w AS dept_running_total,
  COUNT(*) OVER w AS dept_running_count
FROM employees
WINDOW w AS (PARTITION BY department ORDER BY salary)
ORDER BY department, salary;
```

### Window Clause Syntax

```sql theme={null}
function_name() OVER (
  [PARTITION BY expression [, ...]]
  [ORDER BY expression [ASC | DESC] [, ...]]
)
```

| Component      | Description                                                                   |
| -------------- | ----------------------------------------------------------------------------- |
| `PARTITION BY` | Divides rows into groups (partitions). The function resets for each partition |
| `ORDER BY`     | Defines the order of rows within each partition                               |

Standard aggregate functions (`SUM`, `AVG`, `COUNT`, `MIN`, `MAX`, `TOTAL`, `GROUP_CONCAT`) can all be used as window functions.

<Info>
  Window functions in Turso use the default frame definition (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW). Custom frame specifications (ROWS, RANGE, GROUPS with explicit bounds) are not supported. The `row_number()` ranking function and the `FILTER (WHERE ...)` clause are supported; other ranking and navigation functions (`rank`, `dense_rank`, `lag`, `lead`, and so on) are not yet available.
</Info>

## Set Operations

Combine the results of two or more SELECT statements.

| Operator    | Description                                                       |
| ----------- | ----------------------------------------------------------------- |
| `UNION`     | Combines results and removes duplicates                           |
| `UNION ALL` | Combines results and keeps duplicates                             |
| `INTERSECT` | Returns only rows present in both result sets                     |
| `EXCEPT`    | Returns rows from the first result set that are not in the second |

All set operations require the same number of columns in each SELECT, with compatible types.

```sql theme={null}
-- Customers who are also employees
SELECT name FROM customers
INTERSECT
SELECT name FROM employees;

-- All people from both tables, no duplicates
SELECT name, email FROM customers
UNION
SELECT name, email FROM employees;

-- Customers who are not employees
SELECT name FROM customers
EXCEPT
SELECT name FROM employees;
```

### UNION ALL

When duplicates are acceptable, UNION ALL is faster because it skips the deduplication step.

```sql theme={null}
SELECT id, 'order' AS source FROM orders
UNION ALL
SELECT id, 'return' AS source FROM returns;
```

### Set Operations with ORDER BY

ORDER BY applies to the entire combined result set and must appear after the last SELECT.

```sql theme={null}
SELECT name FROM customers
UNION
SELECT name FROM employees
ORDER BY name;
```

## Examples

### Paginated Report with Aggregation

```sql theme={null}
SELECT
  d.department_name,
  COUNT(e.id) AS headcount,
  ROUND(AVG(e.salary), 2) AS avg_salary,
  MIN(e.hire_date) AS earliest_hire
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
GROUP BY d.department_name
HAVING COUNT(e.id) > 0
ORDER BY avg_salary DESC
LIMIT 10 OFFSET 0;
```

### CTE with Filtered Join

```sql theme={null}
WITH high_value_orders AS (
  SELECT customer_id, COUNT(*) AS order_count, SUM(total) AS total_spent
  FROM orders
  WHERE order_date >= '2024-01-01'
  GROUP BY customer_id
  HAVING SUM(total) > 5000
)
SELECT c.name, h.order_count, h.total_spent
FROM customers c
JOIN high_value_orders h ON c.id = h.customer_id
ORDER BY h.total_spent DESC;
```

### Subquery with EXISTS and LEFT JOIN

```sql theme={null}
SELECT p.product_name, p.price, c.category_name
FROM products p
LEFT JOIN categories c ON p.category_id = c.id
WHERE EXISTS (
  SELECT 1 FROM order_items oi WHERE oi.product_id = p.id
)
ORDER BY p.price DESC
LIMIT 20;
```

## See Also

* [INSERT](/sql-reference/statements/insert) for adding rows to a table
* [UPDATE](/sql-reference/statements/update) for modifying existing rows
* [DELETE](/sql-reference/statements/delete) for removing rows
* [CREATE VIEW](/sql-reference/statements/create-view) for saving a query as a view
* [Data Types](/sql-reference/data-types) for storage classes and type affinity


# Transactions
Source: https://docs.turso.tech/sql-reference/statements/transactions

Start, commit, and roll back transactions to group operations atomically

# Transactions

Transactions group multiple SQL statements into a single atomic unit of work. Either all statements in a transaction succeed and are committed, or the entire transaction is rolled back and no changes are applied. Turso supports explicit transactions with BEGIN, COMMIT, and ROLLBACK, and also runs each standalone statement in an implicit transaction (autocommit mode).

## Syntax

```sql theme={null}
BEGIN [DEFERRED | IMMEDIATE | EXCLUSIVE] [TRANSACTION];

COMMIT [TRANSACTION];
END [TRANSACTION];

ROLLBACK [TRANSACTION] [TO [SAVEPOINT] savepoint-name];

SAVEPOINT savepoint-name;
RELEASE [SAVEPOINT] savepoint-name;
```

## BEGIN

The BEGIN statement starts a new explicit transaction. While a transaction is open, all subsequent statements are part of that transaction until a COMMIT or ROLLBACK is issued.

### Transaction Types

| Type        | Description                                                                                                                                               |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DEFERRED`  | Default. No locks are acquired until the first read or write operation. A read operation acquires a shared lock; a write operation acquires a write lock. |
| `IMMEDIATE` | Acquires a write lock immediately when BEGIN is executed. Other connections can still read but cannot write until the transaction completes.              |
| `EXCLUSIVE` | Same behavior as IMMEDIATE in Turso. Turso always uses WAL mode, where EXCLUSIVE and IMMEDIATE are equivalent.                                            |

If no type is specified, the transaction defaults to `DEFERRED`.

```sql theme={null}
-- Start a deferred transaction (default)
BEGIN;

-- Start an immediate transaction
BEGIN IMMEDIATE;

-- The TRANSACTION keyword is optional
BEGIN IMMEDIATE TRANSACTION;
```

## COMMIT / END

The COMMIT statement (or its alias END) finalizes the transaction and writes all changes to the database. Once committed, changes are durable and visible to other connections.

```sql theme={null}
BEGIN;
INSERT INTO accounts (name, balance) VALUES ('Alice', 1000);
INSERT INTO accounts (name, balance) VALUES ('Bob', 500);
COMMIT;
```

COMMIT and END are interchangeable.

```sql theme={null}
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE name = 'Alice';
END;
```

## ROLLBACK

The ROLLBACK statement aborts the current transaction and reverts all changes made since BEGIN. The database is left in the state it was in before the transaction started.

```sql theme={null}
BEGIN;
DELETE FROM accounts WHERE balance < 0;
-- Changed our mind, undo everything
ROLLBACK;
```

## Implicit Transactions

When no explicit transaction is active, Turso wraps each individual statement in an implicit transaction. This is called autocommit mode. Each statement is automatically committed after it executes successfully, or automatically rolled back if it fails.

```sql theme={null}
-- Each of these runs in its own implicit transaction
INSERT INTO logs (msg) VALUES ('first');
INSERT INTO logs (msg) VALUES ('second');
-- If the second INSERT fails, the first INSERT is still committed
```

## Transaction Lifecycle

A typical transaction lifecycle:

```sql theme={null}
-- 1. Begin the transaction
BEGIN;

-- 2. Perform operations
UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 42;
INSERT INTO orders (product_id, customer_id) VALUES (42, 7);

-- 3a. Commit if everything succeeded
COMMIT;

-- OR 3b. Rollback if something went wrong
ROLLBACK;
```

Each connection can have at most one active top-level transaction at a time. For finer-grained rollback within a transaction, use savepoints.

## Savepoints

Savepoints are named markers within a transaction that let you roll back part of the work without aborting the whole transaction. They can be nested.

| Statement                                    | Description                                                                                                           |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `SAVEPOINT name`                             | Establishes a new savepoint with the given name                                                                       |
| `RELEASE [SAVEPOINT] name`                   | Removes the savepoint (and any savepoints created after it), keeping its changes as part of the enclosing transaction |
| `ROLLBACK [TRANSACTION] TO [SAVEPOINT] name` | Undoes all changes made since the savepoint, but keeps the savepoint and the transaction open                         |

```sql theme={null}
BEGIN;
INSERT INTO accounts (name, balance) VALUES ('Alice', 1000);

SAVEPOINT before_bonus;
UPDATE accounts SET balance = balance + 500 WHERE name = 'Alice';

-- Undo just the bonus, keep the insert
ROLLBACK TO before_bonus;

-- Discard the savepoint and keep the rest
RELEASE before_bonus;
COMMIT;
```

<Info>
  While a write statement on the same connection is still in progress, `SAVEPOINT`, `RELEASE`, and `ROLLBACK TO` may return `SQLITE_BUSY`.
</Info>

## BEGIN CONCURRENT

<Info>
  **Turso Extension**: BEGIN CONCURRENT is a Turso-specific extension not available in standard SQLite. It requires MVCC mode.
</Info>

The BEGIN CONCURRENT statement starts an optimistic concurrent write transaction that allows multiple connections to write simultaneously.

### Syntax

```sql theme={null}
BEGIN CONCURRENT [TRANSACTION];
```

### Requirements

BEGIN CONCURRENT requires MVCC mode. Enable it by setting the journal mode before opening any transactions:

```sql theme={null}
PRAGMA journal_mode = mvcc;
```

### How It Works

A concurrent transaction operates on a snapshot of the database taken at the time of BEGIN CONCURRENT. Each connection reads from its own snapshot and writes independently.

When COMMIT is issued, Turso checks whether any other transaction has modified the same rows since the snapshot was taken:

* If there is no conflict, the transaction commits successfully.
* If a write-write conflict is detected, the COMMIT fails and the transaction must be rolled back and retried.

This provides **snapshot isolation**: each transaction sees a consistent view of the database as of its start time, without blocking other writers.

### Example

```sql theme={null}
-- Connection 1
PRAGMA journal_mode = mvcc;
BEGIN CONCURRENT;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- Connection 2 (running simultaneously)
PRAGMA journal_mode = mvcc;
BEGIN CONCURRENT;
UPDATE accounts SET balance = balance + 50 WHERE id = 2;
COMMIT;
-- Both succeed because they modified different rows
```

If both connections modify the same row, the second COMMIT fails:

```sql theme={null}
-- Connection 1
BEGIN CONCURRENT;
UPDATE accounts SET balance = 900 WHERE id = 1;
COMMIT;  -- succeeds

-- Connection 2 (started before Connection 1 committed)
BEGIN CONCURRENT;
UPDATE accounts SET balance = 800 WHERE id = 1;
COMMIT;  -- fails: write-write conflict detected
ROLLBACK;
-- Retry the transaction from BEGIN CONCURRENT
```

## See Also

* [INSERT](/sql-reference/statements/insert) for inserting rows
* [UPDATE](/sql-reference/statements/update) for updating rows
* [DELETE](/sql-reference/statements/delete) for deleting rows
* [Compatibility](/sql-reference/compatibility) for differences from SQLite


# UPDATE
Source: https://docs.turso.tech/sql-reference/statements/update

Modify existing rows in a table

# UPDATE

The `UPDATE` statement modifies the values of columns in existing rows of a table.

## Syntax

```sql theme={null}
UPDATE [OR conflict-algorithm] table-name
SET column-name = expression [, column-name = expression ...]
[FROM from-clause]
[WHERE expression]
[RETURNING result-column [, result-column ...]]
[ORDER BY sort-expression [, sort-expression ...]]
[LIMIT expression [OFFSET expression]]
```

## Description

`UPDATE` changes column values in rows that match the `WHERE` condition. If no `WHERE` clause is provided, all rows in the table are updated.

### Conflict Handling

The optional `OR` clause specifies how to handle constraint violations during the update. When omitted, the default behavior is `ABORT`.

| Algorithm  | Behavior                                                                                                                                                    |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ABORT`    | Roll back the current statement and return an error. The transaction remains active. This is the default.                                                   |
| `ROLLBACK` | Roll back the entire transaction and return an error.                                                                                                       |
| `FAIL`     | Stop at the first constraint violation and return an error, but keep changes made by earlier rows in this statement.                                        |
| `IGNORE`   | Skip the row that caused the constraint violation and continue processing remaining rows.                                                                   |
| `REPLACE`  | Delete the conflicting row before updating the current row. If a NOT NULL constraint fails and there is no DEFAULT value, the statement behaves as `ABORT`. |

```sql theme={null}
UPDATE OR IGNORE employees
SET department = 'Engineering'
WHERE department = 'IT';
```

### SET Clause

The `SET` clause assigns new values to one or more columns. Each assignment consists of a column name, an equals sign, and an expression. The expression can reference other columns of the same row, and it sees the original (pre-update) values of all columns.

```sql theme={null}
UPDATE products
SET price = price * 1.10,
    updated_at = datetime('now');
```

Multiple columns can also be set using a parenthesized column list:

```sql theme={null}
UPDATE products
SET (price, updated_at) = (price * 1.10, datetime('now'));
```

### FROM Clause

The `FROM` clause allows the `UPDATE` to reference other tables. Columns from the joined tables can be used in `SET` expressions and the `WHERE` clause. This is useful for updating a table based on matching rows from another table.

```sql theme={null}
UPDATE inventory
SET quantity = inventory.quantity - order_items.qty
FROM order_items
WHERE inventory.product_id = order_items.product_id
  AND order_items.order_id = 1001;
```

When the target table also appears in the `FROM` clause, it refers to a second instance of that table that can be used for self-joins.

### WHERE Clause

The `WHERE` clause restricts which rows are updated. Only rows where the expression evaluates to true are modified. If the `WHERE` clause is omitted, every row in the table is updated.

### RETURNING Clause

The `RETURNING` clause causes the `UPDATE` statement to return the new values of each modified row. The result columns can be expressions referencing the updated column values.

```sql theme={null}
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 42
RETURNING account_id, balance;
-- Returns: 42|900
```

### ORDER BY and LIMIT

The `ORDER BY` and `LIMIT` clauses restrict which rows are updated. `ORDER BY` determines the order in which rows are considered, and `LIMIT` caps the number of rows that are actually modified. `ORDER BY` requires `LIMIT` to be present.

```sql theme={null}
UPDATE logs
SET archived = 1
ORDER BY created_at ASC
LIMIT 1000;
```

`LIMIT` accepts an optional `OFFSET` to skip a number of rows before applying the limit:

```sql theme={null}
UPDATE logs
SET archived = 1
ORDER BY created_at ASC
LIMIT 1000 OFFSET 500;
```

## Examples

### Update rows matching a condition

```sql theme={null}
CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary REAL, department TEXT);
INSERT INTO employees VALUES (1, 'Alice', 90000, 'Engineering');
INSERT INTO employees VALUES (2, 'Bob', 75000, 'Marketing');
INSERT INTO employees VALUES (3, 'Carol', 85000, 'Engineering');

UPDATE employees
SET salary = salary * 1.05
WHERE department = 'Engineering';

SELECT name, salary FROM employees WHERE department = 'Engineering';
-- Alice|94500.0
-- Carol|89250.0
```

### Update using a join with FROM

```sql theme={null}
CREATE TABLE departments (name TEXT PRIMARY KEY, budget REAL);
INSERT INTO departments VALUES ('Engineering', 500000);
INSERT INTO departments VALUES ('Marketing', 200000);

CREATE TABLE employees2 (id INTEGER PRIMARY KEY, name TEXT, bonus REAL, dept TEXT);
INSERT INTO employees2 VALUES (1, 'Alice', 0, 'Engineering');
INSERT INTO employees2 VALUES (2, 'Bob', 0, 'Marketing');

UPDATE employees2
SET bonus = departments.budget * 0.01
FROM departments
WHERE employees2.dept = departments.name;

SELECT name, bonus FROM employees2;
-- Alice|5000.0
-- Bob|2000.0
```

### Update with RETURNING

```sql theme={null}
CREATE TABLE counters (name TEXT PRIMARY KEY, value INTEGER);
INSERT INTO counters VALUES ('page_views', 41);

UPDATE counters
SET value = value + 1
WHERE name = 'page_views'
RETURNING name, value;
-- page_views|42
```

## See Also

* [INSERT](/sql-reference/statements/insert) for adding new rows
* [DELETE](/sql-reference/statements/delete) for removing rows
* [INSERT ... ON CONFLICT](/sql-reference/statements/upsert) for conditional insert-or-update operations
* [SELECT](/sql-reference/statements/select) for querying data


# INSERT ... ON CONFLICT (Upsert)
Source: https://docs.turso.tech/sql-reference/statements/upsert

Insert rows with automatic conflict resolution using DO NOTHING or DO UPDATE

# INSERT ... ON CONFLICT (Upsert)

The upsert clause is an extension to `INSERT` that specifies what to do when the insert would violate a `UNIQUE` or `PRIMARY KEY` constraint. Instead of failing with an error, the statement can either skip the conflicting row or update it.

## Syntax

```sql theme={null}
INSERT INTO table-name [(column-name [, column-name ...])]
VALUES (expression [, expression ...]) [, ...]
ON CONFLICT [(conflict-target)] DO NOTHING
[ON CONFLICT [(conflict-target)] DO NOTHING | DO UPDATE ...]

INSERT INTO table-name [(column-name [, column-name ...])]
VALUES (expression [, expression ...]) [, ...]
ON CONFLICT [(conflict-target)] DO UPDATE
    SET column-name = expression [, column-name = expression ...]
    [WHERE expression]
[ON CONFLICT [(conflict-target)] DO NOTHING | DO UPDATE ...]
```

Where `conflict-target` is:

```sql theme={null}
(column-name [, column-name ...]) [WHERE expression]
```

## Description

An upsert clause is added to the end of an `INSERT` statement to handle constraint violations gracefully. There are two actions: `DO NOTHING` silently skips the conflicting row, and `DO UPDATE` modifies the existing row in place.

### DO NOTHING

`DO NOTHING` causes the `INSERT` to silently skip any row that would violate the specified constraint. No error is raised and the existing row is left unchanged.

When a conflict target is specified, only violations on that particular constraint trigger the skip. When no conflict target is specified, any constraint violation triggers the skip.

```sql theme={null}
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON CONFLICT DO NOTHING;
```

### DO UPDATE

`DO UPDATE` causes the `INSERT` to update the existing row when a constraint violation occurs. The `SET` clause specifies which columns to update and what values to assign. A conflict target is required for `DO UPDATE`.

```sql theme={null}
INSERT INTO counters (name, value) VALUES ('hits', 1)
ON CONFLICT (name) DO UPDATE
    SET value = counters.value + 1;
```

### Conflict Target

The conflict target identifies which `UNIQUE` or `PRIMARY KEY` constraint the clause applies to. It is a parenthesized list of column names that must exactly match the columns of an existing unique constraint on the table.

For partial unique indexes (indexes with a `WHERE` clause), the conflict target must also include a `WHERE` clause that matches the index's condition.

```sql theme={null}
-- Given: CREATE UNIQUE INDEX idx ON t(a, b) WHERE c > 0
INSERT INTO t VALUES (1, 2, 3)
ON CONFLICT (a, b) WHERE c > 0 DO NOTHING;
```

### The `excluded` Table

Inside the `SET` and `WHERE` clauses of `DO UPDATE`, the special `excluded` table contains the values that were proposed for insertion. This allows the update expression to reference both the existing row (using the table name) and the proposed new row (using `excluded`).

| Reference           | Meaning                                                           |
| ------------------- | ----------------------------------------------------------------- |
| `table-name.column` | The current value of the column in the existing row               |
| `excluded.column`   | The value that was proposed for insertion but caused the conflict |

```sql theme={null}
INSERT INTO products (sku, name, price) VALUES ('A001', 'Widget', 12.99)
ON CONFLICT (sku) DO UPDATE
    SET name = excluded.name,
        price = excluded.price;
```

### WHERE Clause on DO UPDATE

The `DO UPDATE` clause can include a `WHERE` expression that further restricts when the update occurs. If the `WHERE` condition evaluates to false for a conflicting row, that row is left unchanged (the insert is silently skipped, as with `DO NOTHING`).

```sql theme={null}
INSERT INTO products (sku, name, price) VALUES ('A001', 'Widget', 8.99)
ON CONFLICT (sku) DO UPDATE
    SET price = excluded.price
    WHERE excluded.price < products.price;
```

In this example, the price is only updated if the new price is lower than the existing price.

### Multiple ON CONFLICT Clauses

An `INSERT` statement can have multiple `ON CONFLICT` clauses, each targeting a different constraint. The clauses are evaluated in order. When a constraint violation occurs, the first matching clause is used.

```sql theme={null}
INSERT INTO users (id, email, name) VALUES (1, 'alice@example.com', 'Alice')
ON CONFLICT (id) DO UPDATE SET name = excluded.name, email = excluded.email
ON CONFLICT (email) DO NOTHING;
```

<Info>
  At most one `ON CONFLICT` clause can omit the conflict target. A clause without a conflict target acts as a catch-all and must appear last. Only `DO NOTHING` is allowed without a conflict target.
</Info>

## Examples

### Upsert a counter

```sql theme={null}
CREATE TABLE counters (name TEXT PRIMARY KEY, value INTEGER NOT NULL DEFAULT 0);

-- First insert creates the row
INSERT INTO counters (name, value) VALUES ('logins', 1)
ON CONFLICT (name) DO UPDATE SET value = counters.value + excluded.value;

SELECT * FROM counters;
-- logins|1

-- Subsequent inserts increment the counter
INSERT INTO counters (name, value) VALUES ('logins', 1)
ON CONFLICT (name) DO UPDATE SET value = counters.value + excluded.value;

SELECT * FROM counters;
-- logins|2
```

### Bulk insert, skipping duplicates

```sql theme={null}
CREATE TABLE tags (id INTEGER PRIMARY KEY, label TEXT UNIQUE);
INSERT INTO tags VALUES (1, 'rust');
INSERT INTO tags VALUES (2, 'sql');

-- Insert several tags, skipping any that already exist
INSERT INTO tags VALUES (2, 'sql'), (3, 'wasm'), (4, 'database')
ON CONFLICT DO NOTHING;

SELECT * FROM tags;
-- 1|rust
-- 2|sql
-- 3|wasm
-- 4|database
```

### Conditional update with excluded

```sql theme={null}
CREATE TABLE prices (
    product_id INTEGER PRIMARY KEY,
    price REAL NOT NULL,
    updated_at TEXT NOT NULL
);
INSERT INTO prices VALUES (1, 29.99, '2025-01-01');

-- Only update the price if the new timestamp is more recent
INSERT INTO prices (product_id, price, updated_at) VALUES (1, 24.99, '2025-06-15')
ON CONFLICT (product_id) DO UPDATE
    SET price = excluded.price,
        updated_at = excluded.updated_at
    WHERE excluded.updated_at > prices.updated_at;

SELECT * FROM prices;
-- 1|24.99|2025-06-15
```

## See Also

* [INSERT](/sql-reference/statements/insert) for standard insert operations
* [REPLACE](/sql-reference/statements/replace) for delete-and-reinsert conflict resolution
* [UPDATE](/sql-reference/statements/update) for modifying existing rows directly
* [CREATE TABLE](/sql-reference/statements/create-table) for defining `UNIQUE` and `PRIMARY KEY` constraints


# VACUUM
Source: https://docs.turso.tech/sql-reference/statements/vacuum

Rebuild the database file to reclaim unused space and defragment storage

# VACUUM

The VACUUM statement rebuilds the database file to reclaim unused space, defragment tables and indexes, and reduce the file size. Two forms are supported: `VACUUM` rebuilds the current database in place, while `VACUUM INTO` writes a compacted copy to a new file without modifying the source.

## Syntax

```sql theme={null}
VACUUM [schema-name];
VACUUM [schema-name] INTO filename;
```

| Parameter     | Description                                                                                                                                                                            |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `schema-name` | The database to vacuum. For in-place `VACUUM` only `main` is supported. For `VACUUM INTO` any attached schema is accepted; `temp` is a no-op and produces no file. Defaults to `main`. |
| `filename`    | A string literal giving the destination file path for `VACUUM INTO`. Bind parameters are not accepted.                                                                                 |

## Common Requirements

These rules apply to both `VACUUM` and `VACUUM INTO`:

* The connection must be in autocommit mode — neither form can run inside an explicit `BEGIN` transaction.
* No other statement may be active on the same connection.
* The connection must not be in `query_only` mode.
* The source database must not have `auto_vacuum = incremental`. Incremental autovacuum is not supported.

## VACUUM

<Info>
  In-place `VACUUM` is experimental and must be [enabled before use](/sql-reference/experimental-features). `VACUUM INTO` does not require the flag and is always available.
</Info>

In-place `VACUUM` rebuilds the main database by writing a compacted image into an internal temp database and then copying those pages back over the original file. When it completes, unused pages have been released and all storage-backed objects have been recreated.

### Effect

* Unused pages from deleted rows and dropped objects are removed, shrinking the file.
* All storage-backed tables are recreated and their rows reinserted, which rebuilds the associated indexes.
* `sqlite_sequence` counters used by `AUTOINCREMENT` columns are preserved.
* The schema cookie is bumped so that other connections reload their cached schema on their next access.
* The page size, reserved space, text encoding, user version, and application ID are preserved exactly.

### Requirements

In addition to the [common requirements](#common-requirements):

* The database must be opened in WAL journal mode.
* The database must not be in-memory.
* The database must not be read-only.
* Only the `main` database is supported. Vacuuming an attached schema in place is not supported yet.
* No other process may currently hold the multi-process WAL.

### MVCC Databases

When the database uses MVCC (`PRAGMA journal_mode = mvcc`), additional rules apply to in-place `VACUUM`:

* All MVCC changes must be checkpointed first. If the MVCC log contains uncheckpointed changes, `VACUUM` returns an error — run `PRAGMA wal_checkpoint(TRUNCATE)` first.
* No other MVCC transaction may be active on any connection. `VACUUM` returns a busy error if one is found.
* During the operation the MVCC subsystem is paused so that the rebuilt schema and log can be reconciled atomically.

## VACUUM INTO

`VACUUM INTO` builds a compacted copy of the database at the given path and leaves the source database untouched. The destination is a fully self-contained database file that can be opened independently.

### Effect

* A new database file is created at `filename` containing all user tables, indexes, triggers, views, and virtual table content from the source.
* Indexes, triggers, and views are recreated after the data is copied so that triggers do not fire during the copy.
* Custom index methods (for example FTS and vector) rebuild their backing structures from the copied data.
* `sqlite_sequence` counters used by `AUTOINCREMENT` columns are preserved.
* Page size, reserved space, text encoding, user version, and application ID are copied from the source.
* If the source uses MVCC, the destination is created with MVCC enabled but with fresh state. The source's MVCC metadata table is not copied across.
* The file is fully synced and a TRUNCATE checkpoint is performed before the statement returns, so the destination is durable without further action.

<Warning>
  `VACUUM INTO` does not carry encryption through to the destination. Even when the source is encrypted, the compacted copy is written as an ordinary unencrypted database file and can be opened without a key. Reserved header bytes are still copied across, so the destination's page layout matches the source.
</Warning>

### Requirements

In addition to the [common requirements](#common-requirements):

* The destination file must not already exist. Delete or move it first if you want to overwrite.
* The path must be provided as a string literal in the SQL statement.
* `VACUUM temp INTO filename` is a no-op and does not create a file, matching SQLite's behavior.

`VACUUM INTO` takes a consistent view of the source by starting an implicit read transaction for the duration of the copy, so the destination always reflects a single snapshot of the source even if other connections write to it during the copy.

## Examples

### Rebuild the Main Database

```sql theme={null}
-- Reclaim space and defragment the current database
VACUUM;
```

### Write a Compacted Copy to a New File

```sql theme={null}
VACUUM INTO 'backup.db';
```

The source database is unchanged. `backup.db` can be opened as a standalone database:

```bash theme={null}
tursodb backup.db
```

### Vacuum an Attached Database Into a New File

```sql theme={null}
ATTACH DATABASE 'archive.db' AS archive;

-- Write a compacted copy of the archive schema to a new file
VACUUM archive INTO 'archive-compact.db';
```

### Use VACUUM INTO for a Backup Snapshot

Because `VACUUM INTO` copies from a consistent snapshot, it is a simple way to capture a point-in-time backup of an active database:

```sql theme={null}
VACUUM INTO '/var/backups/app-2026-04-23.db';
```

### Restore Performance After Heavy Churn

After bulk deletes or long-running workloads that leave indexes fragmented, `VACUUM` rebuilds the indexes and often improves scan speed:

```sql theme={null}
DELETE FROM events WHERE created_at < '2025-01-01';
VACUUM;
```

## Errors

| Error                                                                                                   | Cause                                                                                            |
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `VACUUM is an experimental feature. Enable with --experimental-vacuum flag`                             | In-place `VACUUM` used in a release build without the experimental flag.                         |
| `VACUUM is only supported for the main database; schema '<name>' is not supported yet`                  | In-place `VACUUM schema-name` with a schema other than `main`.                                   |
| `Cannot execute VACUUM in query_only mode`                                                              | `VACUUM` or `VACUUM INTO` run on a connection where `PRAGMA query_only` is set.                  |
| `cannot VACUUM from within a transaction` / `cannot VACUUM INTO from within a transaction`              | Run inside an explicit `BEGIN`.                                                                  |
| `cannot VACUUM - SQL statements in progress`                                                            | Another statement is still active on the same connection.                                        |
| `Incremental auto-vacuum is not supported`                                                              | The source database has `auto_vacuum = incremental`. Applies to both `VACUUM` and `VACUUM INTO`. |
| `VACUUM requires a WAL-mode database`                                                                   | In-place `VACUUM` on a non-WAL database.                                                         |
| `cannot VACUUM an in-memory database`                                                                   | In-place `VACUUM` on an in-memory database.                                                      |
| `ReadOnly`                                                                                              | In-place `VACUUM` on a read-only database.                                                       |
| `cannot VACUUM while experimental multiprocess WAL is active in another process`                        | In-place `VACUUM` while another process holds the multi-process WAL.                             |
| `cannot VACUUM an MVCC database with uncheckpointed changes; run PRAGMA wal_checkpoint(TRUNCATE) first` | In-place `VACUUM` on an MVCC database with pending log entries.                                  |
| `output file already exists: <path>`                                                                    | The destination for `VACUUM INTO` already exists.                                                |
| `VACUUM INTO path cannot be empty`                                                                      | `VACUUM INTO ''` was used.                                                                       |
| `VACUUM INTO requires a string literal path`                                                            | A non-literal expression (for example a bind parameter) was used for the destination.            |
| `no such database: <name>`                                                                              | `VACUUM <schema> INTO` referenced an unknown schema.                                             |

## See Also

* [Experimental Features](/sql-reference/experimental-features) for enabling in-place `VACUUM`
* [PRAGMAs](/sql-reference/pragmas) for `auto_vacuum`, `journal_mode`, `query_only`, and `wal_checkpoint`
* [ATTACH DATABASE](/sql-reference/statements/attach-database) for attaching a schema that can be targeted by `VACUUM INTO`
* [ANALYZE](/sql-reference/statements/analyze) for refreshing query planner statistics after a vacuum


# Checkpoint
Source: https://docs.turso.tech/sync/checkpoint

How to compact the local WAL to bound disk usage while preserving sync state.

<Note>
  This particular usage uses the Turso Cloud to sync the local Turso databases and assumes that you have an account.
</Note>

## Overview

The sync engine uses a WAL (Write-Ahead Log) to track local writes. Over time, the WAL grows as you make changes. Checkpoint compacts it by transferring committed frames into the main database file and then truncating the WAL.

Auto-checkpoint is **disabled** for sync databases — you must call `checkpoint()` explicitly.

## Why Checkpoint Matters

Without checkpointing, the WAL grows unbounded. After many writes, the WAL can become significantly larger than the database itself. Checkpointing reclaims that disk space.

You can observe this with `stats()`:

<CodeGroup>
  ```ts TypeScript theme={null}
  const before = await db.stats();
  console.log('WAL size before:', before.mainWalSize);

  await db.checkpoint();

  const after = await db.stats();
  console.log('WAL size after:', after.mainWalSize);
  ```

  ```py Python theme={null}
  before = conn.stats()
  print("WAL size before:", before.main_wal_size)

  conn.checkpoint()

  after = conn.stats()
  print("WAL size after:", after.main_wal_size)
  ```

  ```go Go theme={null}
  before, _ := db.Stats(ctx)
  log.Printf("WAL size before: %d", before.MainWalSize)

  db.Checkpoint(ctx)

  after, _ := db.Stats(ctx)
  log.Printf("WAL size after: %d", after.MainWalSize)
  ```
</CodeGroup>

## When to Checkpoint

Call `checkpoint()` periodically based on your write patterns:

* **After bulk inserts** — if you insert many rows at once, checkpoint afterward to reclaim WAL space
* **On a schedule** — for steady write workloads, checkpoint at regular intervals (e.g. every few minutes)
* **When WAL size is large** — use `stats().mainWalSize` to monitor and checkpoint when it exceeds a threshold

<CodeGroup>
  ```ts TypeScript theme={null}
  await db.checkpoint();
  ```

  ```py Python theme={null}
  conn.checkpoint()
  ```

  ```go Go theme={null}
  if err := db.Checkpoint(ctx); err != nil {
  	return err
  }
  ```
</CodeGroup>


# Conflict Resolution
Source: https://docs.turso.tech/sync/conflict-resolution

How Turso sync handles concurrent changes from multiple clients.

<Note>
  This particular usage uses the Turso Cloud to sync the local Turso databases and assumes that you have an account.
</Note>

## Last Push Wins

Turso sync uses a **last push wins** strategy. When two clients modify the same data and push, the last push determines the final state on the remote.

| Time | Client A                                   | Client B                                 |
| ---- | ------------------------------------------ | ---------------------------------------- |
| T1   | `UPDATE users SET name='Alice' WHERE id=1` |                                          |
| T2   |                                            | `UPDATE users SET name='Bob' WHERE id=1` |
| T3   | `push()`                                   |                                          |
| T4   |                                            | `push()`                                 |

**Result**: The name is `'Bob'` because Client B pushed last.

## What Happens During Pull

When you pull and have unpushed local changes:

1. Your local database is rolled back to the last synced state
2. Remote changes are applied
3. Your unpushed local changes are **replayed** on top

This rollback-and-replay happens atomically — if anything fails, your database remains in its previous state.


# Local Sync Server
Source: https://docs.turso.tech/sync/local-sync-server

Use the Turso CLI as a local sync server for development and testing without Turso Cloud.

The `tursodb` CLI includes a built-in sync server that you can run locally. This lets you develop and test sync workflows entirely on your machine — no Turso Cloud account required.

<Info>
  The local sync server implements the same sync protocol as Turso Cloud, so your application code works the same way in both environments.
</Info>

## Starting the server

Pass `--sync-server` with an address to start the sync server. The database argument specifies where the server stores its data:

```bash theme={null}
tursodb ./server.db --sync-server 0.0.0.0:8080
```

This starts a sync server listening on port `8080`, backed by `./server.db`.

## Connecting clients

Point your sync client at `http://localhost:8080`. No auth token is needed for the local server.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { connect } from '@tursodatabase/sync';

  const db = await connect({
    path: './local-replica.db',
    url: 'http://localhost:8080',
  });
  ```

  ```py Python theme={null}
  import turso.sync

  conn = turso.sync.connect(
      path="./local-replica.db",
      remote_url="http://localhost:8080",
  )
  ```

  ```go Go theme={null}
  package main

  import (
  	turso "turso.tech/database/tursogo"
  )

  db, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{
      Path:      "./local-replica.db",
      RemoteUrl: "http://localhost:8080",
  })
  ```
</CodeGroup>

Once connected, all sync operations (`push`, `pull`, `checkpoint`, `stats`) work exactly as described in the [Usage](/sync/usage) guide.

## Example: full local sync workflow

This example walks through a complete workflow — starting the server, writing data from one client, and syncing it to another.

<Steps>
  <Step title="1. Start the sync server">
    Open a terminal and start the server:

    ```bash theme={null}
    tursodb ./server.db --sync-server 0.0.0.0:8080
    ```
  </Step>

  <Step title="2. Write and push from Client A">
    <CodeGroup>
      ```ts TypeScript theme={null}
      import { connect } from '@tursodatabase/sync';

      const clientA = await connect({
        path: './client-a.db',
        url: 'http://localhost:8080',
      });

      await clientA.exec("CREATE TABLE IF NOT EXISTS notes (id TEXT PRIMARY KEY, body TEXT)");
      await clientA.exec("INSERT INTO notes VALUES ('n1', 'hello from client A')");

      await clientA.push();
      ```

      ```py Python theme={null}
      import turso.sync

      client_a = turso.sync.connect(
          path="./client-a.db",
          remote_url="http://localhost:8080",
      )

      client_a.execute("CREATE TABLE IF NOT EXISTS notes (id TEXT PRIMARY KEY, body TEXT)")
      client_a.commit()
      client_a.execute("INSERT INTO notes VALUES ('n1', 'hello from client A')")
      client_a.commit()

      client_a.push()
      ```

      ```go Go theme={null}
      clientA, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{
          Path:      "./client-a.db",
          RemoteUrl: "http://localhost:8080",
      })
      if err != nil {
          return err
      }
      connA, _ := clientA.Connect(ctx)

      connA.ExecContext(ctx, "CREATE TABLE IF NOT EXISTS notes (id TEXT PRIMARY KEY, body TEXT)")
      connA.ExecContext(ctx, "INSERT INTO notes VALUES ('n1', 'hello from client A')")

      clientA.Push(ctx)
      ```
    </CodeGroup>
  </Step>

  <Step title="3. Pull from Client B">
    <CodeGroup>
      ```ts TypeScript theme={null}
      import { connect } from '@tursodatabase/sync';

      const clientB = await connect({
        path: './client-b.db',
        url: 'http://localhost:8080',
      });

      const changed = await clientB.pull();
      console.log('changes pulled:', changed);

      const rows = await clientB.query("SELECT * FROM notes");
      console.log(rows);
      // [{ id: 'n1', body: 'hello from client A' }]
      ```

      ```py Python theme={null}
      import turso.sync

      client_b = turso.sync.connect(
          path="./client-b.db",
          remote_url="http://localhost:8080",
      )

      changed = client_b.pull()
      print("changes pulled:", changed)

      rows = client_b.execute("SELECT * FROM notes").fetchall()
      print(rows)
      # [('n1', 'hello from client A')]
      ```

      ```go Go theme={null}
      clientB, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{
          Path:      "./client-b.db",
          RemoteUrl: "http://localhost:8080",
      })
      if err != nil {
          return err
      }

      changed, _ := clientB.Pull(ctx)
      log.Println("changes pulled:", changed)

      connB, _ := clientB.Connect(ctx)
      rows, _ := connB.QueryContext(ctx, "SELECT * FROM notes")
      ```
    </CodeGroup>
  </Step>
</Steps>


# Partial sync
Source: https://docs.turso.tech/sync/partial

Sync only what you need. Faster cold starts and lower bandwidth by lazily fetching database pages on demand.

<Note>
  This particular usage uses the Turso Cloud to sync the local Turso databases and assumes that you have an account.
</Note>

Partial sync lets your app open and use a database without downloading the entire file.
The client lazily fetches pages of the database file from the Turso Cloud when a query touches data that is not present locally.
This reduces startup time and network usage for large databases, while remaining fully compatible with the push/pull methods used
by Turso's standard `sync` solution.

<Note>
  * Reads on not-yet-downloaded data transparently trigger on-demand page fetches.
  * Writes still apply locally first and are pushed as logical statements.
</Note>

## Modes

Two bootstrap strategies define what is present locally at connect time:

* **Prefix bootstrap**: download the first N bytes of the database file.
  * Good default when you want a minimal, predictable starting footprint.
* **Query bootstrap**: download pages touched by running a server-side SQL query.
  * Ideal to hydrate only a narrow working set (e.g., a single user's rows, small tables with metadata, references, etc).

Both modes continue to lazily fetch missing pages on demand.

### Prefix bootstrap

<CodeGroup>
  ```ts TypeScript theme={null}
  import { connect } from '@tursodatabase/sync';

  const db = await connect({
    path: './app.db',
    url: 'turso://...',
    authToken: process.env.TURSO_AUTH_TOKEN,
    partialSyncExperimental: {
      bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 }, // 128 KiB
    },
  });
  ```

  ```py Python theme={null}
  import os
  import turso.sync

  conn = turso.sync.connect(
      path="./app.db",
      remote_url="turso://...",
      auth_token=os.environ["TURSO_AUTH_TOKEN"],
      partial_sync_experimental=turso.sync.PartialSyncOpts(
          bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024),
      ),
  )
  ```

  ```go Go theme={null}
  import (
  	turso "turso.tech/database/tursogo"
  )

  db, err := turso.NewTursoSyncDb(context.Background(), turso.TursoSyncDbConfig{
    Path:      "./app.db",
    RemoteUrl: "turso://...",
    AuthToken: os.Getenv("TURSO_AUTH_TOKEN"),
    PartialSyncExperimental: turso.TursoPartialSyncConfig{
      BootstrapStrategyPrefix: 128 * 1024, // 128 KiB
    },
  })
  ```
</CodeGroup>

### Query bootstrap

<CodeGroup>
  ```ts TypeScript theme={null}
  import { connect } from '@tursodatabase/sync';

  const db = await connect({
    path: './app.db',
    url: 'turso://...',
    authToken: process.env.TURSO_AUTH_TOKEN,
    partialSyncExperimental: {
      bootstrapStrategy: {
        kind: 'query',
        query: `SELECT * FROM messages WHERE user_id = 'u_123' LIMIT 100`,
      },
    },
  });
  ```

  ```py Python theme={null}
  import turso.sync

  conn = turso.sync.connect(
      path=":memory:",
      remote_url="turso://...",
      partial_sync_experimental=turso.sync.PartialSyncOpts(
          bootstrap_strategy=turso.sync.PartialSyncQueryBootstrap(
              query="SELECT * FROM messages WHERE user_id = 'u_123' LIMIT 100"
          ),
      ),
  )
  ```

  ```go Go theme={null}
  import (
  	turso "turso.tech/database/tursogo"
  )

  db, err := turso.NewTursoSyncDb(context.Background(), turso.TursoSyncDbConfig{
    Path:      "./app.db",
    RemoteUrl: "turso://...",
    AuthToken: os.Getenv("TURSO_AUTH_TOKEN"),
    PartialSyncExperimental: turso.TursoPartialSyncConfig{
      BootstrapStrategyQuery: "SELECT * FROM messages WHERE user_id = 'u_123' LIMIT 100",
    },
  })
  ```
</CodeGroup>

## Optimizations

### Segment size (batched lazy reads)

<Frame>
  <div>
    <svg>
      <defs>
        <marker>
          <polygon />
        </marker>
      </defs>

      <g>
        <text>select page</text>

        <g><rect /><text>1</text></g>
        <g><rect /><text>2</text></g>
        <g><rect /><text>3</text></g>
        <g><rect /><text>4</text></g>

        <g><rect /><text>5</text></g>
        <g><rect /><text>6</text></g>
        <g><rect /><text>7</text></g>
        <g><rect /><text>8</text></g>

        <g><rect /><text>9</text></g>
        <g><rect /><text>10</text></g>
        <g><rect /><text>11</text></g>
        <g><rect /><text>12</text></g>
      </g>

      <g>
        <line />

        <text>read(7)</text>
      </g>

      <g>
        <text>fetching segment</text>

        <rect />

        <rect />

        <rect />

        <g><rect /><text>1</text></g>
        <g><rect /><text>2</text></g>
        <g><rect /><text>3</text></g>
        <g><rect /><text>4</text></g>

        <g><rect /><text>5</text></g>
        <g><rect /><text>6</text></g>
        <g><rect /><text>7</text></g>
        <g><rect /><text>8</text></g>

        <g><rect /><text>9</text></g>
        <g><rect /><text>10</text></g>
        <g><rect /><text>11</text></g>
        <g><rect /><text>12</text></g>
        <text>segment 5-8</text>
      </g>

      <g>
        <line />
      </g>

      <g>
        <text>segment cached</text>

        <g><rect /><text>1</text></g>
        <g><rect /><text>2</text></g>
        <g><rect /><text>3</text></g>
        <g><rect /><text>4</text></g>

        <g><rect /><text>5</text></g>
        <g><rect /><text>6</text></g>
        <g><rect /><text>7</text></g>
        <g><rect /><text>8</text></g>

        <g><rect /><text>9</text></g>
        <g><rect /><text>10</text></g>
        <g><rect /><text>11</text></g>
        <g><rect /><text>12</text></g>
      </g>
    </svg>

    <p>
      Read page 7 → fetch segment 5-8 (3 new pages)
    </p>
  </div>
</Frame>

When the client needs a page that isn’t present locally, partial sync performs an on-demand fetch from the remote database.

To reduce round-trips and speed up these fetches, you can configure the **segment size**: instead of requesting a single page, the client downloads a whole *segment* of pages in one request.

This lets the client amortize network overhead and hydrate nearby pages that are likely to be accessed soon.

**How it works**

Suppose your database has:

* `page_size = 4 KiB`
* `segment_size = 16 KiB`

If a local query touches page **6**, the client computes the segment that page belongs to:

* 16 KiB segment = 4 pages
* Segment covering page 6 = pages **5-8**

The client then fetches all four pages in a single request and stores them locally.
Future reads to those pages incur no additional network cost.

**Benefits**

* Fewer HTTP requests (one segment fetch vs. many single-page fetches)
* Faster hydration of hot ranges
* Better performance for workloads with spatial locality (e.g., range scans, index lookups)

**Default**

The default `segment_size` is **128 KiB** (typically 32 pages on a 4 KiB page size), which provides a good balance between request overhead and total bytes transferred.

<Tip>
  If your workload touches data in tight clusters (e.g., reading several adjacent rows), larger segment sizes can significantly improve performance.

  Conversely, very sparse/random-access workloads may benefit from smaller segment sizes.
</Tip>

<CodeGroup>
  ```ts TypeScript theme={null}
  await connect({
    ...
    partialSyncExperimental: {
      bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 }, // 128 KiB
      segmentSize: 16 * 1024,
    },
  });
  ```

  ```py Python theme={null}
  turso.sync.connect(
      ...
      partial_sync_experimental=turso.sync.PartialSyncOpts(
          bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024),
          segment_size=16 * 1024,
      ),
  )
  ```

  ```go Go theme={null}
  turso.NewTursoSyncDb(context.Background(), turso.TursoSyncDbConfig{
    ...
    PartialSyncExperimental: turso.TursoPartialSyncConfig{
      BootstrapStrategyPrefix: 128 * 1024, // 128 KiB
      SegmentSize: 16 * 1024,
    },
  })
  ```
</CodeGroup>

### Prefetch

<Frame>
  <div>
    <svg>
      <defs>
        <marker>
          <polygon />
        </marker>

        <marker>
          <polygon />
        </marker>
      </defs>

      <g>
        <text>select page</text>

        <g><rect /><text>1</text></g>
        <g><rect /><text>2</text></g>
        <g><rect /><text>3</text></g>
        <g><rect /><text>4</text></g>

        <g><rect /><text>5</text></g>
        <g><rect /><text>6</text></g>
        <g><rect /><text>7</text></g>
        <g><rect /><text>8</text></g>

        <g><rect /><text>9</text></g>
        <g><rect /><text>10</text></g>
        <g><rect /><text>11</text></g>
        <g><rect /><text>12</text></g>

        <g />
      </g>

      <g>
        <line />

        <text>read(4)</text>
      </g>

      <g>
        <text>fetching</text>

        <g><rect /><text>1</text></g>
        <g><rect /><text>2</text></g>
        <g><rect /><text>3</text></g>
        <g><rect /><text>4</text></g>

        <g><rect /><text>5</text></g>
        <g><rect /><text>6</text></g>
        <g><rect /><text>7</text></g>
        <g><rect /><text>8</text></g>

        <g><rect /><text>9</text></g>
        <g><rect /><text>10</text></g>
        <g><rect /><text>11</text></g>
        <g><rect /><text>12</text></g>
      </g>

      <g>
        <line />
      </g>

      <g>
        <text>loaded</text>

        <g><rect /><text>1</text></g>
        <g><rect /><text>2</text></g>
        <g><rect /><text>3</text></g>
        <g><rect /><text>4</text></g>

        <g><rect /><text>5</text></g>
        <g><rect /><text>6</text></g>
        <g><rect /><text>7</text></g>
        <g><rect /><text>8</text></g>

        <g><rect /><text>9</text></g>
        <g><rect /><text>10</text></g>
        <g><rect /><text>11</text></g>
        <g><rect /><text>12</text></g>
      </g>
    </svg>

    <p>
      Read page 4 → prefetch 2 child pages (11, 12)
    </p>
  </div>
</Frame>

Prefetch is an optional optimization that builds on top of lazy page fetches.
When enabled, the client not only retrieves the pages required by the current query, but also **inspects the structure of the newly downloaded pages and recent access patterns** to predict which pages are likely to be needed next.

If the client detects a natural continuation of the access pattern — such as child pages referenced by an internal B-tree node — it proactively downloads those pages in advance.
This reduces the number of future on-demand fetches and helps avoid stalls during operations like range scans, index walks, or sequential lookups.

<CodeGroup>
  ```ts TypeScript theme={null}
  await connect({
    ...
    partialSyncExperimental: {
      bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 }, // 128 KiB
      prefetch: true,
    },
  });
  ```

  ```py Python theme={null}
  turso.sync.connect(
      ...
      partial_sync_experimental=turso.sync.PartialSyncOpts(
          bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024),
          prefetch=True,
      ),
  )
  ```

  ```go Go theme={null}
  turso.NewTursoSyncDb(context.Background(), turso.TursoSyncDbConfig{
    ...
    PartialSyncExperimental: turso.TursoPartialSyncConfig{
      BootstrapStrategyPrefix: 128 * 1024, // 128 KiB
      Prefetch: true,
    },
  })
  ```
</CodeGroup>

<Note>
  `segment_size` and `prefetch` are complementary.

  Segment size batches nearby pages into a single on-demand fetch, while prefetch looks at the query's access pattern and proactively fetches additional pages likely to be needed next.

  Using both together can provide the best performance for real-world workloads.
</Note>


# Usage
Source: https://docs.turso.tech/sync/usage

How to enable and use sync with Turso across TypeScript, Python, and Go.

This guide shows how to set up a Turso database and use the sync features from your application.

<Note>
  This particular usage uses the Turso Cloud to sync the local Turso databases and assumes that you have an account.
</Note>

<Steps>
  <Step title="1. Setup Turso Cloud database">
    * Follow our [Quickstart](/quickstart) to install the CLI, create a database

    * Get the database URL (`turso://...`):

    ```
    turso db show <db>
    ```

    * Create a token for your app:

    ```
    turso db tokens create <db>
    ```
  </Step>

  <Step title="2. Setup a basic connection with sync">
    You need three essentials to enable sync:

    * Local path: where the local, synced tursodb file is stored
    * Remote URL: your Turso Cloud URL (`turso://...`)
    * Auth token: Turso Cloud token to authenticate requests

    <CodeGroup>
      ```ts TypeScript theme={null}
      import { connect } from '@tursodatabase/sync';

      const db = await connect({
        path: './app.db',                               // local path
        url: 'turso://...',                            // remote URL (generated with turso db show <db-name> --url)
        authToken: process.env.TURSO_AUTH_TOKEN,        // authentication token (generated with turso db tokens create <db-name>)
        // longPollTimeoutMs: 10_000,                   // optional: server waits before replying to pull
        // bootstrapIfEmpty: false,                     // set to false to avoid bootstrapping on first run
      });
      ```

      ```py Python theme={null}
      import os
      import turso.sync

      conn = turso.sync.connect(
          path="./app.db",                                  # local path
          remote_url="turso://...",                        # remote URL (generated with turso db show <db-name> --url)
          auth_token=os.environ["TURSO_AUTH_TOKEN"],        # authentication token (generated with turso db tokens create <db-name>)
          # long_poll_timeout_ms=10_000,                    # optional: server waits before replying to pull
          # bootstrap_if_empty=False,                       # set to false to avoid bootstrapping on first run
      )
      ```

      ```go Go theme={null}
      package main

      import (
      	turso "turso.tech/database/tursogo"
      )

      db, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{
        Path:              "./app.db",                    // local path
        RemoteUrl:         "turso://...",                // remote URL (generated with turso db show <db-name> --url)
        AuthToken:         os.Getenv("TURSO_AUTH_TOKEN"), // authentication token (generated with turso db tokens create <db-name>)
        // LongPollTimeoutMs: 10_000,                     // optional: server waits before replying to pull
        // BootstrapIfEmpty: new(bool),                   // *bool; point at false to avoid bootstrapping on first run
      })
      ```
    </CodeGroup>

    <Note>
      On the first run, the local database is automatically bootstrapped from the remote — so the remote must be reachable during the initial connect.

      If you set `bootstrap_if_empty` to `false`, the local database will start empty instead.
      You can bootstrap or update it later at any time by explicitly calling `pull()`.
    </Note>
  </Step>

  <Step title="3. Push changes">
    Push sends your local changes to the Turso Cloud server. Under the hood, logical statements are sent, and on conflicts the strategy is "last push wins".

    <CodeGroup>
      ```ts TypeScript theme={null}
      await db.exec("CREATE TABLE IF NOT EXISTS notes(id TEXT PRIMARY KEY, body TEXT)");
      await db.exec("INSERT INTO notes VALUES ('n1', 'hello')");

      await db.push();
      ```

      ```py Python theme={null}
      conn.execute("CREATE TABLE IF NOT EXISTS notes(id TEXT PRIMARY KEY, body TEXT)")
      conn.commit()
      conn.execute("INSERT INTO notes VALUES ('n1', 'hello')")
      conn.commit()

      conn.push()
      ```

      ```go Go theme={null}
      // create *sql.DB instance
      conn, err := db.Connect(ctx)
      if err != nil {
        return err
      }

      _, err = conn.ExecContext(ctx, "CREATE TABLE IF NOT EXISTS notes(id TEXT PRIMARY KEY, body TEXT)")
      if err != nil {
        return err
      }
      _, err = conn.ExecContext(ctx, "INSERT INTO notes VALUES ('n1', 'hello')")
      if err != nil {
        return err
      }

      if err := db.Push(ctx); err != nil {
      	return err
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="4. Pull changes">
    Pull fetches remote changes and applies them locally. It returns a boolean indicating whether anything changed.

    * Configure `long_poll_timeout_ms`/`LongPollTimeoutMs` if you want the server to wait for changes and avoid empty replies.
    * If you pushed earlier, a subsequent pull can still return that something changed due to server-side conflict resolution frames.

    <CodeGroup>
      ```ts TypeScript theme={null}
      // Returns true if anything changed locally
      const changed = await db.pull();
      console.info('pulled changes:', changed);
      ```

      ```py Python theme={null}
      changed = conn.pull()
      print("pulled changes:", changed)
      ```

      ```go Go theme={null}
      changed, err := db.Pull(ctx)
      if err != nil {
      	return err
      }
      log.Println("pulled changes:", changed)
      ```
    </CodeGroup>
  </Step>

  <Step title="5. Checkpoint">
    Checkpoint compacts the local WAL to bound local disk usage while preserving sync state.

    <CodeGroup>
      ```ts TypeScript theme={null}
      await db.checkpoint();
      ```

      ```py Python theme={null}
      conn.checkpoint()
      ```

      ```go Go theme={null}
      if err := db.Checkpoint(ctx); err != nil {
      	return err
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="6. Stats">
    Stats help you observe sync behavior and usage (WAL sizes, last push/pull times, network usage, revision, etc.).

    <CodeGroup>
      ```ts TypeScript theme={null}
      const s = await db.stats();
      console.info({
        cdcOperations: s.cdcOperations,
        mainWalSize: s.mainWalSize,
        revertWalSize: s.revertWalSize,
        networkReceivedBytes: s.networkReceivedBytes,
        networkSentBytes: s.networkSentBytes,
        lastPullUnixTime: s.lastPullUnixTime,
        lastPushUnixTime: s.lastPushUnixTime,
        revision: s.revision,
      });
      ```

      ```py Python theme={null}
      s = conn.stats()
      print({
          "cdc_operations": s.cdc_operations,
          "main_wal_size": s.main_wal_size,
          "revert_wal_size": s.revert_wal_size,
          "network_received_bytes": s.network_received_bytes,
          "network_sent_bytes": s.network_sent_bytes,
          "last_pull_unix_time": s.last_pull_unix_time,
          "last_push_unix_time": s.last_push_unix_time,
          "revision": s.revision,
      })
      ```

      ```go Go theme={null}
      s, err := db.Stats(ctx)
      if err != nil {
      	return err
      }
      log.Printf("stats: cdc=%v, main=%d revert=%d rx=%d tx=%d pull=%d push=%d revision=%s",
        s.CdcOperations,
        s.MainWalSize,
        s.RevertWalSize,
        s.NetworkReceivedBytes,
        s.NetworkSentBytes,
        s.LastPullUnixTime,
        s.LastPushUnixTime,
        s.Revision,
      )
      ```
    </CodeGroup>
  </Step>
</Steps>

## Offline-first writes

If your app needs to accept writes without internet connectivity, write locally and call `push()` when the connection is available. All changes are safely stored in the local database file until they can be synced.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { connect } from '@tursodatabase/sync';

  // bootstrapIfEmpty: false lets the app start offline without
  // needing to reach the remote on first launch
  const db = await connect({
    path: './local.db',
    url: process.env.TURSO_URL!,
    authToken: process.env.TURSO_AUTH_TOKEN!,
    bootstrapIfEmpty: false,
  });

  async function syncWhenOnline(db) {
    try {
      await db.push();
    } catch (e) {
      // No connectivity — changes are safe in the local file
      // and will sync on the next push() call
    }
  }

  // On app launch, pull the latest state if online
  try {
    await db.pull();
  } catch (e) {
    // Offline — local data is still available for reads and writes
  }

  // On a timer or connectivity event
  await syncWhenOnline(db);
  ```

  ```py Python theme={null}
  import os
  import turso.sync

  # bootstrap_if_empty=False lets the app start offline without
  # needing to reach the remote on first launch
  conn = turso.sync.connect(
      path="./local.db",
      remote_url=os.environ["TURSO_URL"],
      auth_token=os.environ["TURSO_AUTH_TOKEN"],
      bootstrap_if_empty=False,
  )

  def sync_when_online(conn):
      try:
          conn.push()
      except Exception:
          # No connectivity — changes are safe in the local file
          # and will sync on the next push() call
          pass

  # On app launch, pull the latest state if online
  try:
      conn.pull()
  except Exception:
      # Offline — local data is still available for reads and writes
      pass

  # On a timer or connectivity event
  sync_when_online(conn)
  ```

  ```go Go theme={null}
  // BootstrapIfEmpty pointing at false lets the app start offline
  // without needing to reach the remote on first launch
  noBootstrap := false
  db, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{
  	Path:             "./local.db",
  	RemoteUrl:        os.Getenv("TURSO_URL"),
  	AuthToken:        os.Getenv("TURSO_AUTH_TOKEN"),
  	BootstrapIfEmpty: &noBootstrap,
  })

  func syncWhenOnline(ctx context.Context, db *turso.TursoSyncDb) {
  	if err := db.Push(ctx); err != nil {
  		// No connectivity — changes are safe in the local file
  		// and will sync on the next Push() call
  		log.Println("push deferred:", err)
  	}
  }

  // On app launch, pull the latest state if online
  if _, err := db.Pull(ctx); err != nil {
  	// Offline — local data is still available for reads and writes
  	log.Println("pull deferred:", err)
  }

  // On a timer or connectivity event
  syncWhenOnline(ctx, db)
  ```
</CodeGroup>

This is the modern equivalent of the `offline: true` flag from [`@libsql/client` Embedded Replicas](/features/embedded-replicas/introduction). With Turso Sync, all reads and writes happen locally by default — you control when to sync with explicit `push()` and `pull()` calls.


# Turso Cloud Documentation
Source: https://docs.turso.tech/turso-cloud

Fully managed platform for SQLite-compatible databases. Deploy Turso, a rewrite of SQLite with concurrent writes and multi-user access built in, or libSQL, the battle-tested SQLite fork. No need to switch to Postgres for concurrency.

Turso Cloud is a fully managed database platform, designed to scale from prototype to production. It hosts two database engines, both fully compatible with SQLite:

* **[Turso](/tursodb/quickstart)** — a ground-up rewrite of SQLite. Turso supports **concurrent writes** and **multi-user access by default**: many clients can read and write the same database at the same time, over the network. If SQLite's single-writer limitation is what would push you toward a client-server database like Postgres, Turso removes it — you keep SQLite's simplicity and compatibility and gain the concurrency. Concurrent writes are the headline, but not the whole story: see the [Turso documentation](/tursodb/quickstart) for change data capture, vector search, encryption, sync, and more.

* **[libSQL](/libsql)** — a fork of SQLite, battle-tested in production on Turso Cloud for years.

<Note>
  Turso databases on Turso Cloud are in early preview — a reflection of how
  recently the offering landed on the platform, not of the engine itself.
  Create one with `turso db create --tursodb`.
</Note>

<CardGroup>
  <Card
    title="Vector Search"
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke-width="1.5"
    stroke="#1ebca1"
  >
    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z"
    />
  </svg>
}
    href="/features/ai-and-embeddings"
  >
    Native similarity search for AI apps and RAG workflows, no extensions
    required.
  </Card>

  <Card
    title="Replication & Sync"
    icon={
  <svg viewBox="0 0 216 216" xmlns="http://www.w3.org/2000/svg">
    <path
      d="m25.3 114.3 15.9 48c1.5 4.5 5 8 9.5 9.5l48 15.9c5.3 1.8 11.2.4 15.2-3.6l76.7-76.7c3-3 4.6-7.1 4.3-11.4l-3.7-64c-.3-5.5-4.7-10-10.3-10.3l-64-3.7c-4.2-.2-8.4 1.3-11.4 4.3l-76.6 76.8c-4 4-5.4 9.9-3.6 15.2z"
      fill="none"
      stroke="#1ebca1"
      stroke-miterlimit="10"
      stroke-width="19.2"
    />
    <path
      d="m119.2 82.2 6.5-1.5c2.9-.7 5.8 1.2 6.5 4.1.2.8.2 1.6 0 2.4l-1.5 6.5c-3.3 12.4-21.1 8.4-18.8-4.2.8-3.8 3.8-6.5 7.3-7.3z"
      fill="#1ebca1"
    />
    <path
      d="m21.1 191.8 99.8-99.8"
      fill="none"
      stroke="#1ebca1"
      stroke-linecap="round"
      stroke-linejoin="round"
      stroke-width="19.2"
    />
    <g fill="#1ebca1">
      <path d="m141.8 142.4c-1.4 1.4-3.5 1.9-5.4 1.3l-13.3-4.4c-2-.7-4.3-.1-5.8 1.4l-8.9 8.9c-1.8 1.8-1.1 4.8 1.3 5.6l20.6 6.8c8.9 2.9 18.4.6 25-5.9" />
      <path d="m107 177.2c-1.4 1.4-3.5 1.9-5.4 1.3l-14.6-4.8c-1.2-.4-2.5 0-3.4.8l-9.9 9.9c-1.8 1.8-1.1 4.8 1.3 5.6l20.6 6.8c8.9 2.9 18.4.6 25-5.9" />
      <path d="m175.2 109c-1.4 1.4-3.5 1.9-5.4 1.3l-13.3-4.4c-2-.7-4.3-.1-5.8 1.4l-8.9 8.9c-1.8 1.8-1.1 4.8 1.3 5.6l20.6 6.8c8.9 2.9 18.4.6 25-5.9" />
    </g>
  </svg>
}
    href="/features/embedded-replicas/introduction"
  >
    Keep devices in sync with each other on demand with Turso Cloud.
  </Card>

  <Card
    title="Branching"
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke-width="1.5"
    stroke="#1ebca1"
  >
    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z"
    />
  </svg>
}
    href="/features/branching"
  >
    Create isolated Copy-on-Write branches super fast.
  </Card>

  <Card
    title="Analytics"
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke-width="1.5"
    stroke="#1ebca1"
  >
    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z"
    />
  </svg>
}
  >
    Monitor performance and usage across databases.
  </Card>

  <Card
    title="Bring-Your-Own-Key Encryption"
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke-width="1.5"
    stroke="#1ebca1"
  >
    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z"
    />
  </svg>
}
    href="/cloud/encryption"
  >
    Keep sensitive data private with keys only you control. Keys are per-database for full isolation.
  </Card>

  <Card
    title="Scoped Access Tokens"
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke-width="1.5"
    stroke="#1ebca1"
  >
    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"
    />
  </svg>
}
    href="/sdk/authorization"
  >
    Fine-grained tokens scoped by database, table, action, and expiration. Read-only, write, or custom permissions.
  </Card>

  <Card
    title="Team Access"
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke-width="1.5"
    stroke="#1ebca1"
  >
    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z"
    />
  </svg>
}
  >
    Collaborate on databases with members, and manage access permissions.
  </Card>

  <Card
    title="Fully Managed"
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke-width="1.5"
    stroke="#1ebca1"
  >
    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z"
    />
  </svg>
}
  >
    We handle infrastructure, scaling, and security.
  </Card>

  <Card
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke-width="1.5"
    stroke="#1ebca1"
  >
    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      d="M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3"
    />
  </svg>
}
    title="Backups and Recovery"
    href="/features/point-in-time-recovery"
  >
    Restore your database to any point in time
  </Card>
</CardGroup>

## Start building

Learn how to manage, distribute and integrate your databases with the CLI, API and SDKs.

<CardGroup>
  <Card title="Turso CLI" icon="terminal" href="/cli">
    Manage groups, databases, and API tokens with the Turso CLI.
  </Card>

  <Card title="Turso Platform API" icon="database" href="/api-reference">
    Programmatically manage groups and databases using the Platform API.
  </Card>

  <Card
    title="Client SDKs"
    icon={
  <svg viewBox="0 0 216 216" xmlns="http://www.w3.org/2000/svg">
    <path
      d="m25.3 114.3 15.9 48c1.5 4.5 5 8 9.5 9.5l48 15.9c5.3 1.8 11.2.4 15.2-3.6l76.7-76.7c3-3 4.6-7.1 4.3-11.4l-3.7-64c-.3-5.5-4.7-10-10.3-10.3l-64-3.7c-4.2-.2-8.4 1.3-11.4 4.3l-76.6 76.8c-4 4-5.4 9.9-3.6 15.2z"
      fill="none"
      stroke="#1ebca1"
      stroke-miterlimit="10"
      stroke-width="19.2"
    />
    <path
      d="m119.2 82.2 6.5-1.5c2.9-.7 5.8 1.2 6.5 4.1.2.8.2 1.6 0 2.4l-1.5 6.5c-3.3 12.4-21.1 8.4-18.8-4.2.8-3.8 3.8-6.5 7.3-7.3z"
      fill="#1ebca1"
    />
    <path
      d="m21.1 191.8 99.8-99.8"
      fill="none"
      stroke="#1ebca1"
      stroke-linecap="round"
      stroke-linejoin="round"
      stroke-width="19.2"
    />
    <g fill="#1ebca1">
      <path d="m141.8 142.4c-1.4 1.4-3.5 1.9-5.4 1.3l-13.3-4.4c-2-.7-4.3-.1-5.8 1.4l-8.9 8.9c-1.8 1.8-1.1 4.8 1.3 5.6l20.6 6.8c8.9 2.9 18.4.6 25-5.9" />
      <path d="m107 177.2c-1.4 1.4-3.5 1.9-5.4 1.3l-14.6-4.8c-1.2-.4-2.5 0-3.4.8l-9.9 9.9c-1.8 1.8-1.1 4.8 1.3 5.6l20.6 6.8c8.9 2.9 18.4.6 25-5.9" />
      <path d="m175.2 109c-1.4 1.4-3.5 1.9-5.4 1.3l-13.3-4.4c-2-.7-4.3-.1-5.8 1.4l-8.9 8.9c-1.8 1.8-1.1 4.8 1.3 5.6l20.6 6.8c8.9 2.9 18.4.6 25-5.9" />
    </g>
  </svg>
}
    href="/sdk"
  >
    Connect and integrate your application with one of our client SDKs, for
    Turso and libSQL databases alike.
  </Card>

  <Card title="Tutorials" icon="newspaper" href="https://turso.tech/blog">
    Learn how to work with Turso and your favorite language or framework.
  </Card>
</CardGroup>

## Community

Join the Turso community to ask questions, share what you're working on, discuss best practices, and share tips on [Discord](https://tur.so/discord), [Twitter](https://twitter.com/tursodatabase), and [LinkedIn](https://www.linkedin.com/company/turso).

<CardGroup>
  <Card title="Discord" icon="discord" href="https://tur.so/discord" />

  <Card title="GitHub" icon="star" href="https://github.com/tursodatabase" />

  <Card title="X (Twitter)" icon="x-twitter" href="https://twitter.com/tursodatabase" />
</CardGroup>


# Change Data Capture
Source: https://docs.turso.tech/tursodb/cdc



Change Data Capture (CDC) tracks all data changes (inserts, updates, deletes) made to your database tables. Changes are recorded in a local table that you can query like any other table. This is useful for building reactive applications, syncing data between systems, auditing, and more.

## Enable CDC

Enable CDC per connection using a PRAGMA:

```sql theme={null}
PRAGMA capture_data_changes_conn('full');
```

To disable:

```sql theme={null}
PRAGMA capture_data_changes_conn('off');
```

<Warning>
  CDC cannot be used together with [MVCC](/tursodb/concurrent-writes). They are mutually exclusive on the same connection.
</Warning>

## Capture Modes

| Mode     | Description                                                          |
| -------- | -------------------------------------------------------------------- |
| `id`     | Capture only the primary key/rowid of changed rows                   |
| `before` | Capture row state before changes (for updates/deletes)               |
| `after`  | Capture row state after changes (for inserts/updates)                |
| `full`   | Capture both before and after states, plus per-column update details |

## CDC Table

Changes are stored in the `turso_cdc` table by default. You can specify a custom table name:

```sql theme={null}
PRAGMA capture_data_changes_conn('full,my_changes_table');
```

The CDC table has the following schema:

| Column          | Type    | Description                                             |
| --------------- | ------- | ------------------------------------------------------- |
| `change_id`     | INTEGER | Auto-incrementing unique identifier                     |
| `change_time`   | INTEGER | Timestamp (Unix epoch)                                  |
| `change_txn_id` | INTEGER | Transaction ID (groups rows into transactions)          |
| `change_type`   | INTEGER | `1` = INSERT, `0` = UPDATE, `-1` = DELETE, `2` = COMMIT |
| `table_name`    | TEXT    | Name of the table that was changed                      |
| `id`            | varies  | Primary key/rowid of the changed row                    |
| `before`        | BLOB    | Row data before the change (modes: `before`, `full`)    |
| `after`         | BLOB    | Row data after the change (modes: `after`, `full`)      |
| `updates`       | BLOB    | Per-column change details (mode: `full`)                |

## Querying Changes

```sql theme={null}
-- All changes
SELECT * FROM turso_cdc;

-- Only inserts
SELECT * FROM turso_cdc WHERE change_type = 1;

-- Only updates
SELECT * FROM turso_cdc WHERE change_type = 0;

-- Only deletes
SELECT * FROM turso_cdc WHERE change_type = -1;

-- Changes for a specific table
SELECT * FROM turso_cdc WHERE table_name = 'users';

-- Changes in the last hour
SELECT * FROM turso_cdc WHERE change_time > unixepoch() - 3600;
```

## Decoding Binary Records

The `before`, `after`, and `updates` columns store data in a binary format. Use the built-in helper functions to decode them:

```sql theme={null}
SELECT
  change_type,
  table_name,
  id,
  bin_record_json_object(table_columns_json_array('users'), after) AS after_state,
  bin_record_json_object(table_columns_json_array('users'), before) AS before_state
FROM turso_cdc
WHERE table_name = 'users' AND change_type != 2;
```

| Function                                     | Description                                                                     |
| -------------------------------------------- | ------------------------------------------------------------------------------- |
| `table_columns_json_array(table_name)`       | Returns a JSON array of column names for a table (e.g. `["id","name","email"]`) |
| `bin_record_json_object(columns_json, blob)` | Decodes a binary record into a JSON object using the given column names         |

## Transactions

CDC respects transaction boundaries. Changes are only recorded when a transaction commits. If a transaction rolls back, no CDC entries are created.

All rows from the same transaction share the same `change_txn_id`, with a single COMMIT record (`change_type = 2`) at the end:

```sql theme={null}
BEGIN;
INSERT INTO users VALUES (1, 'Alice', 'alice@example.com');
INSERT INTO users VALUES (2, 'Bob', 'bob@example.com');
UPDATE users SET name = 'Charles' WHERE id = 1;
COMMIT;
-- All 3 changes + 1 COMMIT record share the same change_txn_id
```

## Schema Changes

CDC also tracks DDL operations (CREATE TABLE, DROP TABLE, CREATE INDEX, etc.) as changes to the `sqlite_schema` table:

```sql theme={null}
PRAGMA capture_data_changes_conn('full');

CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT);
-- Recorded in turso_cdc as a change to sqlite_schema
```

## Multiple Connections

Each connection has its own independent CDC configuration. Different connections can capture to different tables:

```sql theme={null}
-- Connection 1: Capture to 'audit_log'
PRAGMA capture_data_changes_conn('full,audit_log');

-- Connection 2: Capture to 'sync_queue'
PRAGMA capture_data_changes_conn('id,sync_queue');
```

## Example

```sql theme={null}
-- Create a table
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name TEXT,
    email TEXT
);

-- Enable full CDC
PRAGMA capture_data_changes_conn('full');

-- Make some changes
INSERT INTO users VALUES (1, 'Alice', 'alice@example.com');
INSERT INTO users VALUES (2, 'Bob', 'bob@example.com');
UPDATE users SET email = 'alice@newdomain.com' WHERE id = 1;
DELETE FROM users WHERE id = 2;

-- View the captured changes with decoded data
SELECT
  change_id,
  CASE change_type
    WHEN 1 THEN 'INSERT'
    WHEN 0 THEN 'UPDATE'
    WHEN -1 THEN 'DELETE'
    WHEN 2 THEN 'COMMIT'
  END AS operation,
  table_name,
  id,
  bin_record_json_object(table_columns_json_array('users'), after) AS after_state
FROM turso_cdc
WHERE change_type != 2
ORDER BY change_id;
```


# Changelog
Source: https://docs.turso.tech/tursodb/changelog





# Concurrent Writes
Source: https://docs.turso.tech/tursodb/concurrent-writes



Turso with default configuration only allows one connection to write at a time. With MVCC (Multi-Version Concurrency Control), multiple connections can write simultaneously. If two transactions modify the same data, one will receive a conflict error and must roll back and retry.

## Enable MVCC

Set the journal mode on your database connection:

```sql theme={null}
PRAGMA journal_mode = 'mvcc';
```

## BEGIN CONCURRENT

Use `BEGIN CONCURRENT` instead of `BEGIN` to start a transaction that allows other writers to proceed in parallel:

```sql theme={null}
BEGIN CONCURRENT;
-- your writes here
COMMIT;
```

If two transactions touch the same rows, one will receive a conflict error and must roll back and retry. Transactions that write to non-overlapping data proceed without conflict.

## Handling Conflicts

Your application must detect conflict errors and retry the transaction.

## Example

<Tabs>
  <Tab title="Rust">
    ```rust theme={null}
    fn is_retryable(e: &Error) -> bool {
        matches!(e, Error::Busy(_) | Error::BusySnapshot(_))
            || matches!(e, Error::Error(msg) if msg.contains("conflict"))
    }

    #[tokio::main]
    async fn main() -> Result<(), Error> {
        let tmp = NamedTempFile::new().expect("failed to create temp file");
        let db = Builder::new_local(tmp.path().to_str().unwrap())
            .build()
            .await?;

        let conn = db.connect()?;
        conn.pragma_update("journal_mode", "'mvcc'").await?;
        conn.execute("CREATE TABLE hits (val INTEGER)", ()).await?;

        let mut handles = Vec::new();
        for _ in 0..16 {
            let db = db.clone();
            handles.push(tokio::spawn(async move {
                let val = rand::rng().random_range(1..=100);
                let conn = db.connect()?;
                loop {
                    conn.execute("BEGIN CONCURRENT", ()).await?;
                    let result = conn
                        .execute(&format!("INSERT INTO hits VALUES ({val})"), ())
                        .await
                        .and(conn.execute("COMMIT", ()).await);
                    match result {
                        Ok(_) => return Ok::<_, Error>(val),
                        Err(ref e) if is_retryable(e) => {
                            let _ = conn.execute("ROLLBACK", ()).await;
                            tokio::task::yield_now().await;
                        }
                        Err(e) => {
                            let _ = conn.execute("ROLLBACK", ()).await;
                            return Err(e);
                        }
                    }
                }
            }));
        }

        for handle in handles {
            handle.await.expect("task panicked")?;
        }

        Ok(())
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    ```js theme={null}
    const DB_PATH = join(mkdtempSync(join(tmpdir(), "turso-mvcc-")), "hits.db");

    function isRetryable(err) {
      const msg = (err?.message ?? "").toLowerCase();
      return msg.includes("conflict") || msg.includes("busy");
    }

    async function writeWorker() {
      const val = Math.floor(Math.random() * 100) + 1;
      const db = await connect(DB_PATH);
      try {
        await db.exec("PRAGMA journal_mode = 'mvcc'");
        while (true) {
          await db.exec("BEGIN CONCURRENT");
          try {
            await db.exec(`INSERT INTO hits VALUES (${val})`);
            await db.exec("COMMIT");
            return val;
          } catch (err) {
            try { await db.exec("ROLLBACK"); } catch (_) {}
            if (!isRetryable(err)) throw err;
            await new Promise((r) => setImmediate(r));
          }
        }
      } finally {
        await db.close();
      }
    }

    const setup = await connect(DB_PATH);
    await setup.exec("PRAGMA journal_mode = 'mvcc'");
    await setup.exec("CREATE TABLE hits (val INTEGER)");
    await setup.close();

    await Promise.all(Array.from({ length: 16 }, () => writeWorker()));
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    def is_retryable(e: Exception) -> bool:
        msg = str(e).lower()
        return "conflict" in msg or "busy" in msg

    def write_worker(db_path: str) -> int:
        val = random.randint(1, 100)
        conn = turso.connect(db_path)
        try:
            conn.execute("PRAGMA journal_mode = 'mvcc'").fetchone()
            while True:
                conn.execute("BEGIN CONCURRENT")
                try:
                    conn.execute("INSERT INTO hits VALUES (?)", (val,))
                    conn.execute("COMMIT")
                    return val
                except Exception as e:
                    try:
                        conn.execute("ROLLBACK")
                    except Exception:
                        pass
                    if not is_retryable(e):
                        raise
        finally:
            conn.close()

    async def main() -> None:
        with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
            db_path = tmp.name

        setup = turso.connect(db_path)
        setup.execute("PRAGMA journal_mode = 'mvcc'").fetchone()
        setup.execute("CREATE TABLE hits (val INTEGER)")
        setup.commit()
        setup.close()

        tasks = [asyncio.to_thread(write_worker, db_path) for _ in range(16)]
        await asyncio.gather(*tasks)

    asyncio.run(main())
    ```
  </Tab>
</Tabs>

Try the full example:
[Rust](https://github.com/tursodatabase/turso/blob/main/bindings/rust/examples/concurrent_writes.rs) · [JavaScript](https://github.com/tursodatabase/turso/blob/main/examples/javascript/concurrent-writes/index.mjs) · [Python](https://github.com/tursodatabase/turso/blob/main/examples/python/concurrent_writes.py)


# Encryption
Source: https://docs.turso.tech/tursodb/encryption



Turso Database provides native encryption for data at rest using industry-standard AEAD algorithms. Each database page is encrypted individually, ensuring data integrity and security across all environments.

* **Page-level encryption**: Each 4 KiB page is encrypted individually with a unique nonce
* **AEAD algorithms**: Support for AEGIS-256 (fast, modern) and AES-GCM (NIST-approved)
* **Data integrity**: Built-in authentication tags prevent tampering and corruption
* **High performance**: As little as 6% read overhead and 14% write overhead
* **In-memory keys**: Encryption keys are never stored on disk

Turso Database with encryption is perfect for:

<CardGroup>
  <Card title="Fintech Applications" icon="credit-card">
    Meet regulatory requirements for sensitive financial data
  </Card>

  <Card title="Privacy-Focused Products" icon="user-shield">
    Build user trust with strong data protection
  </Card>

  <Card title="Healthcare Apps" icon="heart-pulse">
    Protect patient data and meet HIPAA compliance
  </Card>

  <Card title="AI/ML Applications" icon="brain">
    Secure training data and model outputs
  </Card>
</CardGroup>

## What's Encrypted

<CardGroup>
  <Card title="Encrypted" icon="lock">
    * All database pages with your data - The database file - Write-Ahead Log
      (WAL) file
  </Card>

  <Card title="Not Encrypted" icon="lock-open">
    * The database header (first 100 bytes)
  </Card>
</CardGroup>

## Generate an Encryption Key

Generate a secure encryption key in hexadecimal format. The key size depends on the cipher you choose:

<Tabs>
  <Tab title="256-bit (32 bytes)">
    For AEGIS-256 variants and AES-256-GCM:

    ```bash theme={null}
    openssl rand -hex 32
    ```

    Output example:

    ```
    2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d
    ```
  </Tab>

  <Tab title="128-bit (16 bytes)">
    For AEGIS-128 variants and AES-128-GCM:

    ```bash theme={null}
    openssl rand -hex 16
    ```

    Output example:

    ```
    5f3e2a8c9b1d4f6e7a2c8d4b9e1f3a6c
    ```
  </Tab>
</Tabs>

<Warning>
  **Store your key securely!** If you lose the encryption key, you will not be
  able to access your encrypted database. Keys are never stored on disk.
</Warning>

## Create an Encrypted Database

<Steps>
  <Step title="Launch with encryption">
    Use the `--experimental-encryption` flag and specify your cipher and key in the connection URI:

    ```bash theme={null}
    tursodb --experimental-encryption "file:encrypted.db?cipher=aegis256&hexkey=YOUR_HEX_KEY"
    ```

    Replace `YOUR_HEX_KEY` with the key you generated above.
  </Step>

  <Step title="Create and insert data">
    Once in the interactive shell, create a table and insert some data:

    ```sql theme={null}
    CREATE TABLE secrets (id INT, data TEXT);
    INSERT INTO secrets VALUES (1, 'sensitive information');
    INSERT INTO secrets VALUES (2, 'confidential data');
    ```
  </Step>

  <Step title="Verify encryption">
    Exit the shell (type `.quit`) and try to open the database without the key:

    ```bash theme={null}
    tursodb encrypted.db
    ```

    You won't be able to access the data. The database is encrypted on disk.
  </Step>
</Steps>

## Open an Encrypted Database

To access an existing encrypted database, provide the same cipher and key used during creation:

```bash theme={null}
tursodb --experimental-encryption "file:encrypted.db?cipher=aegis256&hexkey=YOUR_HEX_KEY"
```

Then query your data:

```sql theme={null}
SELECT * FROM secrets;
```

## Supported Ciphers

Turso Database supports multiple variants of two AEAD encryption algorithms, offering different trade-offs between performance and compatibility.

### AES-GCM

NIST-approved standard for compliance requirements. Widely supported across industries.

| Cipher          | Key Size           | URI Value   | Use Case                             |
| --------------- | ------------------ | ----------- | ------------------------------------ |
| **AES-128-GCM** | 128-bit (16 bytes) | `aes128gcm` | Compliance with 128-bit requirements |
| **AES-256-GCM** | 256-bit (32 bytes) | `aes256gcm` | Maximum security for compliance      |

### AEGIS

Modern, high-performance cipher family optimized for speed. **Recommended for most use cases.**

| Cipher          | Key Size           | URI Value    | Use Case                               |
| --------------- | ------------------ | ------------ | -------------------------------------- |
| **AEGIS-128L**  | 128-bit (16 bytes) | `aegis128l`  | Balanced performance, 128-bit security |
| **AEGIS-128X2** | 128-bit (16 bytes) | `aegis128x2` | 2x parallel processing                 |
| **AEGIS-128X4** | 128-bit (16 bytes) | `aegis128x4` | 4x parallel processing, maximum speed  |
| **AEGIS-256**   | 256-bit (32 bytes) | `aegis256`   | Balanced 256-bit security              |
| **AEGIS-256X2** | 256-bit (32 bytes) | `aegis256x2` | 2x parallel processing, 256-bit        |
| **AEGIS-256X4** | 256-bit (32 bytes) | `aegis256x4` | 4x parallel, maximum speed & security  |

<Info>
  **Choosing a cipher:**

  * Use `aegis128L` for 128 bit encryption, `aegis256` for 256 bit encryption (default recommendation)
  * Use the other AEGIS variants if your hardware supports it `aegis128X2` `aegis128X4` `aegis256x2` `aegis256x4`
  * For AES, use `aes128gcm` for 128 bit encryption, `aes256gcm` for 256 bit encryption
</Info>

## URI Parameters

When creating or opening an encrypted database, use the following URI format:

```
file:database.db?cipher=CIPHER&hexkey=HEX_KEY
```

| Parameter | Required | Description                  | Values                                                   |
| --------- | -------- | ---------------------------- | -------------------------------------------------------- |
| `cipher`  | Yes      | Encryption algorithm         | See [Supported Ciphers](#supported-ciphers)              |
| `hexkey`  | Yes      | Encryption key in hex format | 32-character hex (128-bit) or 64-character hex (256-bit) |

**Example URIs:**

```bash theme={null}
# AEGIS-256 with 256-bit key
file:encrypted.db?cipher=aegis256&hexkey=2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d

# AES-128-GCM with 128-bit key
file:encrypted.db?cipher=aes128gcm&hexkey=5f3e2a8c9b1d4f6e7a2c8d4b9e1f3a6c

# AEGIS-256X4 (max performance) with 256-bit key
file:encrypted.db?cipher=aegis256x4&hexkey=2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d
```

## Performance

Encryption overhead is minimal, especially for smaller databases:

* **Read operations**: \~6% overhead with AEGIS-256
* **Write operations**: \~14% overhead with AEGIS-256
* **Overall**: \~1-3% total time overhead for mixed workloads

For detailed benchmarks, see the [encryption announcement blog post](https://turso.tech/blog/introducing-fast-native-encryption-in-turso-database).

## Coming Soon

Future encryption features in development:

* **Key derivation from passphrases**: Use memorable passphrases instead of raw keys
* **Encrypt existing databases**: Migrate unencrypted databases to encrypted format
* **Key rotation**: Update encryption keys without data loss


# Quickstart
Source: https://docs.turso.tech/tursodb/quickstart



<Steps>
  <Step title="Install">
    <Tabs>
      <Tab title="macOS / Linux">
        ```bash theme={null}
        curl --proto '=https' --tlsv1.2 -LsSf https://github.com/tursodatabase/turso/releases/latest/download/turso_cli-installer.sh | sh
        ```
      </Tab>

      <Tab title="Windows">
        Run the following command in PowerShell:

        ```powershell theme={null}
        irm https://github.com/tursodatabase/turso/releases/latest/download/turso_cli-installer.ps1 | iex
        ```

        Alternatively, you can [download the Windows executable](https://github.com/tursodatabase/turso/releases/latest) and add it to your PATH.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Launch">
    Then launch the interactive shell:

    ```bash theme={null}
    tursodb
    ```

    This will start the Turso interactive shell where you can execute SQL statements:

    ```
    Turso
    Enter ".help" for usage hints.
    Connected to a transient in-memory database.
    Use ".open FILENAME" to reopen on a persistent database
    turso>
    ```
  </Step>

  <Step title="Execute">
    Now you can execute SQL statements in the interactive shell:

    <AccordionGroup>
      <Accordion title="Create a table">
        Create a table for users:

        ```sql theme={null}
        CREATE TABLE users (id INT, username TEXT);
        ```
      </Accordion>

      <Accordion title="Insert data">
        Insert some data into the users table:

        ```sql theme={null}
        INSERT INTO users VALUES (1, 'alice');
        INSERT INTO users VALUES (2, 'bob');
        ```
      </Accordion>

      <Accordion title="Query data">
        Query all users from the table:

        ```sql theme={null}
        SELECT * FROM users;
        ```

        This will return:

        ```
        1|alice
        2|bob
        ```
      </Accordion>
    </AccordionGroup>
  </Step>
</Steps>

## Experimental Features

You can enable experimental features when launching `tursodb` using various flags such as `--experimental-encryption`, `--experimental-mvcc`, `--experimental-strict`, and `--experimental-views`.

**Note:** These features are not production ready, so do not use them for critical data right now.

For a complete list of available flags and options, see the [Turso manual](https://github.com/tursodatabase/turso).