Prerequisites
- The
telnyx-edgeCLI, authenticated (telnyx-edge auth api-key set <YOUR_API_KEY>), on a release that includesstorage sqldb. - A Telnyx API key, if calling the REST API directly.
- An Edge Compute function project with a
telnyx.toml(orfunc.toml) manifest, and Node.js fornpm.
1. Create a Database
A database is an isolated SQLite database with its own schema. Create one with the CLI or the API.- CLI
- API
id — a UUID, and the only durable handle. Names are not resolution keys:
409, and the API rejects an empty or upper-case name with 422 (the CLI stops an empty name before any request is made). Hyphen placement is not constrained — a leading hyphen is accepted — but avoid one: that name then reads as a flag when passed to CLI commands, so you are left addressing the database by id. Keep names short: past 255 characters the create fails with a 500 instead of a validation error.
A new database starts at
status: "pending". SQL sent to it from outside a function — execute, migrations, or the REST query endpoint — returns 409 ("Database is not ready (status: pending)") until provisioning finishes, typically in 2 to 11 seconds. If scripting, poll until the status is provision_ok rather than sleeping a fixed interval:2. Create the Schema
Create your tables from the terminal, before any code exists.execute takes exactly one of --command or --file, and --remote is required — there is no local emulation.
--json to get the raw result object instead of the rendered table. For scripting note one wrinkle: the current CLI writes the JSON to stderr, so capture it with 2>&1 >/dev/null rather than piping stdout.
execute is for one-off statements and inspection. For schema changes intended to be kept and replayed on another database, use Migrations — numbered .sql files with a tracking table inside the database itself.3. Bind the Database to a Function
Declare the database in the function’s manifest. The manifest itself — and the[edge_compute] identity in it — comes from the function, not from this guide: telnyx-edge new-func registers the function and writes func_id when it scaffolds a project. Starting from nothing, run telnyx-edge new-func -n links-api -l ts first and work in the project it creates (it writes a classic func.toml; for the umbrella telnyx.toml form shown here, copy the generated [edge_compute] block across). The only part this step adds is the [storage.sqldb.DB] block.
The example below is a telnyx.toml; the [storage.sqldb.<NAME>] block parses the same way in a func.toml project, though a classic func.toml project’s entry point has a different shape from the export default { fetch } handler in step 5:
DB here — is a name of your choosing, and it is what the code sees on env: this manifest produces env.DB. It must be a valid identifier (letters, digits, underscores; no leading digit, no __) and unique across every binding in the manifest.
id must be the database UUID. Binding by name, or creating a database from the manifest, is rejected before deploy. Declare as many [storage.sqldb.<NAME>] blocks as needed; two bindings that carry different ids are two different databases, and neither can see the other’s tables.
4. Generate Types
telnyx-edge types reads the manifest — plus the @telnyx/edge-runtime version declared in package.json — and nothing else: it runs offline and needs no authentication. Re-run it after adding, renaming, or removing a binding.
Types are for the compiler only. The binding itself resolves at runtime from the manifest, so a stale telnyx-env.d.ts does not change what the deployed function sees — in either direction.
5. Write the Function
prepare() is synchronous and returns a statement. .bind() returns a new statement with the values attached — it never mutates the one it was called on. The terminal calls are async, and each resolves to a different shape: run() and all() give { results, success, meta }, first() gives a single row or null (first("col") gives that column’s value), and raw() gives an array of value-arrays. run() and all() are the same call: both return rows, including the rows a RETURNING clause produces.
Bind every value that comes from a request with ? placeholders rather than building SQL by concatenation. Ordered ?NNN placeholders work too.
A failing statement — a syntax error, a UNIQUE violation, a missing table — rejects with a plain Error. There is no typed error class or error-code taxonomy: the SQLite message is nested inside a longer transport string on error.message.
6. Ship and Call It
ship bundles the project, uploads it, and prints the function host — {func-name}-{func-id-prefix}.telnyxcompute.com, without a scheme; add https:// when calling it.
7. Query It Without Deploying
The rows the function wrote are readable from the terminal against the same database:env.DB, reached through the same primary — the CLI is not a second writer, and no schema or data is duplicated. Use it to inspect state, apply a fix-up statement, or seed a table without redeploying.
8. Query It From Any Language
The CLI is a wrapper over one HTTP endpoint. Anything that can make a request can reach the same database — a Go or Python function, a backfill script, a CI job:sql. Any other key — including params — is rejected with 400 Invalid request body. The response carries results and nothing else: no meta, no row counts, no duration. A script may hold several ;-separated statements, and the rows returned are the last statement’s.
Binary columns come back as JSON arrays of byte values ([9, 8, 7]), not base64. Errors arrive in the standard Telnyx envelope: a SQL failure is a 422 whose detail embeds the SQLite message inside the same longer transport string described in step 5, a database that has not finished provisioning is a 409, and an unknown or other-organization id is a 404.
Next Steps
- How SQL Databases Work — the shared-database model, consistency, and what
env.DBis talking to - Migrations — numbered
.sqlfiles and the in-database tracking table - Runtime API — the full
SqlDatabasesurface, includingbatch()andexec() - CLI Commands —
create,list,get,delete,execute,migrations - Limits — database and value size, bound parameters, and query performance
- Bindings — how the
envbinding surface works across storage products