---
title: "JS/TS v4 → v5"
description: Migration guide for upgrading the Langfuse JS/TS SDK from v4 to v5.
category: SDKs
---

# JS/TS v4 → v5

The JS/TS SDK v5 introduces the **[observations-first data model](/docs/observability/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

```typescript
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`.

```typescript
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"` (or `LANGFUSE_LOG_LEVEL="DEBUG"`).
- See [SDK advanced features](/docs/observability/sdk/advanced-features) and [OpenTelemetry troubleshooting for unwanted spans](/faq/all/existing-otel-setup#unwanted-spans-in-langfuse).

### `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:**

```typescript
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:**

```typescript
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](/docs/evaluation/evaluation-methods/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:**

```typescript
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:**

```typescript
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](/docs/evaluation/evaluation-methods/llm-as-a-judge)
  evaluators that rely on trace input/output.

### Public API namespace remapping (`api.*`)

In v5, high-performance Public API resources are now the defaults. The v2
aliases were removed.

| v4 / transitional name                  | v5 name                              |
| --------------------------------------- | ------------------------------------ |
| `langfuse.api.observationsV2`           | `langfuse.api.observations`          |
| `langfuse.api.scoreV2`                  | `langfuse.api.scores`                |
| `langfuse.api.metricsV2`                | `langfuse.api.metrics`               |
| `langfuse.api.observations` (legacy v1) | `langfuse.api.legacy.observationsV1` |
| `langfuse.api.score` (legacy v1)        | `langfuse.api.legacy.scoreV1`        |
| `langfuse.api.metrics` (legacy v1)      | `langfuse.api.legacy.metricsV1`      |

If your JS/TS SDK v5 client must temporarily query a self-hosted Langfuse v3
server, use the corresponding `langfuse.api.legacy.*V1` namespace.

  The new default `langfuse.api.observations` and `langfuse.api.metrics`
  methods point to the Observations v2 and Metrics v2 endpoints, which require
  Langfuse v4 (Langfuse Cloud, or a self-hosted server upgraded to v4). On
  self-hosted Langfuse v3, use `langfuse.api.legacy.observationsV1` and
  `langfuse.api.legacy.metricsV1` instead. See the [self-hosted
  compatibility matrix](/self-hosting/upgrade/versioning#sdk-server).

  **Public API endpoint deprecations are separate from the v5 breaking
  changes.** Some API methods remain callable in JS/TS v5 but call server
  endpoints that are deprecated for Langfuse v4. After upgrading, use the
  [JS/TS SDK method mappings in the deprecated API migration
  guide](/faq/all/deprecated-api-migration#sdk-method-quick-reference) to audit
  methods such as `langfuse.api.trace.list()`,
  `langfuse.api.sessions.list()`, and `langfuse.api.scores.getMany()`.

### [`@langfuse/langchain`](/integrations/frameworks/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 `traceMetadata` accepting non-string values — non-string values are now serialized via `JSON.stringify` before being passed to `propagateAttributes`, which requires `Record<string, string>`

### [`@langfuse/openai`](/integrations/model-providers/openai-py) 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

1. Audit traces/dashboards that depended on non-LLM OpenTelemetry spans: these may stop appearing with the v5 default filter
2. If needed, set `shouldExportSpan: () => true` on `LangfuseSpanProcessor` to preserve pre-v5 "export all spans" behavior
3. If you use custom filtering, compose with `isDefaultExportSpan` to keep the default LLM-focused behavior
4. Search for `updateActiveTrace` → split into `propagateAttributes()` + `setActiveTraceIO()` (when relying on legacy trace-level LLM-as-a-judge configurations) + `setActiveTraceAsPublic()`
5. Search for `.updateTrace(` → split into `propagateAttributes()` + `.setTraceIO()` + `.setTraceAsPublic()`
6. Verify propagated metadata values are `Record<string, string>` with values ≤200 characters
7. Replace `release`/`environment` attribute usage with env vars (`LANGFUSE_RELEASE`, `LANGFUSE_TRACING_ENVIRONMENT`)
8. Search for `api.observationsV2` / `api.scoreV2` / `api.metricsV2` → replace with `api.observations` / `api.scores` / `api.metrics`
9. Search for legacy v1 usage on `api.observations` / `api.score` / `api.metrics` → move to `api.legacy.observationsV1` / `api.legacy.scoreV1` / `api.legacy.metricsV1`
10. If you self-host Langfuse v3, use `api.legacy.observationsV1` and `api.legacy.metricsV1`; the default `api.observations` / `api.metrics` require Langfuse v4 (see the [self-hosted compatibility matrix](/self-hosting/upgrade/versioning#sdk-server))
11. Remove any remaining references to `*V2` aliases (they were removed in v5)

<!-- agent-instructions -->

---

## Agent Instructions

This page is part of the [Langfuse](https://langfuse.com) documentation, published as plain Markdown for AI agents. Every page is available as Markdown by appending `.md` to its URL, or by sending an `Accept: text/markdown` header. This page: `https://langfuse.com/docs/observability/sdk/upgrade-path/js-v4-to-v5.md`.

### Querying these docs

If the answer is not on this page, query the documentation instead of guessing:

- **Semantic search** across all Langfuse docs, returning an answer with the relevant pages and excerpts. Ask a specific, self-contained question:

  ```bash
  curl -sG "https://langfuse.com/api/search-docs" --data-urlencode "query=How do I trace a LangGraph agent?"
  ```

- **Index of every page**: <https://langfuse.com/llms.txt>, with per-section indexes [llms-docs.txt](https://langfuse.com/llms-docs.txt), [llms-integrations.txt](https://langfuse.com/llms-integrations.txt), and [llms-self-hosting.txt](https://langfuse.com/llms-self-hosting.txt).

### Before writing Langfuse code

- **Install the [Langfuse Agent Skill](https://langfuse.com/docs/api-and-data-platform/features/agent-skill).** It encodes Langfuse's own best practices for instrumentation, prompt management, and evaluation, and materially improves results.
- **Read [What does a good trace look like?](https://langfuse.com/docs/observability/best-practices.md)** before instrumenting an application.
- **Verify endpoints, parameters, and response fields** against the [API reference](https://api.reference.langfuse.com) instead of inferring them from code examples.
- **Use the [Langfuse CLI](https://langfuse.com/docs/api-and-data-platform/features/cli)** (`npx langfuse-cli api <resource> <action>`) to read or write traces, prompts, datasets, and scores from the terminal.

Found an error in these docs? Please open an issue at <https://github.com/langfuse/langfuse-docs/issues>.
