Skip to content

Binary Data (VARBINARY) ​

SQL Server VARBINARY(MAX) columns store raw binary data. The driver transports binary across the FFI boundary as base64 strings, so both writing and reading require a small encoding step.

Writing Binary Data ​

Pass a Uint8Array with type: "varbinary". The type hint is required — without it the value is treated as an NVARCHAR string:

ts
const bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // "Hello"

await cn.execute("INSERT INTO Files (data) VALUES (@data)", {
  data: { value: bytes, type: "varbinary" },
});

Reading a file and storing it:

ts
import { readFile } from "node:fs/promises";

const bytes = new Uint8Array(await readFile("./upload.bin"));

await cn.execute("INSERT INTO Files (name, data) VALUES (@name, @data)", {
  name: "upload.bin",
  data: { value: bytes, type: "varbinary" },
});

Using Buffer (Node.js style) ​

Buffer is a subclass of Uint8Array, so it works anywhere a Uint8Array is accepted. Pass it with type: "varbinary" the same way:

ts
import { readFileSync } from "node:fs";

const buf = readFileSync("./upload.bin"); // Buffer

await cn.execute("INSERT INTO Files (data) VALUES (@data)", {
  data: { value: buf, type: "varbinary" },
});

In Deno and Bun, Buffer is available via node:buffer if needed, but plain Uint8Array is preferred in those runtimes.

Reading Binary Data ​

VARBINARY columns are returned as base64-encoded strings. Declare the column type as string and decode after reading:

ts
// Declare the column as string — that's what comes back from JSON
const row = await cn.querySingle<{ data: string }>(
  "SELECT data FROM Files WHERE id = @id",
  { id: 1 },
);

// Decode base64 → Uint8Array (works in Deno, Node, and Bun)
const bytes = Uint8Array.from(atob(row.data), (c) => c.charCodeAt(0));

Nullable columns ​

ts
const row = await cn.querySingle<{ data: string | null }>(
  "SELECT data FROM Files WHERE id = @id",
  { id: 1 },
);

if (row.data !== null) {
  const bytes = Uint8Array.from(atob(row.data), (c) => c.charCodeAt(0));
}

Using Buffer for decoding (Node.js style) ​

Buffer.from(base64, "base64") is a convenient Node.js idiom and produces a Buffer (which is a Uint8Array):

ts
// Node.js / Bun
const buf = Buffer.from(row.data, "base64");

// Deno (import Buffer from node:buffer)
import { Buffer } from "node:buffer";
const buf = Buffer.from(row.data, "base64");

All three runtimes support the global atob() / btoa() functions, so the Uint8Array.from(atob(...)) pattern is always portable.

Runtime Differences ​

The binary API is identical across all three runtimes. There are no runtime-specific code paths in the driver for binary data.

FeatureDenoNode.jsBun
Uint8Array input✅✅✅
Buffer inputvia node:buffer✅ (global)✅ (global)
atob / btoa globals✅✅ (v16+)✅
Buffer.from(b64, "base64")via node:buffer✅✅
Output as base64 string✅✅✅

Full Round-Trip Example ​

ts
import * as mssql from "@tsdrivers/mssql";

const TEST = new TextEncoder().encode("Hello, world! 🌍");

await using cn = await mssql.connect(connectionString);

// Write
await cn.execute("INSERT INTO Blobs (data) VALUES (@data)", {
  data: { value: TEST, type: "varbinary" },
});

// Read back
const row = await cn.querySingle<{ data: string }>(
  "SELECT TOP 1 data FROM Blobs ORDER BY id DESC",
);
const roundTripped = Uint8Array.from(atob(row.data), (c) => c.charCodeAt(0));

console.log(new TextDecoder().decode(roundTripped)); // "Hello, world! 🌍"

Empty VARBINARY ​

Empty Uint8Array values are supported — the driver automatically generates a CAST(0x AS VARBINARY(MAX)) literal:

ts
// ✅ Works — empty binary is handled correctly
await cn.execute("INSERT INTO T (data) VALUES (@data)", {
  data: { value: new Uint8Array(0), type: "varbinary" },
});

Large Data ​

Binary data is serialized through JSON (base64-encoded), which works well for BLOBs up to ~10 MB. For larger data, use streaming:

ApproachPlatformBest For
Standard query (in-memory)AllUp to ~10 MB
Blob StreamingAllLarge reads/writes via Readable/Writable streams
FILESTREAMWindows onlyVery large files with native file handle I/O

See Blob Streaming for the full API — it provides node:stream and Web Standard stream interfaces that work identically to the FILESTREAM API but on all platforms.