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.Check compatibility
Upgrade your dependencies
braintrust.
You don’t need to install packages you don’t use.Apply the relevant breaking changes
Verify your migration
- 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.
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.Instrumentation registration
Instrumentation registration
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.mjsandbraintrust/apply-auto-instrumentationalready register integrations. - Manual wrappers: Functions such as
wrapOpenAI()andwrapAISDK()register their own integrations when called.
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/autoautomatically. - Loading the Node.js instrumentation entrypoints.
- Calling a manual wrapper.
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.Logging and flushing
Logging and flushing
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:- Use
logger.flush()to flush that logger’s state. - Use
flush()frombraintrustto 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.
logger.log()now always returns a string ID.traced,logger.traced, andwrapTracedreturn a synchronous value for synchronous callbacks and a promise for asynchronous callbacks.
.then() on a wrapped synchronous function, update it to use the returned value directly.Attachment capture
Attachment capture
autoCaptureAttachments: true when initializing the logger: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
autoCaptureAttachmentsoption overrides the environment variable, including when set tofalse. - 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: falseexplicitly on a later current logger. Loggers that already exist keep their own policy. - Child spans inherit the policy.
- Loggers created with
setCurrent: falsehave an independent policy. They don’t change the default, and they use the environment variable when the option is omitted.
- 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, andJSONAttachmentobjects are unaffected by this option.
Trace propagation
Trace propagation
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:- Forward
traceparent,tracestate, andbaggagetogether to preserve trace identity and Braintrust routing. injectTraceContext()provides the headers for the current span.
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: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():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(), orwithCurrent()to create child spans. span.export()remains available for APIs that explicitly accept exported identifiers, such asinvoke().
Dataset records
Dataset records
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.outputwithrecord.expected. - Update insert calls to use
expected:
Eval results
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
summaryandresultsfields directly. - Use
JSON.stringify(result.summary)to serialize the summary.
Temporal
Temporal
@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-spanand_braintrust-workflow-span* - In v4:
traceparent,tracestate, andbaggage
Package and CLI changes
SDK v4 separates CLI installation from the core package and removes older integration packages and import paths.CLI installation
CLI installation
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: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.Removed integration packages
Removed integration packages
Removed package entrypoints
Removed package entrypoints
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 SDK
Core SDK
init() and initDataset() have been removed. Pass the project name in the options object:Logging and eval fields
Logging and eval fields
-
The
inputsfield on log events has been removed. Useinputinstead. -
The
datasetRecordIdfield on log events has been removed. Useoriginto reference the source dataset record:Before (v3)IncludeAfter (v4)_xact_idinoriginwhen referencing a specific record version.
-
The
meta()method on eval task hooks has been removed. Updatehooks.metadatadirectly:Before (v3)After (v4)
- The
errorfield on scorer results has been removed. Throw the error from the scorer instead. runEvaluator(),buildLocalSummary(), andreportFailures()have been removed. UseEval()and its result, withReporter()for custom reporting.
Instrumentation
Instrumentation
braintrust to braintrust/instrumentation:configureInstrumentationInstrumentationConfigSpanCustomizerSpanExportDataregisterOtelFlushbraintrustEveInstrumentationbraintrustFlueInstrumentation
configureInstrumentation({ integrations: … }) have been removed:vercel: Useaisdk.google: UsegoogleGenAI.cursor: UsecursorSDK.bedrockandawsBedrock: UseawsBedrockRuntime.flue: RegisterbraintrustFlueInstrumentation()explicitly as described in the Flue section.
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()orwrapTraced()for custom tracing.
Bundler plugins
Bundler plugins
useDiagnosticChannelCompatShim option has been removed.
Use the browser option in your bundler plugin configuration instead.Vercel AI SDK
Vercel AI SDK
BraintrustMiddleware and wrapAISDKModel() have been removed.
Use either of the following instead:wrapAISDK()- Auto-instrumentation
Mastra
Mastra
wrapMastraAgent() has been removed. It was a no-op, so you can remove calls to it. To trace Mastra activity, use either:BraintrustObservabilityExporterin Mastra’s observability configuration, which sends spans recorded by Mastra to Braintrust.- Auto-instrumentation
Eve
Eve
- Replace
braintrustEveHookand its hook-file setup withbraintrustEveInstrumentation(). - Import it from
braintrust/instrumentationand configure it through Eve’s instrumentation configuration.
Flue
Flue
braintrustFlueObserver and Braintrust’s older loader and CLI integration for Flue. To trace Flue activity, register braintrustFlueInstrumentation() with Flue’s runtime:OpenTelemetry export
OpenTelemetry export
@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: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:unregister() during application shutdown, then await provider.shutdown().OpenTelemetry and custom context managers
OpenTelemetry and custom context managers
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.
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.Project builders and internal APIs
Project builders and internal APIs
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:ESM packages
ESM packages
import() when their loader cannot load ESM through require():Removing unused features
Removing unused features
- Continue importing public APIs from
braintrust. - Keep your bundler plugin or Node.js instrumentation entrypoint configured as described in Instrumentation registration.
Runtime selection
Runtime selection
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
Built file locations
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 thebraintrust import or moved to another entrypoint in v4, expand the section below.
Removed and moved exports
Removed and moved exports
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."The SDK requires a newer Node.js version"
"The SDK requires a newer Node.js version"
"Module not found" errors after upgrading
"Module not found" errors after upgrading
Provider calls are no longer instrumented
Provider calls are no longer instrumented
Attachments are missing from traces
Attachments are missing from traces
autoCaptureAttachments or BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS as needed.
If you set BRAINTRUST_CAPTURE_ATTACHMENTS in v3, rename it to BRAINTRUST_AUTO_CAPTURE_ATTACHMENTS.