Database Binding

PostgreSQL database access from your workers.


Usage

export default {
  async fetch(request, env, ctx) {
    const results = await env.DB.query(
      'SELECT * FROM users WHERE id = $1',
      [1]
    );

    return new Response(JSON.stringify(results), {
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

API

query(sql, params?)

Execute a SQL query with optional parameters.

// Without parameters
const users = await env.DB.query('SELECT * FROM users');

// With parameters (use $1, $2, $3...)
const user = await env.DB.query(
  'SELECT * FROM users WHERE id = $1',
  [userId]
);

Parameters:

ParameterTypeDescription
sqlstringSQL query with $1, $2… placeholders
paramsany[]Optional array of values to bind

Returns: Promise<any[]> - Array of row objects


Parameter Binding

Use PostgreSQL-style placeholders ($1, $2, $3…). Parameters are 1-indexed.

// Single parameter
await env.DB.query('SELECT * FROM posts WHERE id = $1', [42]);

// Multiple parameters
await env.DB.query(
  'SELECT * FROM posts WHERE author = $1 AND status = $2',
  ['john', 'published']
);

// Array parameter
await env.DB.query('SELECT * FROM posts WHERE id = ANY($1)', [[1, 2, 3]]);

// Explicit cast when the column type is ambiguous
await env.DB.query(
  'INSERT INTO scores (value) VALUES ($1::int)',
  [score]
);

Note: Parameters keep their JavaScript type: null, booleans, numbers and strings map to the matching SQL types, and an array maps to a SQL array. Binary values are passed as { $bytes: '<base64>' }.


Examples

CRUD Operations

// Create
const newPost = await env.DB.query(
  'INSERT INTO posts (title, body) VALUES ($1, $2) RETURNING *',
  ['Hello', 'World']
);

// Read
const posts = await env.DB.query('SELECT * FROM posts');

// Read with filter
const post = await env.DB.query(
  'SELECT * FROM posts WHERE id = $1',
  [postId]
);

// Update
const updated = await env.DB.query(
  'UPDATE posts SET title = $1 WHERE id = $2 RETURNING *',
  ['Updated Title', postId]
);

// Delete
await env.DB.query('DELETE FROM posts WHERE id = $1', [postId]);

Aggregations

const stats = await env.DB.query(`
  SELECT
    COUNT(*) as total,
    AVG(score)::int as avg_score
  FROM games
  WHERE created_at > NOW() - INTERVAL '7 days'
`);

Joins

const postsWithAuthors = await env.DB.query(`
  SELECT
    p.id,
    p.title,
    u.name as author_name
  FROM posts p
  JOIN users u ON p.author_id = u.id
  WHERE p.status = $1
  ORDER BY p.created_at DESC
  LIMIT $2
`, ['published', 10]);

Configuration

Add a database binding in the dashboard:

  1. Go to your worker settings
  2. Click Add Binding
  3. Select Database
  4. Choose your database and set the binding name (e.g., DB)

With the CLI:

ow env bind my-env DB my-database --type database
ow workers link my-worker my-env

Limits

LimitValue
Query timeout30 seconds
Max result size10 MB

Differences from Cloudflare D1

OpenWorkers exposes a single query() method instead of D1’s prepared-statement chain:

D1OpenWorkers
env.DB.prepare(sql).bind(...).all()env.DB.query(sql, params)
env.DB.prepare(sql).bind(...).first()(await env.DB.query(sql, params))[0]
env.DB.prepare(sql).bind(...).run()env.DB.query(sql, params)
? placeholders$1, $2… placeholders