TypeScript bridge

Generate a Node.js SDK, call BAML functions, and read streamed results.

Call BAML from a Node.js application using a generated baml_sdk and the @boundaryml/baml-bridge runtime. These examples use Canary and the generator’s default preserve-case naming convention.

Generate the SDK

Start with a BAML project, then run these commands from its root:

baml generate add typescript/node
baml generate
npm init -y
npm pkg set type=module
npm install @boundaryml/baml-bridge
npm install --save-dev tsx typescript @types/node

The runtime uses ES modules. These commands initialize a new Node.js package; in an existing application, use its ES-module configuration.

Run generate add once. It adds a generator to baml.toml; baml generate writes baml_sdk/ beside that file. Regenerate after changing BAML source.

Match the runtime package version to the toolchain version printed by baml --version. If npm’s default release differs, install that exact version with npm install @boundaryml/baml-bridge@<version>.

Call a function

With return_number from Get started, create app.ts beside baml.toml:

import { return_number } from "./baml_sdk/index.js"

const value = return_number(42)
console.log(value)

Run it with npx tsx app.ts. It prints 42.

return_number runs synchronously and returns the value directly. Import from baml_sdk/index.js so the SDK initializes its runtime and type mappings.

Call asynchronously

The Node.js generator also exports an _async variant of each function. It returns a promise:

import { return_number_async } from "./baml_sdk/index.js"

async function main() {
  const value = await return_number_async(42)
  console.log(value)
}

main().catch(console.error)

Replace app.ts with this version and run npx tsx app.ts again. It also prints 42.

Use the async variant when other JavaScript work needs to continue during the call, such as when a Node.js server waits for a model response. The synchronous variant blocks the JavaScript thread until the call finishes.

Compatibility

ConcernTypeScript behavior
CallsNode.js exports synchronous functions and promise-returning _async variants.
NamesThe default preserve-case convention keeps BAML names. Async variants add _async.
RegenerationGenerated files are replaced; application edits belong elsewhere.

Type mappings

BAMLTypeScriptNotes
stringstringText values map directly.
int / floatnumberKeep integer constraints in mind at runtime boundaries.
T[]T[]Element types remain explicit.
T?T | nullDo not use truthiness when zero or empty text is valid.
classGenerated classFields preserve their declared names and types.
enumGenerated enumImport the generated symbol instead of duplicating literals.

Call a model

Add the BAML source from Classify support tickets, run baml generate, and set OPENAI_API_KEY in the server’s environment:

import { classify_message_async, type Category } from "./baml_sdk/index.js"

export async function classify(input: string): Promise<Category> {
  return classify_message_async(input)
}

Streaming

For an LLM function, the generator also exports a $stream_async function. Read partial values with nextAsync() until it returns ai.stream.Done, then call finalAsync() for the complete result:

import { ai, classify_message$stream_async } from "./baml_sdk/index.js"

export async function streamCategory(input: string) {
  const stream = await classify_message$stream_async(input)
  while (true) {
    const partial: unknown = await stream.nextAsync()
    if (partial instanceof ai.stream.Done) break
    console.log(partial)
  }
  return await stream.finalAsync()
}

Canary’s generated stream type currently omits the Done sentinel from nextAsync()’s return type. The example narrows from unknown to check the runtime sentinel.

A partial value can be null; it does not mean the stream has ended. Calls and stream reads can fail, so handle rejections at your application boundary.

Transition rules

  • Await the _async variants before using their results. Synchronous variants block until completion.
  • Regenerate after changing a BAML function, class, enum, or generator configuration.
  • Treat partial streaming values separately from the final typed result.

Gotchas

  • Never edit generated client files by hand.
  • Keep secret-bearing client construction on the server side of web applications.
  • Do not use truthiness to test nullable numbers or strings when zero or empty text is valid.
  • Import generated enum symbols instead of duplicating their literal values.