Skip to main content
Execute SQL queries and database operations with automatic binding resolution.
For simple key-value or object storage, see the storage operation.

Configuration

config:
  backend: string    # d1, vectorize, hyperdrive, [plugin-name]
  operation: string  # query, execute, transaction, batch
  [backend-specific options]

D1 (SQL Database)

Serverless SQLite database for relational data with full SQL support.

Query Operation

operations:
  - name: get-user
    operation: data
    config:
      backend: d1
      binding: DB
      operation: query
      sql: SELECT * FROM users WHERE id = ?
      params: [${input.user_id}]
Output:
{
  results: any[]        // Query results
  success: boolean
  meta: {
    duration: number    // Query duration (ms)
    rows_read: number
    rows_written: number
  }
}

Parameterized Queries

Always use parameterized queries to prevent SQL injection:
operations:
  - name: search-users
    operation: data
    config:
      backend: d1
      binding: DB
      operation: query
      sql: |
        SELECT * FROM users
        WHERE email LIKE ? AND status = ?
        ORDER BY created_at DESC
        LIMIT ?
      params:
        - %@example.com
        - active
        - 10

INSERT with RETURNING

operations:
  - name: create-user
    operation: data
    config:
      backend: d1
      binding: DB
      operation: execute
      sql: |
        INSERT INTO users (name, email, status)
        VALUES (?, ?, ?)
        RETURNING id
      params:
        - ${input.name}
        - ${input.email}
        - active

UPDATE Operation

operations:
  - name: update-user
    operation: data
    config:
      backend: d1
      binding: DB
      operation: execute
      sql: |
        UPDATE users
        SET name = ?, email = ?, updated_at = CURRENT_TIMESTAMP
        WHERE id = ?
      params:
        - ${input.name}
        - ${input.email}
        - ${input.id}

DELETE Operation

operations:
  - name: delete-user
    operation: data
    config:
      backend: d1
      binding: DB
      operation: execute
      sql: DELETE FROM users WHERE id = ?
      params: [${input.id}]

Transactions

Execute multiple statements atomically:
operations:
  - name: transfer-funds
    operation: data
    config:
      backend: d1
      binding: DB
      operation: transaction
      statements:
        - sql: UPDATE accounts SET balance = balance - ? WHERE id = ?
          params: [${input.amount}, ${input.from_account}]
        - sql: UPDATE accounts SET balance = balance + ? WHERE id = ?
          params: [${input.amount}, ${input.to_account}]
        - sql: INSERT INTO transactions (from_id, to_id, amount) VALUES (?, ?, ?)
          params: [${input.from_account}, ${input.to_account}, ${input.amount}]

Batch Operations

Execute multiple queries (non-atomic):
operations:
  - name: bulk-insert
    operation: data
    config:
      backend: d1
      binding: DB
      operation: batch
      statements:
        - sql: INSERT INTO users (name) VALUES (?)
          params: ["Alice"]
        - sql: INSERT INTO users (name) VALUES (?)
          params: ["Bob"]
        - sql: INSERT INTO users (name) VALUES (?)
          params: ["Charlie"]

Vectorize (Vector Database)

Vector search for semantic similarity, embeddings, and AI applications.

Insert Vectors

operations:
  - name: store-embedding
    operation: data
    config:
      backend: vectorize
      binding: VECTORIZE
      operation: insert
      vectors:
        - id: doc-${input.id}
          values: ${previous.generate-embedding.vector}
          metadata:
            text: ${input.text}
            category: ${input.category}

Query by Vector

operations:
  - name: search-similar
    operation: data
    config:
      backend: vectorize
      binding: VECTORIZE
      operation: query
      vector: ${previous.query-embedding.vector}
      topK: 10
      filter:
        category: ${input.category}
Output:
{
  matches: Array<{
    id: string
    score: number        // Similarity score
    metadata: object
  }>
}

Metadata Filtering

operations:
  - name: filtered-search
    operation: data
    config:
      backend: vectorize
      binding: VECTORIZE
      operation: query
      vector: ${input.queryVector}
      topK: 5
      filter:
        category: "documentation"
        lang: "en"
        date: { $gte: "2024-01-01" }

External Databases via Plugins

Hyperdrive (Postgres, MySQL)

Connect to external databases with automatic connection pooling and edge caching. Configuration options:
config:
  backend: hyperdrive
  binding: string           # Wrangler binding name
  databaseType: string      # postgres, mysql, or mariadb (default: postgres)
  schema: string            # Schema name (Postgres only)
  readOnly: boolean         # Prevent write operations (default: false)
  operation: string         # query, get, put, delete, list
PostgreSQL Example:
operations:
  - name: query-postgres
    operation: data
    config:
      backend: hyperdrive
      binding: PRODUCTION_DB
      databaseType: postgres
      operation: query
    input:
      query: SELECT * FROM users WHERE email = $1
      params: [${input.email}]
MySQL Example:
operations:
  - name: query-mysql
    operation: data
    config:
      backend: hyperdrive
      binding: ANALYTICS_DB
      databaseType: mysql
      operation: query
    input:
      query: SELECT * FROM orders WHERE user_id = ?
      params: [${input.userId}]
Output:
{
  rows: any[]              // Query results
  metadata: {
    executionTime: number  // Query duration (ms)
    columns: string[]      // Column names
    rowCount: number       // Number of rows returned
    rowsAffected?: number  // For write operations
  }
  success: boolean
}
Wrangler configuration:
[[hyperdrive]]
binding = "PRODUCTION_DB"
id = "your-hyperdrive-id-production"

[[hyperdrive]]
binding = "ANALYTICS_DB"
id = "your-hyperdrive-id-analytics"
Hyperdrive automatically handles connection pooling, so you don’t need to manage connections manually. This dramatically reduces latency compared to direct database connections.

Supabase Plugin

operations:
  - name: query-supabase
    operation: data
    config:
      backend: supabase
      url: ${env.SUPABASE_URL}
      key: ${env.SUPABASE_KEY}
      operation: query
      table: users
      filter:
        email: ${input.email}

Neon Plugin

operations:
  - name: query-neon
    operation: data
    config:
      backend: neon
      connectionString: ${env.NEON_CONNECTION_STRING}
      operation: query
      sql: SELECT * FROM users WHERE id = $1
      params: [${input.userId}]

PlanetScale Plugin

operations:
  - name: query-planetscale
    operation: data
    config:
      backend: planetscale
      host: ${env.PLANETSCALE_HOST}
      username: ${env.PLANETSCALE_USERNAME}
      password: ${env.PLANETSCALE_PASSWORD}
      operation: query
      sql: SELECT * FROM orders WHERE user_id = ?
      params: [${input.userId}]

Best Practices

SQL Injection Prevention

Never concatenate user input:
# DANGEROUS - DO NOT DO THIS
sql: SELECT * FROM users WHERE email = '${input.email}'
Always use parameterized queries:
# SAFE - DO THIS
sql: SELECT * FROM users WHERE email = ?
params: [${input.email}]

Connection Pooling

For external databases, use Hyperdrive for automatic connection pooling:
# Without Hyperdrive: New connection each request (slow)
backend: postgres
connectionString: ${env.DATABASE_URL}

# With Hyperdrive: Connection pooling (fast)
backend: hyperdrive
binding: HYPERDRIVE

Transaction Handling

Use transactions for operations that must succeed or fail together:
operations:
  # ✅ Atomic: Both succeed or both fail
  - name: atomic-update
    operation: data
    config:
      backend: d1
      operation: transaction
      statements:
        - sql: UPDATE accounts SET balance = balance - ? WHERE id = ?
        - sql: UPDATE accounts SET balance = balance + ? WHERE id = ?

  # ❌ Non-atomic: First might succeed, second might fail
  - name: update-from
    operation: data
    config:
      backend: d1
      operation: execute
      sql: UPDATE accounts SET balance = balance - ? WHERE id = ?

  - name: update-to
    operation: data
    config:
      backend: d1
      operation: execute
      sql: UPDATE accounts SET balance = balance + ? WHERE id = ?

Error Handling

operations:
  - name: query-user
    operation: data
    config:
      backend: d1
      operation: query
      sql: SELECT * FROM users WHERE id = ?
      params: [${input.userId}]
    onError:
      - name: log-error
        operation: think
        config:
          prompt: |
            Database query failed: ${error.message}
            Log this error and notify the team.

Choosing the Right Backend

BackendUse CaseLatencyDurabilityCost
D1Serverless SQLiteLow (edge)HighLow
VectorizeVector search, AILow (edge)HighMedium
HyperdriveExternal Postgres/MySQLMedium (pooled)DependsMedium
SupabaseManaged Postgres + AuthMediumHighMedium
NeonServerless PostgresLow-MediumHighMedium
PlanetScaleServerless MySQLLow-MediumHighMedium

When to Use data vs storage

Use data when you need:
  • ✅ SQL queries
  • ✅ Relational data
  • ✅ Transactions
  • ✅ Complex queries with JOINs
  • ✅ Vector search
Use storage when you need:
  • ❌ Simple key-value lookups
  • ❌ Object/file storage
  • ❌ Temporary caching
  • ❌ Global edge caching
See storage operation for key-value and object storage.
  • storage - Key-value and object storage
  • http - HTTP requests
  • think - AI reasoning