Skip to main content

Raw SQL

Execute raw SQL queries directly against a collection's Durable Object SQLite database. This gives you full SQL power for complex queries, aggregations, and data operations that go beyond the standard CRUD API.

Endpoint

POST /api/sql
Headers: X-EdgeBase-Service-Key: <key>

Raw SQL requires Service Key authentication. It bypasses access rules entirely, so it is intended for server-side use only.

Language Coverage

Raw SQL is available in all Admin SDKs.

Request

{
"namespace": "shared",
"id": "optional-instance-id",
"table": "posts",
"sql": "SELECT * FROM posts WHERE status = ? ORDER BY createdAt DESC LIMIT 10",
"params": ["published"]
}
FieldRequiredDescription
namespaceYesThe database namespace (e.g., shared, workspace)
idNoThe specific DO instance ID. Omit for the default instance
tableYesThe target table name. Used for scope validation with scoped Service Keys
sqlYesThe SQL statement to execute. Supports any valid SQLite syntax
paramsNoArray of bind parameters for ? placeholders

Response

{
"rows": [
{ "id": "01abc...", "title": "Hello World", "status": "published", "createdAt": "2026-01-15T10:00:00Z" }
],
"changes": 0,
"lastRowId": 0
}
FieldDescription
rowsArray of result objects (for SELECT queries)
changesNumber of rows affected (for INSERT/UPDATE/DELETE)
lastRowIdThe rowid of the last inserted row

Parameterized Queries

Always use ? bind parameters to prevent SQL injection:

{
"namespace": "shared",
"table": "posts",
"sql": "SELECT * FROM posts WHERE authorId = ? AND status = ?",
"params": ["user-123", "published"]
}
danger

Never interpolate user input directly into SQL strings. Always use the params array.

Admin SDK Usage

The Admin SDK exposes a top-level sql(...) helper across every Admin SDK.

import { createAdminClient } from '@edgebase/admin';

const admin = createAdminClient('https://your-app.example.com', {
serviceKey: process.env.EDGEBASE_SERVICE_KEY!,
});

const rows = await admin.sql(
'shared',
undefined,
'SELECT * FROM posts WHERE status = ? ORDER BY createdAt DESC LIMIT 10',
['published']
);

Inside App Functions

// functions/generateReport.ts
import { defineFunction } from '@edgebase/shared';

export default defineFunction({
trigger: { type: 'http', method: 'GET', path: '/report/top-authors' },
handler: async ({ admin }) => {
const result = await admin.sql(
'shared',
undefined,
`SELECT authorId, COUNT(*) as postCount, SUM(views) as totalViews
FROM posts
WHERE status = 'published'
GROUP BY authorId
ORDER BY totalViews DESC
LIMIT 20`
);
return result.rows;
},
});

Use Cases

Complex Aggregations

SELECT
strftime('%Y-%m', createdAt) AS month,
COUNT(*) AS count,
AVG(views) AS avgViews
FROM posts
WHERE status = 'published'
GROUP BY month
ORDER BY month DESC

Multi-Table Joins

SELECT p.title, p.views, u.displayName AS author
FROM posts p
JOIN users u ON p.authorId = u.id
WHERE p.status = 'published'
ORDER BY p.views DESC
LIMIT 10

Full-Text Search with Ranking

SELECT p.*, rank
FROM posts_fts
JOIN posts p ON posts_fts.rowid = p.rowid
WHERE posts_fts MATCH ?
ORDER BY rank
LIMIT 20

Data Migration

UPDATE posts SET category = 'general' WHERE category IS NULL

Limitations

No Cross-DO Queries

Each Durable Object has its own independent SQLite database. A single SQL query runs against one DO instance only. You cannot join data across different namespaces or instance IDs in a single query.

// Each call targets a single DO
const sharedPosts = await admin.sql('shared', undefined, 'SELECT * FROM posts');
const workspaceDocs = await admin.sql('workspace', 'ws-1', 'SELECT * FROM documents');
// These are two separate databases — no cross-join is possible

Security

Raw SQL bypasses all access rules. Access is controlled entirely by the Service Key:

  • A root-tier key can execute any SQL on any table
  • A scoped key requires the sql:table:{table}:exec scope to match the table field in the request

The table field in the request body is used for scope checking only. The SQL statement itself is not parsed or restricted, so a query referencing multiple tables will still execute as long as the scope matches the declared table.

caution

Because raw SQL runs with full database privileges, treat it with the same care as direct database access. Validate inputs, use parameterized queries, and restrict Service Key distribution.