tursodatabase/turso · error · Error
remoteWritesExperimental requires a non-null URL
Error message
remoteWritesExperimental requires a non-null URL
What it means
Thrown from the Database constructor when remoteWritesExperimental is enabled and the url option is a lazy provider function (url?: string | (() => string | null)) whose call returns null. The RemoteWriter that forwards writes to the remote server needs a concrete URL at construction time, so resolveUrl() invokes the provider immediately and throws when it yields null. Note the asymmetry: the plain sync engine tolerates a null lazy URL (it just sets bootstrapIfEmpty: false), but remoteWritesExperimental does not.
Source
Thrown at bindings/javascript/sync/packages/wasm/promise-default.ts:45
async write(path: string, data: Buffer | Uint8Array): Promise<void> {
values.set(path, data);
}
}
};
async function init(): Promise<Worker> {
await initThreadPool();
if (MainWorker == null) {
throw new Error("panic: MainWorker is not initialized");
}
return MainWorker;
}
function resolveUrl(url: string | (() => string | null)): string {
if (typeof url === "function") {
const resolved = url();
if (resolved == null) {
throw new Error("remoteWritesExperimental requires a non-null URL");
}
return resolved;
}
return url;
}
class Database extends DatabasePromise {
#runner: Runner;
#engine: any;
#io: ProtocolIo;
#guards: SyncEngineGuards;
#worker: Worker | null;
#remoteWriter: RemoteWriter | null = null;
#db: any;
constructor(opts: DatabaseOpts) {
if (opts.url == null) {
const db = new NativeDatabase(opts.path, { tracing: opts.tracing, experimental: opts.experimental }) as any;
super(View on GitHub (pinned to bad083fafb)
Solutions
- Make sure the URL source is populated before construction: set TURSO_DATABASE_URL in the environment (or .env.local) for the failing environment and confirm it is present at runtime, not only on your machine.
- Pass a concrete string URL when using remoteWritesExperimental: url: process.env.TURSO_DATABASE_URL! (assert non-null yourself before constructing).
- Enable remote writes conditionally: remoteWritesExperimental: url != null, so construction degrades to local/sync-only mode instead of crashing when the URL is absent.
- If you intended offline-first lazy sync without remote writes, remove remoteWritesExperimental; the sync engine alone accepts a null lazy URL.
Example fix
// before — throws in the constructor when the env var is missing at that moment
const db = new Database({
path: "app.db",
remoteWritesExperimental: true,
url: () => process.env.TURSO_DATABASE_URL ?? null,
});
// after — resolve the URL first, enable remote writes only when it exists
const url = process.env.TURSO_DATABASE_URL ?? null;
const db = new Database({
path: "app.db",
url: url ?? undefined,
remoteWritesExperimental: url != null,
}); Defensive patterns
Strategy: validation
Validate before calling
import { Database, type DatabaseOpts } from "@tursodatabase/sync-wasm";
const lazyUrl = () => process.env.TURSO_DATABASE_URL ?? null;
// Resolve the URL BEFORE constructing so the constructor can never throw.
const url = lazyUrl();
const opts: DatabaseOpts = {
path: "app.db",
url: url ?? undefined,
remoteWritesExperimental: url != null, // commit to remote writes only with a real URL
};
const db = new Database(opts); Type guard
type LazyUrl = string | (() => string | null);
function resolvesToUrl(url: LazyUrl | undefined): url is string {
if (typeof url === "string") return url.length > 0;
if (typeof url === "function") return url() != null;
return false;
}
// use: if (resolvesToUrl(opts.url)) { /* safe to set remoteWritesExperimental */ } Try / catch
try {
db = new Database({ path, remoteWritesExperimental: true, url: lazyUrl });
} catch (e) {
if (e instanceof Error && e.message.includes("requires a non-null URL")) {
// Configuration problem, not a transient failure — fail loudly with context.
throw new Error("TURSO_DATABASE_URL must be set before enabling remoteWritesExperimental");
}
throw e; // never swallow unrelated constructor errors
} Prevention
- Never combine remoteWritesExperimental with an unguarded lazy url provider; resolve the value first and pass a plain string.
- Assert required environment variables at process start (fail fast) instead of inside closures evaluated at construction time.
- Remember bundler variants: the identical error surfaces from promise-bundle.ts, promise-default.ts or promise-turbopack-hack.ts depending on your bundler.
- Add a CI test that constructs the Database with remoteWritesExperimental in an environment without the URL variable, so a missing-secret crash is caught before deploy.
When it happens
Trigger: new Database({ path, remoteWritesExperimental: true, url: () => process.env.TURSO_DATABASE_URL ?? null }) where the env var (or any config store the closure reads) is unset at the moment the Database is constructed. Any lazy provider returning null (KV read, platform binding attached late, not-yet-loaded config) hits the same path. The same code ships as promise-bundle.ts, promise-default.ts and promise-turbopack-hack.ts, so the stack trace names whichever bundler entry point your build selected.
Common situations: Next.js/Vite/Turbopack builds where process.env values are inlined at build time and the variable was not configured for that environment; serverless/edge runtimes where the binding is attached after module evaluation; CI or preview deployments missing the TURSO_DATABASE_URL secret; copy-pasting the lazy-url pattern from plain-sync examples while also enabling remoteWritesExperimental.
Related errors
- remoteWritesExperimental requires a non-null URL
- remoteWritesExperimental requires a non-null URL
- remoteWritesExperimental requires a non-null URL
- sync is disabled as database was opened without sync support
- transactionAsync is not supported with remoteWritesExperimen
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/f4e216c2bf192486.
Report an issue: GitHub.