JS/TS v4 → v5
The JS/TS SDK v5 introduces the observation-centric data model. In this model, correlating attributes (userId, sessionId, metadata, tags) propagate to every observation rather than living only on the trace. This enables single-table queries without expensive joins, significantly improving query performance at scale.
This changes how you set trace attributes: instead of imperatively updating the trace with updateActiveTrace(), you use propagateAttributes() — a function that wraps a callback, automatically applying attributes to all child observations created within its scope.
v5 changes default OpenTelemetry export behavior: Langfuse now applies a smart default span filter. If you previously expected all spans to be exported (including non-LLM spans), review the first breaking change below.
Breaking Changes
Smart default span filtering replaces export-all behavior
In previous versions, exporting all OpenTelemetry spans by default increased trace noise from infrastructure and non-LLM instrumentation (HTTP, DB, queues, framework internals). To keep traces focused and useful, v5 introduces a smart default span filter.
By default, v5 exports a span if any of these are true:
- The span was created by Langfuse (
langfuse-sdk) - The span has
gen_ai.*attributes - The span instrumentation scope matches known LLM scope prefixes (for example
openinference,langsmith,haystack,litellm)
Before v5, all spans were exported unless you implemented a custom shouldExportSpan function.
Keep pre-v5 “export everything” behavior
import { LangfuseSpanProcessor } from "@langfuse/otel";
const spanProcessor = new LangfuseSpanProcessor({
publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
secretKey: process.env.LANGFUSE_SECRET_KEY!,
shouldExportSpan: () => true,
});Compose custom rules with default behavior
shouldExportSpan is a full override in v5. If you want to extend (not replace) default filtering, compose with isDefaultExportSpan.
import { LangfuseSpanProcessor, isDefaultExportSpan } from "@langfuse/otel";
const spanProcessor = new LangfuseSpanProcessor({
publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
secretKey: process.env.LANGFUSE_SECRET_KEY!,
shouldExportSpan: ({ otelSpan }) =>
isDefaultExportSpan(otelSpan) ||
otelSpan.instrumentationScope.name.startsWith("my_framework"),
});Possible trace-tree side effects and how to debug
Filtering can break trace trees when intermediate or parent spans are dropped while child spans are still exported. If traces appear disconnected, enable SDK debug logging to inspect dropped spans, then allowlist the required scopes in your callback.
- JS/TS debug mode: set
LANGFUSE_DEBUG="true"(orLANGFUSE_LOG_LEVEL="DEBUG"). - See SDK advanced features and OpenTelemetry troubleshooting for unwanted spans.
updateActiveTrace() decomposed into 3 functions
In the new model, correlating attributes (userId, sessionId, metadata, tags) must live on every observation, not just the trace. propagateAttributes() wraps a callback — the current and all child spans created inside the callback automatically inherit the attributes. Spans created before the callback are not retroactively updated.
v4:
import { updateActiveTrace, startActiveObservation } from "@langfuse/tracing";
await startActiveObservation("my-operation", async (span) => {
updateActiveTrace({
name: "user-workflow",
userId: "user-123",
sessionId: "session-456",
tags: ["production"],
public: true,
metadata: { testRun: "server-export" },
input: { query: "hello" },
output: { response: "world" },
});
});v5:
import {
propagateAttributes,
startActiveObservation,
setActiveTraceIO,
setActiveTraceAsPublic,
} from "@langfuse/tracing";
await propagateAttributes(
{
traceName: "user-workflow", // was "name"
userId: "user-123",
sessionId: "session-456",
tags: ["production"],
metadata: { testRun: "server-export" },
},
async () => {
await startActiveObservation("my-operation", async (span) => {
setActiveTraceIO({
input: { query: "hello" },
output: { response: "world" },
});
setActiveTraceAsPublic();
});
},
);Key differences:
| Attribute | v4 | v5 |
|---|---|---|
name | updateActiveTrace({name: ...}) | propagateAttributes({traceName: ...}, cb) |
userId, sessionId, tags, version | updateActiveTrace({...}) | propagateAttributes({...}, cb) |
metadata | updateActiveTrace({metadata: any}) | propagateAttributes({metadata: Record<string,string>}, cb) |
input, output | updateActiveTrace({...}) | setActiveTraceIO({...}) (deprecated) |
public | updateActiveTrace({public: true}) | setActiveTraceAsPublic() |
release | updateActiveTrace({release: ...}) | Removed — use LANGFUSE_RELEASE env var |
environment | updateActiveTrace({environment: ...}) | Removed — use LANGFUSE_TRACING_ENVIRONMENT env var |
setActiveTraceIO() is deprecated and exists only for backward compatibility
with trace-level
LLM-as-a-judge
evaluators that rely on trace input/output. For new code, set input/output on
the root observation directly.
.updateTrace() → .setTraceIO() + .setTraceAsPublic()
The same decomposition applies on all observation wrapper classes (LangfuseSpan, LangfuseGeneration, etc.).
v4:
import { startObservation } from "@langfuse/tracing";
const span = startObservation("my-op");
span.updateTrace({
name: "my-trace",
userId: "user-123",
sessionId: "session-456",
tags: ["prod"],
public: true,
input: { query: "hello" },
output: { response: "world" },
});v5:
import { propagateAttributes, startObservation } from "@langfuse/tracing";
propagateAttributes(
{
traceName: "my-trace",
userId: "user-123",
sessionId: "session-456",
tags: ["prod"],
},
() => {
const span = startObservation("my-op");
span.setTraceIO({
input: { query: "hello" },
output: { response: "world" },
});
span.setTraceAsPublic();
span.end();
},
);.setTraceIO() is deprecated and exists only for backward compatibility with
trace-level
LLM-as-a-judge
evaluators that rely on trace input/output.
@langfuse/langchain internal changes
The CallbackHandler now uses propagateAttributes() for trace-level attributes. This affects users who:
- Subclass
CallbackHandler - Depend on the internal span-creation behavior
- Rely on
traceMetadataaccepting non-string values — non-string values are now serialized viaJSON.stringifybefore being passed topropagateAttributes, which requiresRecord<string, string>
@langfuse/openai internal changes
The traceMethod wrapper now wraps the traced call in propagateAttributes() to set userId, sessionId, tags, and traceName, instead of calling .updateTrace() on the observation. (If you rely on attributes being set on parent observations as well, wrap the entire execution in with propagateAttributes).
Removed attributes
| Removed | Replacement |
|---|---|
release | Set via LANGFUSE_RELEASE env var |
environment | Set via LANGFUSE_TRACING_ENVIRONMENT env var |
public | Replaced by setActiveTraceAsPublic() / .setTraceAsPublic() |
Migration Checklist
- Audit traces/dashboards that depended on non-LLM OpenTelemetry spans: these may stop appearing with the v5 default filter
- If needed, set
shouldExportSpan: () => trueonLangfuseSpanProcessorto preserve pre-v5 “export all spans” behavior - If you use custom filtering, compose with
isDefaultExportSpanto keep the default LLM-focused behavior - Search for
updateActiveTrace→ split intopropagateAttributes()+setActiveTraceIO()(when relying on legacy trace-level LLM-as-a-judge configurations) +setActiveTraceAsPublic() - Search for
.updateTrace(→ split intopropagateAttributes()+.setTraceIO()+.setTraceAsPublic() - Verify propagated metadata values are
Record<string, string>with values ≤200 characters - Replace
release/environmentattribute usage with env vars (LANGFUSE_RELEASE,LANGFUSE_TRACING_ENVIRONMENT)