Skip to main content
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: Node.js 22.18.0 or later, ES2022 builds, and higher minimum provider-library versions
  • Runtime behavior: Integration registration, background-only log uploads, opt-in attachment capture, and W3C trace propagation
  • Packages and the CLI: Separate CLI installation and removal of deprecated integration packages and legacy entrypoints
  • SDK APIs: Removed legacy and internal exports, renamed APIs, and instrumentation exports moved to braintrust/instrumentation
  • Types: Fewer exported types, removed boolean type arguments, and a single eval task signature
  • Build configuration: 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.
2

Upgrade your dependencies

Upgrade the SDK using your package manager:
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.Check the package replacements for integrations you have installed separately. If you use the CLI, install it separately 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.
A successful type-check does not by itself verify the migration. Review the runtime 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 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 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:

Behavior changes

Changes to defaults, return values, and trace propagation can affect your application even when its imports and types remain valid.
Importing braintrust no longer registers all provider integrations. This allows bundlers to remove unused integrations from your application.Bundler-based auto-instrumentationKeep 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 wrappersIf 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 orderIf 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:
startup.ts
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.
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 uploadsIf 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:
Before (v3)
After (v4)
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 for setup and shutdown changes.
  • For short-lived processes, await flushing before exiting.
Return valuesLogging 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.
Instrumentation no longer captures inline images, audio, video, or files by default.Enabling attachment captureTo continue capturing these as attachments, set autoCaptureAttachments: true when initializing the logger:
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 variablev3 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 evalsinit() 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 inheritanceThe 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 disabledDisabling 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.
The public parent argument on span APIs and withParent() no longer accepts serialized strings from span.export().Passing trace context between servicesUse W3C trace headers and extractTraceContextFromHeaders() instead:
Before (v3)
After (v4)
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 destinationsAn 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:
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 IDsUnset 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 evalsspan.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():
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 tagsExported 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 spansThe 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().
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:
Before (v3)
After (v4)
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.
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.
The braintrust package no longer includes the JavaScript braintrust CLI or installs the bt binary.Installing the CLIInstall bt separately in development and CI using the CLI installation instructions.Updating commands and scriptsUpdate scripts that use the bundled CLI. For example, replace the eval command as follows:
Before (v3)
After (v4)
Replace npx braintrust push with bt functions push and npx braintrust pull with bt functions pull. Check their flags with bt functions push --help and bt functions pull --help.Lambda sandboxesbt 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 for the commands. Modal sandboxes are registered with registerSandbox(), which SDK v4 includes.
The following packages are no longer published:
The following import paths are no longer available. Their replacements depend on your runtime or bundler:

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.
Core API changes affect imports, object initialization, tracing helpers, and object lifecycle methods.Import syntaxThe default export has been removed. Use named imports or a namespace import:
Before (v3)
After (v4)
Initialization argumentsThe string-first overloads of init() and initDataset() have been removed. Pass the project name in the options object:
Before (v3)
After (v4)
Removed APIsThe following APIs are no longer available:
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:
    Before (v3)
    After (v4)
    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:
    Before (v3)
    After (v4)
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.
Instrumentation changes affect import paths, configuration aliases, and access to plugin internals.Import pathsThe following exports have moved from braintrust to braintrust/instrumentation:
  • configureInstrumentation
  • InstrumentationConfig
  • SpanCustomizer
  • SpanExportData
  • registerOtelFlush
  • braintrustEveInstrumentation
  • braintrustFlueInstrumentation
Before (v3)
After (v4)
Configuration aliasesThe 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.
Environment-variable aliases continue to map to the canonical keys, except for the removed Flue aliases.Plugin internalsBasePlugin, 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.
The deprecated plugin names have been removed. Use the corresponding replacements:Plugin optionsThe useDiagnosticChannelCompatShim option has been removed. Use the browser option in your bundler plugin configuration instead.
BraintrustMiddleware and wrapAISDKModel() have been removed. Use either of the following instead:
  • wrapAISDK()
  • Auto-instrumentation
Module wrappingFor module wrapping, pass the original model to the wrapped SDK functions:
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
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.
SDK v4 removes braintrustFlueObserver and Braintrust’s older loader and CLI integration for Flue. To trace Flue activity, register braintrustFlueInstrumentation() with Flue’s runtime:
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 setupReplace the self-contained BraintrustSpanProcessor configuration with an explicit exporter and processor:
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:
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 changesbraintrustOtlpConfig() 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 shutdownFlush 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:
Call unregister() during application shutdown, then await provider.shutdown().
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 contextTo connect spans across services, pass trace information using W3C headers instead of exported span strings.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 managersUse the public configuration APIs for custom context managers:@braintrust/browser now installs its context manager through configureContextManager(). Update it alongside the core SDK.Span IDs and exported trace contextThe 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. Unset this variable when migrating to that propagation path.
Several builder types and implementation helpers are no longer public exports. The following entries identify supported alternatives where available.Logging internals, testing helpers, and low-level utilities are also no longer exported. See the complete list of removed root exports.

Type changes

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

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:
Configure your bundler and test runner to load ESM dependencies:CommonJS applications can use dynamic import() when their loader cannot load ESM through require():
In CommonJS, put this call inside an async function.
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.
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.
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.
“Removed” means the name is no longer publicly exported by the braintrust package. For replacements and migration instructions, see Removed and changed APIs.

Troubleshooting

The following symptoms can help identify configuration and dependency updates missed during migration.
Upgrade to Node.js 22.18.0 or newer, then reinstall your dependencies.
Check the removed package entrypoints and removed integration packages, then update imports and dependencies.
Review instrumentation registration and ensure that configuration runs before loading transformed provider modules or instrumentation entrypoints, or calling manual wrappers.
Review 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.
If you encounter issues during migration, please open an issue with details about your setup.