> ## Documentation Index
> Fetch the complete documentation index at: https://docs.braintrust.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrading from Braintrust JavaScript SDK v3 to v4

> Guide for migrating from TypeScript SDK v3.x to v4.x

v4 of the Braintrust JavaScript SDK removes deprecated packages, narrows the public API by removing legacy and internal exports, requires newer versions of Node.js and provider libraries, and separates integration registration from the main SDK import.

These changes mean less SDK type information for TypeScript to check, fewer unused integrations in application bundles, and a smaller set of supported interfaces for your project to depend on.

## What's changed

The v4 upgrade includes changes to:

* **[Version support](#version-support-changes):** Node.js 22.18.0 or later, ES2022 builds, and higher minimum provider-library versions
* **[Runtime behavior](#behavior-changes):** Integration registration, background-only log uploads, opt-in attachment capture, and W3C trace propagation
* **[Packages and the CLI](#package-and-cli-changes):** Separate CLI installation and removal of deprecated integration packages and legacy entrypoints
* **[SDK APIs](#removed-and-changed-apis):** Removed legacy and internal exports, renamed APIs, and instrumentation exports moved to `braintrust/instrumentation`
* **[Types](#type-changes):** Fewer exported types, removed boolean type arguments, and a single eval task signature
* **[Build configuration](#build-changes):** ESM-only packages, conditional exports, and new built file locations

## Migration steps

The migration starts with compatibility and dependency updates, followed by application changes and verification.

**1. Check compatibility**

Before installing, confirm that your runtime and provider libraries meet the [version requirements](#version-support-changes).

**2. Upgrade your dependencies**

Upgrade the SDK using your package manager:

```bash npm theme={null}
npm install braintrust@latest
```

```bash pnpm theme={null}
pnpm add braintrust@latest
```

```bash yarn theme={null}
yarn add braintrust@latest
```

If your project depends on any of the following companion packages, upgrade them together with `braintrust`.
You don't need to install packages you don't use.

| Package | Major version for SDK v4 |
| - | - |
| `braintrust` | 4.x |
| `@braintrust/otel`, `@braintrust/temporal`, `@braintrust/browser`, `@braintrust/deepseek-harness`, and `@braintrust/templates-nunjucks-js` | 2.x |
| `@braintrust/audio` | 1.x |

Check the [package replacements](#removed-integration-packages) for integrations you have installed separately. If you use the CLI, [install it separately](#cli-installation) too.

**3. Apply the relevant breaking changes**

Review the detailed sections below for the SDK features and integrations your application uses. Update affected imports, API calls, configuration, and build settings.

> [!WARNING]
>
> A successful type-check does not by itself verify the migration. Review the [runtime behavior changes](#behavior-changes) even if your application still type-checks.

**4. Verify your migration**

After updating your application, verify that it builds and sends the expected data to Braintrust:

* Check your code for TypeScript errors, build your application, and run your full test suite.
* Exercise the tracing and evaluation paths your application uses, then confirm that the expected traces and results reach Braintrust.
* If you run scripts or short-lived processes, confirm their final logs reach Braintrust before they exit. See [logging and flushing](#logging-and-flushing) for how to wait for pending uploads.
* If you log files or media, confirm they still appear in your traces.
* If a request passes between services, confirm you can still follow it across those services in a single trace.

See [troubleshooting](#troubleshooting) if you encounter an upgrade issue.

## Migration reference

The following sections explain the v4 requirements and behavior changes.

### Version support changes

SDK v4 requires the following runtime and provider-library versions:

| Runtime or library | Requirement |
| - | - |
| Node.js | 22.18.0 or later for `braintrust` and Braintrust integration packages. Node.js 18, 20, 21, and 22 releases before 22.18.0 are no longer supported. |
| Browsers and edge runtimes | SDK builds target ES2022. If your application supports older runtimes, include the SDK in your application's transpilation step. |
| Vercel AI SDK (`ai`) | v4 or later. Support for v3 has been removed. |
| Eve | v0.34.0 or later. Support for earlier versions has been removed. |
| Flue | v1 or later. Support for earlier versions has been removed. |

### Behavior changes

Changes to defaults, return values, and trace propagation can affect your application even when its imports and types remain valid.

> [accordion] Instrumentation registration
>
> Importing `braintrust` no longer registers all provider integrations.
> This allows bundlers to remove unused integrations from your application.
>
> **Bundler-based auto-instrumentation**
>
> Keep the Braintrust plugin in your bundler configuration.
> The Vite, Webpack, Rollup, esbuild, and Next.js integrations inject `braintrust/auto` into transformed provider modules, so you don't need to import it separately.
> `braintrust/auto` registers the integrations that handle instrumented calls, but does not transform provider code itself.
>
> **Node.js entrypoints and manual wrappers**
>
> If you use either of these approaches, you don't need an additional `braintrust/auto` import:
>
> * **Node.js entrypoints:** `braintrust/hook.mjs` and `braintrust/apply-auto-instrumentation` already register integrations.
> * **Manual wrappers:** Functions such as `wrapOpenAI()` and `wrapAISDK()` register their own integrations when called.
>
> **Configuration order**
>
> If you use `configureInstrumentation()` to customize instrumentation, call it before integrations are registered. That means before:
>
> * Loading `braintrust/auto`.
> * Loading provider modules transformed by a Braintrust bundler plugin, which load `braintrust/auto` automatically.
> * Loading the Node.js instrumentation entrypoints.
> * Calling a manual wrapper.
>
> A static import runs before the rest of your module's code, even if you place it below the configuration call.
> For a bundled application, configure instrumentation in a startup module, then dynamically import the application:
>
> ```ts startup.ts theme={null}
> import { configureInstrumentation } from "braintrust/instrumentation";
>
> configureInstrumentation({ integrations: { openai: false } });
> await import("./app");
> ```
>
> Use this module as your application's entrypoint, and keep provider imports in `app` or its dependencies.
> Do not statically import those modules from the startup module, because their injected `braintrust/auto` imports would register integrations before configuration runs.

> [accordion] Logging and flushing
>
> The `asyncFlush` option has been removed from `initLogger`, `currentLogger`, `startSpan`, `traced`, and `wrapTraced`.
> The `logger.asyncFlush` getter has also been removed.
> Logs are now always uploaded in the background.
>
> **Waiting for uploads**
>
> If you previously used `asyncFlush: false`, or read `logger.asyncFlush` to decide whether to wait for uploads, remove that logic.
> Call and await `flush()` where you need to wait for uploads:
>
> ```ts Before (v3) theme={null}
> const logger = initLogger({
>   projectName: "my-project",
>   asyncFlush: false,
> });
> await logger.log({
>   input: "hello",
>   output: "world",
> });
> ```
>
> ```ts After (v4) theme={null}
> const logger = initLogger({
>   projectName: "my-project",
> });
> logger.log({ input: "hello", output: "world" });
> await logger.flush();
> ```
>
> Awaiting a log call or traced function does not wait for its logs to upload.
> To wait for pending uploads:
>
> * Use `logger.flush()` to flush that logger's state.
> * Use `flush()` from `braintrust` to flush the default state's pending SDK logs. This does not flush registered OpenTelemetry handlers.
> * For OpenTelemetry spans, flush your provider or processor. See [OpenTelemetry export](#opentelemetry-export) for setup and shutdown changes.
> * For short-lived processes, await flushing before exiting.
>
> **Return values**
>
> Logging and tracing calls return the following values:
>
> * `logger.log()` now always returns a string ID.
> * `traced`, `logger.traced`, and `wrapTraced` return a synchronous value for synchronous callbacks and a promise for asynchronous callbacks.
>
> If your code calls `.then()` on a wrapped synchronous function, update it to use the returned value directly.

> [accordion] Attachment capture
>
> Instrumentation no longer captures inline images, audio, video, or files by default.
>
> **Enabling attachment capture**
>
> To continue capturing these as attachments, set `autoCaptureAttachments: true` when initializing the logger:
>
> ```ts theme={null}
> initLogger({
>   projectName: "my-project",
>   autoCaptureAttachments: true,
> });
> ```
>
> You can also enable capture with the `BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS` environment variable, set to `true` or `1`.
>
> This also applies to `@braintrust/deepseek-harness`, which accepts `autoCaptureAttachments` in its configuration.
>
> **Renamed environment variable**
>
> v3 used `BRAINTRUST_CAPTURE_ATTACHMENTS` to enable attachment capture for some integrations.
> If you set it, rename it to `BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS`.
> The old variable has no effect in v4, and the SDK reports no error when it is set.
> Capture is enabled only by the new variable or an explicit `autoCaptureAttachments` setting, including a default from an earlier current logger.
>
> **Experiments and evals**
>
> `init()` and `Eval()` don't accept an `autoCaptureAttachments` option.
> To capture attachments in experiment and eval spans, set `BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS`.
> These spans also use a default set by an earlier current logger, as described in the next section.
>
> **Configuration precedence and inheritance**
>
> The capture policy is determined as follows:
>
> * An explicit `autoCaptureAttachments` option overrides the environment variable, including when set to `false`.
> * For current loggers (`setCurrent: true`, the default), an explicit option also sets the default capture policy for the logger's SDK state.
>   Later current loggers that omit the option use this default instead of the environment variable, and so do experiment and eval spans in the same state.
> * To disable capture after a current logger enabled it, set `autoCaptureAttachments: false` explicitly on a later current logger.
>   Loggers that already exist keep their own policy.
> * Child spans inherit the policy.
> * Loggers created with `setCurrent: false` have an independent policy.
>   They don't change the default, and they use the environment variable when the option is omitted.
>
> **When capture is disabled**
>
> Disabling capture affects trace content as follows:
>
> * Inline media payloads are omitted from logged inputs and outputs.
> * Text, remote URLs, and file references can still appear in traces.
> * Explicitly logged `Attachment`, `ExternalAttachment`, and `JSONAttachment` objects are unaffected by this option.

> [accordion] Trace propagation
>
> The public `parent` argument on span APIs and `withParent()` no longer accepts serialized strings from `span.export()`.
>
> **Passing trace context between services**
>
> Use W3C trace headers and `extractTraceContextFromHeaders()` instead:
>
> ```ts Before (v3) theme={null}
> // Sender
> const exportedParent = await span.export();
>
> // Receiver
> const parent = request.headers.get(
>   "x-braintrust-parent",
> );
> const child = startSpan({
>   name: "handler",
>   parent,
> });
> ```
>
> ```ts After (v4) theme={null}
> // Sender
> const headers = span.inject();
>
> // Receiver: forward the sender's headers
> // with the request.
> const parent = extractTraceContextFromHeaders(
>   request.headers,
> );
> const child = startSpan({
>   name: "handler",
>   parent,
> });
> ```
>
> When forwarding trace context:
>
> * Forward `traceparent`, `tracestate`, and `baggage` together to preserve trace identity and Braintrust routing.
> * `injectTraceContext()` provides the headers for the current span.
>
> **Experiment destinations**
>
> An experiment's ID must be resolved before its spans can inject the destination into `baggage`.
> Unlike `await span.export()`, `span.inject()` is synchronous and does not wait for that ID.
> For a span in a newly initialized experiment, resolve its destination before injecting headers:
>
> ```ts theme={null}
> await span.export();
> const headers = span.inject();
> ```
>
> Send the injected headers to the receiver.
> Without the destination, the receiver can route the span to its own logger or experiment, or fail to continue the trace.
>
> **Legacy IDs**
>
> Unset `BRAINTRUST_LEGACY_IDS` before starting spans that need W3C propagation.
> Legacy UUIDs cannot be encoded as W3C trace IDs and span IDs, so `span.inject()` and `injectTraceContext()` emit no trace headers for those spans.
>
> **Distributed evals**
>
> `span.export()` disables the local span cache because remote spans are not stored in it.
> `span.inject()` and `injectTraceContext()` do not disable that cache.
> If your eval calls another service and scorers read its spans through `trace.getSpans()`, set `enableCache: false` in the third argument to `Eval()`:
>
> ```ts theme={null}
> await Eval("my-project", evaluator, {
>   enableCache: false,
> });
> ```
>
> Otherwise, scorers can receive only locally cached spans and miss remote children.
> Ensure that the remote service flushes its spans before scoring starts.
>
> **Propagated metadata and tags**
>
> Exported span strings include `propagatedEvent`, but the W3C headers do not serialize it.
> If you use `propagatedEvent` to inherit metadata or tags across services, send that data separately in your application's request payload.
> On the receiver, pass the received data as `propagatedEvent` alongside the extracted `parent`, for example `startSpan({ name: "handler", parent, propagatedEvent })`.
> The receiving span and its descendants then inherit that data.
>
> **Other ways to create or reference spans**
>
> The following APIs remain available:
>
> * Within one process, use `span.startSpan()`, `span.traced()`, or `withCurrent()` to create child spans.
> * `span.export()` remains available for APIs that explicitly accept exported identifiers, such as `invoke()`.

> [accordion] Dataset records
>
> The `useOutput` option on `initDataset()` has been removed.
> Dataset records now use `expected` for the expected output, including when reading existing records stored in the legacy format.
>
> Update your dataset reads and inserts:
>
> * Replace reads of `record.output` with `record.expected`.
> * Update insert calls to use `expected`:
>
> ```ts Before (v3) theme={null}
> dataset.insert({
>   input: "hello",
>   output: "world",
> });
> ```
>
> ```ts After (v4) theme={null}
> dataset.insert({
>   input: "hello",
>   expected: "world",
> });
> ```

> [accordion] Eval results
>
> `Eval()` now resolves to a plain object with `summary` and `results` fields instead of an `EvalResultWithSummary` instance. The custom `toString()` and `toJSON()` methods are no longer available on the resolved result.
>
> To work with the returned object:
>
> * Read the `summary` and `results` fields directly.
> * Use `JSON.stringify(result.summary)` to serialize the summary.

> [accordion] Temporal
>
> The `@braintrust/temporal` integration passes trace information between Temporal clients and workers to connect their spans in Braintrust. The headers carrying that information have changed:
>
> * **Previously:** `_braintrust-span` and `_braintrust-workflow-span*`
> * **In v4:** `traceparent`, `tracestate`, and `baggage`
>
> Upgrade the Braintrust Temporal integration in clients and workers together. The new integration does not read the old headers, so running mixed versions can lose parent trace links.

### Package and CLI changes

SDK v4 separates CLI installation from the core package and removes older integration packages and import paths.

> [accordion] CLI installation
>
> The `braintrust` package no longer includes the JavaScript `braintrust` CLI or installs the `bt` binary.
>
> **Installing the CLI**
>
> Install `bt` separately in development and CI using the [CLI installation instructions](https://docs.braintrust.dev/reference/cli/quickstart.md).
>
> **Updating commands and scripts**
>
> Update scripts that use the bundled CLI. For example, replace the eval command as follows:
>
> ```bash Before (v3) theme={null}
> npx braintrust eval example.eval.ts
> ```
>
> ```bash After (v4) theme={null}
> bt eval example.eval.ts
> ```
>
> Replace `npx braintrust push` with [`bt functions push`](https://docs.braintrust.dev/reference/cli/functions#bt-functions-push) and `npx braintrust pull` with [`bt functions pull`](https://docs.braintrust.dev/reference/cli/functions#bt-functions-pull).
> Check their flags with `bt functions push --help` and `bt functions pull --help`.
>
> **Lambda sandboxes**
>
> `bt` doesn't register Lambda sandboxes, so registering a TypeScript Lambda sandbox still requires the SDK v3 `braintrust` CLI.
> Instead of downgrading your migrated application, register the sandbox from a separate package that depends on `braintrust@^3`.
> The eval code and dependencies in that package must remain compatible with SDK v3.
> Eval code that uses v4-only APIs can't run through the v3 CLI unchanged.
> See [Register your sandbox](https://docs.braintrust.dev/evaluate/remote-evals.md#2-register-your-sandbox) for the commands.
> Modal sandboxes are registered with `registerSandbox()`, which SDK v4 includes.

> [accordion] Removed integration packages
>
> The following packages are no longer published:
>
> | Removed package | What to use instead |
> | - | - |
> | `@braintrust/langchain-js` | For LangChain tracing, import `BraintrustLangChainCallbackHandler` from `braintrust` and pass it through LangChain's `callbacks` option. For application-wide tracing, replace `setGlobalHandler` with auto-instrumentation. |
> | `@braintrust/openai-agents` | For OpenAI Agents tracing, import `OpenAIAgentsTraceProcessor` from `braintrust/instrumentation` and register it with OpenAI Agents' `addTraceProcessor`, or use auto-instrumentation. A separate integration package is no longer required. |
> | `@braintrust/vercel-ai-sdk` | For streaming through the Vercel AI SDK, replace `BraintrustAdapter` and its `toAIStream`, `toAIStreamResponse`, and `toDataStreamResponse` methods with `BraintrustStream` and your AI SDK version's stream and response APIs. This requires adapting the stream format, not just changing the import. |

> [accordion] Removed package entrypoints
>
> The following import paths are no longer available. Their replacements depend on your runtime or bundler:
>
> | Removed entrypoint | What to use instead |
> | - | - |
> | `braintrust/browser` | For browser applications, import from `braintrust` with browser export conditions, or use `@braintrust/browser` for its context propagation integration. |
> | `braintrust/dev` and `braintrust/util` | Replace imports of helpers from these entrypoints with public SDK APIs or application-owned utilities. |
> | `braintrust/webpack-loader` | For Webpack instrumentation, use `braintrustWebpackPlugin` from `braintrust/webpack`. For Next.js, use `wrapNextjsConfigWithBraintrust` from `braintrust/next`. |

### Removed and changed APIs

SDK v4 removes legacy helpers and internal exports, and moves some APIs to dedicated entrypoints. The replacements below are grouped by functionality and integration.

> [accordion] Core SDK
>
> Core API changes affect imports, object initialization, tracing helpers, and object lifecycle methods.
>
> **Import syntax**
>
> The default export has been removed. Use named imports or a namespace import:
>
> ```ts Before (v3) theme={null}
> import braintrust from "braintrust";
> ```
>
> ```ts After (v4) theme={null}
> import * as braintrust from "braintrust";
> ```
>
> **Initialization arguments**
>
> The string-first overloads of `init()` and `initDataset()` have been removed. Pass the project name in the options object:
>
> ```ts Before (v3) theme={null}
> const experiment = init("my-project", {
>   experiment: "my-experiment",
> });
> const dataset = initDataset("my-project", {
>   dataset: "my-dataset",
> });
> ```
>
> ```ts After (v4) theme={null}
> const experiment = init({
>   project: "my-project",
>   experiment: "my-experiment",
> });
> const dataset = initDataset({
>   project: "my-project",
>   dataset: "my-dataset",
> });
> ```
>
> **Removed APIs**
>
> The following APIs are no longer available:
>
> | Removed API | Replacement or action |
> | - | - |
> | `initExperiment()` | Use `init()` instead. |
> | `withExperiment()`, `withLogger()`, and `withDataset()` | Create the experiment with `init()`, the logger with `initLogger()`, or the dataset with `initDataset()`, then use it directly. |
> | `orgProjectMetadata` option on `initLogger()` | Removed without replacement. |
> | Top-level `log()` and `summarize()` | Use `experiment.log()` and `experiment.summarize()` instead. |
> | `traceable()` | Use `wrapTraced()` instead. |
> | `wrapOpenAIv4()` | Use `wrapOpenAI()` instead. |
> | Top-level `permalink()` | Use `await span.permalink()` or `span.link()` instead. |
> | `Span.close()` | Use `span.end()` instead. |
> | `Experiment.close()` and `Dataset.close()` | These methods were no-ops and their calls can be removed. If you used the returned ID, use `await experiment.id` or `await dataset.id`. Use `flush()` when you need to wait for uploads. |

> [accordion] Logging and eval fields
>
> Log events, eval hooks, and scorer results use updated fields and error-handling conventions.
>
> **Log event fields**
>
> * The `inputs` field on log events has been removed.
>   Use `input` instead.
> * The `datasetRecordId` field on log events has been removed.
>   Use `origin` to reference the source dataset record:
>
>   ```ts Before (v3) theme={null}
>   datasetRecordId: recordId,
>   ```
>
>   ```ts After (v4) theme={null}
>   origin: {
>     object_type: "dataset",
>     object_id: datasetId,
>     id: recordId,
>   },
>   ```
>
>   Include `_xact_id` in `origin` when referencing a specific record version.
>
> **Eval task metadata**
>
> * The `meta()` method on eval task hooks has been removed.
>   Update `hooks.metadata` directly:
>
>   ```ts Before (v3) theme={null}
>   hooks.meta({ language: "en" });
>   ```
>
>   ```ts After (v4) theme={null}
>   hooks.metadata.language = "en";
>   ```
>
> **Scorer errors and eval reporting**
>
> * The `error` field on scorer results has been removed.
>   Throw the error from the scorer instead.
> * `runEvaluator()`, `buildLocalSummary()`, and `reportFailures()` have been removed.
>   Use `Eval()` and its result, with `Reporter()` for custom reporting.

> [accordion] Instrumentation
>
> Instrumentation changes affect import paths, configuration aliases, and access to plugin internals.
>
> **Import paths**
>
> The following exports have moved from `braintrust` to `braintrust/instrumentation`:
>
> * `configureInstrumentation`
> * `InstrumentationConfig`
> * `SpanCustomizer`
> * `SpanExportData`
> * `registerOtelFlush`
> * `braintrustEveInstrumentation`
> * `braintrustFlueInstrumentation`
>
> ```ts Before (v3) theme={null}
> import {
>   configureInstrumentation,
> } from "braintrust";
> ```
>
> ```ts After (v4) theme={null}
> import {
>   configureInstrumentation,
> } from "braintrust/instrumentation";
> ```
>
> **Configuration aliases**
>
> The following aliases in `configureInstrumentation({ integrations: … })` have been removed:
>
> * `vercel`: Use `aisdk`.
> * `google`: Use `googleGenAI`.
> * `cursor`: Use `cursorSDK`.
> * `bedrock` and `awsBedrock`: Use `awsBedrockRuntime`.
> * `flue`: Register `braintrustFlueInstrumentation()` explicitly as described in the [Flue section](#flue).
>
> Environment-variable aliases continue to map to the canonical keys, except for the removed Flue aliases.
>
> **Plugin internals**
>
> `BasePlugin`, `BraintrustPlugin`, `BraintrustPluginConfig`, channel-name helpers, and channel lifecycle types have been removed from the public API.
> Use the supported APIs for configuration and custom tracing:
>
> * Use `configureInstrumentation()` to configure built-in integrations.
> * Use `traced()` or `wrapTraced()` for custom tracing.

> [accordion] Bundler plugins
>
> The deprecated plugin names have been removed. Use the corresponding replacements:
>
> | Removed plugin name | Replacement | Import path |
> | - | - | - |
> | `esbuildPlugin` | `braintrustEsbuildPlugin` | `braintrust/esbuild` |
> | `rollupPlugin` | `braintrustRollupPlugin` | `braintrust/rollup` |
> | `vitePlugin` | `braintrustVitePlugin` | `braintrust/vite` |
> | `webpackPlugin` | `braintrustWebpackPlugin` | `braintrust/webpack` |
>
> **Plugin options**
>
> The `useDiagnosticChannelCompatShim` option has been removed.
> Use the `browser` option in your bundler plugin configuration instead.

> [accordion] Vercel AI SDK
>
> `BraintrustMiddleware` and `wrapAISDKModel()` have been removed.
> Use either of the following instead:
>
> * `wrapAISDK()`
> * Auto-instrumentation
>
> **Module wrapping**
>
> For module wrapping, pass the original model to the wrapped SDK functions:
>
> ```ts theme={null}
> import * as ai from "ai";
> import { wrapAISDK } from "braintrust";
>
> const { generateText, streamText } = wrapAISDK(ai);
> ```

> [accordion] Mastra
>
> `wrapMastraAgent()` has been removed. It was a no-op, so you can remove calls to it. To trace Mastra activity, use either:
>
> * `BraintrustObservabilityExporter` in Mastra's observability configuration, which sends spans recorded by Mastra to Braintrust.
> * Auto-instrumentation

> [accordion] Eve
>
> SDK v4 removes Braintrust's older hook-based integration for Eve. The existing instrumentation-based integration remains available:
>
> * Replace `braintrustEveHook` and its hook-file setup with `braintrustEveInstrumentation()`.
> * Import it from `braintrust/instrumentation` and configure it through Eve's instrumentation configuration.

> [accordion] Flue
>
> SDK v4 removes `braintrustFlueObserver` and Braintrust's older loader and CLI integration for Flue. To trace Flue activity, register `braintrustFlueInstrumentation()` with Flue's runtime:
>
> ```ts theme={null}
> import { instrument } from "@flue/runtime";
> import { braintrustFlueInstrumentation } from "braintrust/instrumentation";
>
> instrument(braintrustFlueInstrumentation());
> ```

> [accordion] OpenTelemetry export
>
> The `@braintrust/otel` integration no longer creates an OpenTelemetry exporter or batch processor for you. Your application supplies these instances and selects compatible OpenTelemetry SDK and exporter versions. The integration supports OpenTelemetry SDK v1, v2, and v3 through the same `@braintrust/otel` entrypoint.
>
> **Processor and exporter setup**
>
> Replace the self-contained `BraintrustSpanProcessor` configuration with an explicit exporter and processor:
>
> ```ts Before (v3) theme={null}
> import { BraintrustSpanProcessor } from "@braintrust/otel";
>
> const processor = new BraintrustSpanProcessor({
>   apiKey: process.env.BRAINTRUST_API_KEY,
>   parent: "project_name:my-project",
>   filterAISpans: true,
> });
> ```
>
> ```ts After (v4, OpenTelemetry SDK v1 or v2) theme={null}
> import {
>   BraintrustExporter,
>   BraintrustSpanProcessor,
>   braintrustOtlpConfig,
> } from "@braintrust/otel";
> import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
> import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
>
> const exporter = new BraintrustExporter({
>   exporter: new OTLPTraceExporter(
>     braintrustOtlpConfig({
>       apiKey: process.env.BRAINTRUST_API_KEY,
>       parent: "project_name:my-project",
>     }),
>   ),
> });
> const processor = new BraintrustSpanProcessor({
>   processor: new BatchSpanProcessor(exporter),
>   filterAISpans: true,
> });
> ```
>
> Register `processor` with your OpenTelemetry provider using the API for your SDK version. For OpenTelemetry SDK v3, import `BatchSpanProcessor` from `@opentelemetry/sdk-trace` and pass an options object instead:
>
> ```ts theme={null}
> import { BatchSpanProcessor } from "@opentelemetry/sdk-trace";
>
> const processor = new BraintrustSpanProcessor({
>   processor: new BatchSpanProcessor({ exporter }),
>   filterAISpans: true,
> });
> ```
>
> `BraintrustSpanProcessor` handles Braintrust routing, span origin metadata, and filtering. `BraintrustExporter` applies registered span customizers before forwarding batches to your exporter. Keep the exporter wrapper if you use span customizers, including when you use `BraintrustSpanProcessor`.
>
> **Configuration changes**
>
> | Previous configuration | Change in v4 |
> | - | - |
> | `apiKey`, `apiUrl`, `parent`, and `headers` on either Braintrust constructor | Move these options to `braintrustOtlpConfig()`, which returns the `url` and `headers` for your OTLP/HTTP exporter. |
> | No explicit destination | Set `parent` or `BRAINTRUST_PARENT` to a project or experiment, such as `project_name:my-project`, `project_id:<id>`, or `experiment_id:<id>`. There is no implicit default project. |
> | `_spanProcessor` on `BraintrustSpanProcessor` | Replace it with the required `processor` option. |
> | `new BraintrustExporter({ apiKey, parent })` | Pass your exporter as `new BraintrustExporter({ exporter })`. |
> | `filterAISpans`, `customFilter`, and `environment` | These remain options on both Braintrust wrappers. Configure filtering on one wrapper to avoid running your filter twice. |
> | Batching, queue limits, export timeouts, and transport retries | Configure these on your OpenTelemetry processor or exporter. The Braintrust wrappers no longer create their own batch processor. |
>
> `braintrustOtlpConfig()` reads `BRAINTRUST_API_KEY` and `BRAINTRUST_API_URL` when their options are omitted. In Node.js, it can also load the API key from the nearest `.env.braintrust` file. Missing credentials or a missing destination cause an error during configuration. Additional `headers` cannot override the resolved authorization or destination headers.
>
> **Flushing and shutdown**
>
> Flush your OpenTelemetry provider or processor before exiting, and shut down the provider when your application stops. `BraintrustExporter.forceFlush()` waits for batches it has received, but cannot drain an upstream processor's queue.
>
> `BraintrustSpanProcessor` registers its flush callback with Braintrust so scorer queries can wait for OpenTelemetry spans. Multiple registrations now coexist instead of replacing each other, and processor shutdown removes its registration.
>
> If you use an exporter without `BraintrustSpanProcessor`, register your provider's flush callback explicitly when scorers need to query those spans:
>
> ```ts theme={null}
> import { registerOtelFlush } from "braintrust/instrumentation";
>
> const unregister = registerOtelFlush(() => provider.forceFlush());
> ```
>
> Call `unregister()` during application shutdown, then await `provider.shutdown()`.

> [accordion] OpenTelemetry and custom context managers
>
> SDK v4 changes how the OpenTelemetry integration connects spans across services and how you configure custom context managers. The following updates apply to code that passes trace information between services or customizes how the SDK tracks the active span.
>
> **Trace context**
>
> To connect spans across services, pass trace information using W3C headers instead of exported span strings.
>
> | API or behavior | Change in v4 |
> | - | - |
> | `contextFromSpanExport()` from `@braintrust/otel` | This function has been removed. Replace `contextFromSpanExport(await span.export())` with `contextFromSpan(span)`. For remote parents, propagate W3C headers. |
> | `parentFromHeaders()` | Now returns `PropagationContext \| undefined` instead of an exported span string. |
>
> Register your OpenTelemetry provider and context manager before calling `setupOtelCompat()`. Asynchronous context propagation requires a registered OpenTelemetry context manager. Compatibility mode uses OpenTelemetry's public context APIs and preserves sampling flags and trace state when combining Braintrust and OpenTelemetry spans.
>
> **Context managers**
>
> Use the public configuration APIs for custom context managers:
>
> | API or behavior | Change in v4 |
> | - | - |
> | Custom context-manager configuration | Use `configureContextManager(YourContextManager)` instead of mutating internal state or globals. Pass `undefined` to `configureContextManager()` to restore the default. |
> | `BRAINTRUST_CURRENT_SPAN_STORE` symbol property | Replace it with the `getCurrentSpanStore()` method on your `ContextManager` implementation. |
>
> `@braintrust/browser` now installs its context manager through `configureContextManager()`. Update it alongside the core SDK.
>
> **Span IDs and exported trace context**
>
> The SDK generates span and trace IDs and determines the format used to export trace context. SDK v4 no longer uses these globals to override that behavior:
>
> * `BRAINTRUST_ID_GENERATOR`: Supplied a custom ID generator. ID-generator exports have also been removed.
> * `BRAINTRUST_SPAN_COMPONENT`: Selected the format used to serialize exported trace context.
>
> The SDK uses built-in ID generation. `BRAINTRUST_LEGACY_IDS` remains available to select legacy UUIDs.
> Legacy UUIDs do not support the W3C header propagation described in [Trace propagation](#trace-propagation).
> Unset this variable when migrating to that propagation path.

> [accordion] Project builders and internal APIs
>
> Several builder types and implementation helpers are no longer public exports. The following entries identify supported alternatives where available.
>
> | Removed API or export | Replacement or action |
> | - | - |
> | `Project`, `CodeFunction`, `CodePrompt`, `ProjectNameIdMap`, `ToolBuilder`, `ScorerBuilder`, and `PromptBuilder` | Use `projects.create()` and its `tools`, `scorers`, `prompts`, and other builder properties to define functions. |
> | `graph` | Removed without replacement. |
> | `SpanFetcher` and `CachedSpanFetcher` | Access spans through `Trace` or `LocalTrace` methods. |
> | `NoopSpan` | Use `NOOP_SPAN` instead. |
> | `templateRegistry`, `getTemplateRenderer`, `TemplateRenderer`, `TemplateFormat`, `renderMessage`, and `renderPromptParams` | Use `registerTemplatePlugin()` for template extensions and `Prompt.build()` for rendering. |
> | Stream helpers and runtime schemas | Use `BraintrustStream` for streaming and define application-specific validation in your application. |
> | `_internalSerializeForCache()` on `Prompt` and loaded parameters | Removed without a public replacement. |
>
> Logging internals, testing helpers, and low-level utilities are also no longer exported. See the [complete list of removed root exports](#export-changes).

### Type changes

The public TypeScript declarations expose fewer named types and remove generic parameters tied to flushing behavior:

| Type or API | Change in v4 |
| - | - |
| `Logger` and `Dataset` | No longer accept boolean type arguments. Replace `Logger<true>` or `Logger<false>` with `Logger`, and `Dataset<true>` or `Dataset<false>` with `Dataset`. |
| Tracing functions, `BraintrustLangChainCallbackHandler`, and its options type | Flush-related type arguments have been removed. For example, `traced<true, Result>` becomes `traced<Result>`. |
| `Eval()` task function | The `task` callback now uses a single signature: `(input, hooks) => Output \| Promise<Output>`. Async-generator data sources remain supported through `AsyncIterable`. |
| Request, result, metadata, and configuration types | Check [Export changes](#export-changes) for affected names. For removed types, remove the imports and annotations and pass values directly to SDK functions. For moved types, update the import path shown in the table. |
| Bundler option types | `EsbuildPluginOptions`, `RollupPluginOptions`, `VitePluginOptions`, and `WebpackPluginOptions` are no longer exported from their respective bundler entrypoints. Remove these imports and annotations, and pass options directly to the bundler plugin functions. |
| Public declarations | Now use SDK-owned TypeScript contracts instead of backend-generated or Zod-inferred types. Supplying Zod schemas for eval and function parameters remains supported. |

### Build changes

SDK v4 changes module formats, runtime selection, and output paths. These changes affect bundlers, test runners, and applications that import built files directly:

> [accordion] ESM packages
>
> Configure your bundler and test runner to load ESM dependencies:
>
> | Packages | Module format in v4 |
> | - | - |
> | `braintrust`, `@braintrust/otel`, `@braintrust/temporal`, `@braintrust/deepseek-harness`, and `@braintrust/templates-nunjucks-js` | Now publish ESM instead of separate CommonJS and ESM builds |
> | `@braintrust/browser` | Remains ESM |
>
> CommonJS applications can use dynamic `import()` when their loader cannot load ESM through `require()`:
>
> ```js theme={null}
> const { initLogger } = await import("braintrust");
> ```
>
> In CommonJS, put this call inside an async function.

> [accordion] Removing unused features
>
> The SDK now preserves module boundaries so bundlers can remove unused features, including evals, datasets, prompts, and provider integrations.
>
> * Continue importing public APIs from `braintrust`.
> * Keep your bundler plugin or Node.js instrumentation entrypoint configured as described in [Instrumentation registration](#instrumentation-registration).

> [accordion] Runtime selection
>
> When you import from `braintrust`, your bundler or module loader selects a build for the environment where your code runs. In v4, it checks the conditions defined under `exports` in the SDK's `package.json`: `workerd`, `edge-light`, `browser`, and `node`. If none of these conditions match, it selects the browser build.
>
> The SDK now identifies its browser build in the `exports` section of `package.json`. The separate `browser` field has been removed.
>
> Your bundler or module loader normally selects the build automatically. To select a runtime explicitly in your application code, import from `braintrust/node`, `braintrust/workerd`, or `braintrust/edge-light`.

> [accordion] Built file locations
>
> Built file locations have changed, including the former `dist/index.js` and `dist/index.mjs` files.
>
> Replace direct `dist` imports or aliases with public package entrypoints such as `braintrust` and `braintrust/hook.mjs`.

### Export changes

To see which exports were removed from the `braintrust` import or moved to another entrypoint in v4, expand the section below.

> [accordion] Removed and moved exports
>
> “Removed” means the name is no longer publicly exported by the `braintrust` package. For replacements and migration instructions, see [Removed and changed APIs](#removed-and-changed-apis).
>
> | Export | Status in v4 |
> | - | - |
> | `_exportsForTestingOnly` | Removed |
> | `_internalGetGlobalState` | Removed |
> | `_internalIso` | Removed |
> | `_internalSetInitialState` | Removed |
> | `addAzureBlobHeaders` | Removed |
> | `AnyDataset` | Removed |
> | `AttachmentParams` | Removed |
> | `AttachmentReference` | Removed |
> | `BackgroundLoggerOpts` | Removed |
> | `BAGGAGE_HEADER` | Removed |
> | `BaseMetadata` | Removed |
> | `BRAINTRUST_CURRENT_SPAN_STORE` | Removed |
> | `BRAINTRUST_LANGCHAIN_CALLBACK_HANDLER_NAME` | Removed |
> | `BRAINTRUST_PARENT_KEY` | Removed |
> | `braintrustEveHook` | Removed |
> | `braintrustEveInstrumentation` | Import from `braintrust/instrumentation` |
> | `braintrustFlueInstrumentation` | Import from `braintrust/instrumentation` |
> | `braintrustFlueObserver` | Removed |
> | `BraintrustMiddleware` | Removed |
> | `BraintrustStreamChunk` | Removed |
> | `braintrustStreamChunkSchema` | Removed |
> | `buildLocalSummary` | Removed |
> | `CachedSpanFetcher` | Removed |
> | `ChatPrompt` | Removed |
> | `CodeFunction` | Removed |
> | `CodeOpts` | Removed |
> | `CodePrompt` | Removed |
> | `CommentEvent` | Removed |
> | `CompiledPrompt` | Removed |
> | `CompiledPromptParams` | Removed |
> | `CompletionPrompt` | Removed |
> | `configureInstrumentation` | Import from `braintrust/instrumentation` |
> | `constructLogs3OverflowRequest` | Removed |
> | `createFinalValuePassThroughStream` | Removed |
> | `CreateProjectOpts` | Removed |
> | `DatasetRecord` | Removed |
> | `DatasetRestorePreviewResult` | Removed |
> | `DatasetRestoreResult` | Removed |
> | `DatasetSnapshot` | Removed |
> | `DatasetSummary` | Removed |
> | `DataSummary` | Removed |
> | `deepCopyEvent` | Removed |
> | `DEFAULT_FETCH_BATCH_SIZE` | Removed |
> | `DEFAULT_MAX_REQUEST_SIZE` | Removed |
> | `DefaultMetadataType` | Removed |
> | `DefaultPromptArgs` | Removed |
> | `deserializePlainStringAsJSON` | Removed |
> | `devNullWritableStream` | Removed |
> | `EndSpanArgs` | Removed |
> | `ERR_PERMALINK` | Removed |
> | `EvalCase` | Removed |
> | `EvalHooks` | Removed |
> | `EvalParameters` | Removed |
> | `EvalParameterSerializedSchema` | Removed |
> | `EvalResult` | Removed |
> | `EvalResultWithSummary` | Removed |
> | `EvalScorerArgs` | Removed |
> | `EvaluatorDef` | Removed |
> | `EvaluatorDefinition` | Removed |
> | `EvaluatorDefinitions` | Removed |
> | `evaluatorDefinitionSchema` | Removed |
> | `evaluatorDefinitionsSchema` | Removed |
> | `EvaluatorFile` | Removed |
> | `EvaluatorManifest` | Removed |
> | `ExperimentLogFullArgs` | Removed |
> | `ExperimentLogPartialArgs` | Removed |
> | `ExperimentSummary` | Removed |
> | `Exportable` | Removed |
> | `ExternalAttachmentParams` | Removed |
> | `FailedHTTPResponse` | Removed |
> | `FullInitDatasetOptions` | Removed |
> | `FullInitOptions` | Removed |
> | `FullLoginOptions` | Removed |
> | `FunctionEvent` | Removed |
> | `getContextManager` | Removed |
> | `getIdGenerator` | Removed |
> | `getSpanParentObject` | Removed |
> | `getTemplateRenderer` | Removed |
> | `GetThreadOptions` | Removed |
> | `graph` | Removed |
> | `IdField` | Removed |
> | `IDGenerator` | Removed |
> | `InitDatasetOptions` | Removed |
> | `initExperiment` | Removed |
> | `InitLoggerOptions` | Removed |
> | `InitOptions` | Removed |
> | `InputField` | Removed |
> | `InstrumentationConfig` | Import from `braintrust/instrumentation` |
> | `InvokeFunctionArgs` | Removed |
> | `InvokeReturn` | Removed |
> | `LazyValue` | Removed |
> | `LEGACY_CACHED_HEADER` | Removed |
> | `LoadPromptOptions` | Removed |
> | `log` | Removed |
> | `LogCommentFullArgs` | Removed |
> | `LogFeedbackFullArgs` | Removed |
> | `LoginInvalidOrgError` | Removed |
> | `LoginOptions` | Removed |
> | `LogOptions` | Removed |
> | `LOGS3_OVERFLOW_REFERENCE_TYPE` | Removed |
> | `Logs3OverflowInputRow` | Removed |
> | `Logs3OverflowUpload` | Removed |
> | `logs3OverflowUploadSchema` | Removed |
> | `MastraObservabilityExporter` | Removed |
> | `MetricSummary` | Removed |
> | `newId` | Removed |
> | `NOOP_SPAN_PERMALINK` | Removed |
> | `NoopSpan` | Removed |
> | `ObjectFetcher` | Removed |
> | `ObjectMetadata` | Removed |
> | `OTELIDGenerator` | Removed |
> | `OtherExperimentLogFields` | Removed |
> | `ParametersSource` | Removed |
> | `ParentExperimentIds` | Removed |
> | `ParentProjectLogIds` | Removed |
> | `parseCachedHeader` | Removed |
> | `ParsedTraceparent` | Removed |
> | `permalink` | Removed |
> | `pickLogs3OverflowObjectIds` | Removed |
> | `Project` | Removed |
> | `ProjectNameIdMap` | Removed |
> | `PromiseUnless` | Removed |
> | `PromptBuilder` | Removed |
> | `PromptContents` | Removed |
> | `promptContentsSchema` | Removed |
> | `PromptDefinition` | Removed |
> | `promptDefinitionSchema` | Removed |
> | `promptDefinitionToPromptData` | Removed |
> | `PromptDefinitionWithTools` | Removed |
> | `promptDefinitionWithToolsSchema` | Removed |
> | `PromptOpts` | Removed |
> | `PromptRowWithId` | Removed |
> | `PropagatedState` | Removed |
> | `registerOtelFlush` | Import from `braintrust/instrumentation` |
> | `RegisterSandboxOptions` | Removed |
> | `RegisterSandboxResult` | Removed |
> | `renderMessage` | Removed |
> | `renderPromptParams` | Removed |
> | `reportFailures` | Removed |
> | `runEvaluator` | Removed |
> | `SandboxConfig` | Removed |
> | `ScorerBuilder` | Removed |
> | `ScorerOpts` | Removed |
> | `ScoreSummary` | Removed |
> | `SerializedBraintrustState` | Removed |
> | `SetCurrentArg` | Removed |
> | `spanComponentsToObjectId` | Removed |
> | `SpanContext` | Removed |
> | `SpanCustomizer` | Import from `braintrust/instrumentation` |
> | `SpanData` | Removed |
> | `SpanDurationFilter` | Removed |
> | `SpanExportData` | Import from `braintrust/instrumentation` |
> | `SpanFetcher` | Removed |
> | `SpanFilters` | Removed |
> | `SpanImpl` | Removed |
> | `summarize` | Removed |
> | `TemplateFormat` | Removed |
> | `templateRegistry` | Removed |
> | `TemplateRenderer` | Removed |
> | `TestBackgroundLogger` | Removed |
> | `ToolBuilder` | Removed |
> | `toolFunctionDefinitionSchema` | Removed |
> | `traceable` | Removed |
> | `TraceContextCarrier` | Removed |
> | `TraceContextHeaders` | Removed |
> | `TRACEPARENT_HEADER` | Removed |
> | `TRACESTATE_HEADER` | Removed |
> | `uploadLogs3OverflowPayload` | Removed |
> | `utf8ByteLength` | Removed |
> | `UUIDGenerator` | Removed |
> | `withDataset` | Removed |
> | `withExperiment` | Removed |
> | `withLogger` | Removed |
> | `WithTransactionId` | Removed |
> | `wrapAISDKModel` | Removed |
> | `wrapMastraAgent` | Removed |
> | `wrapOpenAIv4` | Removed |
> | `X_CACHED_HEADER` | Removed |

## Troubleshooting

The following symptoms can help identify configuration and dependency updates missed during migration.

> [accordion] "The SDK requires a newer Node.js version"
>
> Upgrade to Node.js 22.18.0 or newer, then reinstall your dependencies.

> [accordion] "Module not found" errors after upgrading
>
> Check the [removed package entrypoints](#removed-package-entrypoints) and [removed integration packages](#removed-integration-packages), then update imports and dependencies.

> [accordion] Provider calls are no longer instrumented
>
> Review [instrumentation registration](#instrumentation-registration) and ensure that configuration runs before loading transformed provider modules or instrumentation entrypoints, or calling manual wrappers.

> [accordion] Attachments are missing from traces
>
> Review [attachment capture](#attachment-capture) and set `autoCaptureAttachments` or `BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS` as needed.
> If you set `BRAINTRUST_CAPTURE_ATTACHMENTS` in v3, rename it to `BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS`.

> [!NOTE]
>
> If you encounter issues during migration, please [open an issue](https://github.com/braintrustdata/braintrust-sdk-javascript/issues) with details about your setup.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.