> For the complete documentation index, see [llms.txt](https://triton.pharos.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://triton.pharos.ai/end-to-end/transactions-and-cache.md).

# Transactions & Platform Cache

How a transaction id ties logs together across LWC, Apex, and Flow — the lifecycle methods that manage it and the platform cache that moves it between contexts.

A **transaction id** is Triton's correlation key. Every log carrying the same id groups together in Pharos, no matter which context produced it. This page is the reference for the two things that make correlation work: the transaction lifecycle methods (`startTransaction`, `resumeTransaction`, `stopTransaction`) and the platform cache that shuttles the id between contexts (`withCache` and `TritonCacheManager`).

If you just want to see it wired up in a real LWC → Apex → Flow chain, start with the [End-to-End walkthrough](/end-to-end.md) and come back here for the details.

## The transaction id

In Apex, the current transaction id lives on a single static field:

{% code lineNumbers="true" %}

```apex
public static String Triton.TRANSACTION_ID;
```

{% endcode %}

Because Apex statics live for the duration of one Apex transaction, this one field is shared by everything you log — every `Triton.log(...)`, every convenience method, every Flow Add Log action that runs during that transaction. That's the whole reason logs correlate automatically without you threading an id around. (For the bigger picture on static state, see [The Static Logging API](/core-concepts/static-logging-api.md).)

## Managing the lifecycle

Three static methods drive the transaction id.

### `startTransaction()`

Generates a brand-new UUID v4, stores it in `TRANSACTION_ID`, and returns it. If the cache is enabled (see below), it also writes the id to Org cache.

{% code lineNumbers="true" %}

```apex
String id = Triton.startTransaction();
// TRANSACTION_ID is now a fresh UUID like 7fb8c320-e8f9-4f1b-9742-1a5768b9e0d2
```

{% endcode %}

Call this to **begin a new correlation** — for example at the top of an entry point that isn't downstream of any other context.

### `resumeTransaction(String transactionId)`

Adopts an **existing** id instead of minting one. Use it to tie the current context to a transaction that started somewhere else — the classic case being an Apex controller adopting the id its LWC caller already established.

{% code lineNumbers="true" %}

```apex
Triton.resumeTransaction(idFromLwc);
// Every subsequent log now correlates with the LWC's logs
```

{% endcode %}

If the cache is enabled, `resumeTransaction` also re-caches the id — which conveniently extends its time-to-live.

### `stopTransaction()`

Clears `TRANSACTION_ID` (and removes it from cache when caching is on). After this, the next log that needs an id will start a fresh transaction.

{% code lineNumbers="true" %}

```apex
Triton.stopTransaction();
```

{% endcode %}

{% hint style="warning" %}
`startTransaction()` mid-chain is a common footgun — it mints a **new** id and silently disconnects everything logged afterward from the trace you were building. When you mean "attach to the existing trace," reach for `resumeTransaction(id)`, not `startTransaction()`.
{% endhint %}

## `Triton.withCache()` — let the id travel on its own

Passing a transaction id through method parameters works, but it gets tedious across many components and calls. `Triton.withCache()` offers an alternative: park the id in **platform Org cache** so any later context — even in a separate transaction — can pick it up without you passing anything.

Here's exactly what `withCache()` does:

1. Checks whether Org cache is available (`TritonCacheManager.available()`).
2. **If it isn't available** — turns caching off, and starts a transaction if one isn't already running. Everything still works; you just don't get cross-transaction propagation.
3. **If it is available** — turns caching on, then:
   * looks for a cached transaction id and, if found, **resumes** it;
   * otherwise, if there's no current id, **starts** a new one (which gets cached).

{% code lineNumbers="true" %}

```apex
// At the top of an Apex entry point that should share the browser's transaction:
Triton.withCache();
// TRANSACTION_ID is now either the resumed cached id or a freshly cached new one
```

{% endcode %}

The cached id is stored in Org cache under a **per-user key**, with a **5-minute (300s) time-to-live**. Each resume re-caches it and resets that clock, so an active chain of calls keeps the id alive. Because the key is namespaced by user, two users' transactions never collide in the shared Org cache.

{% hint style="info" %}
**Org cache vs. the browser.** The LWC logger keeps *its* transaction id in the browser's `sessionStorage`; Apex's `withCache()` uses Salesforce **platform Org cache**. These are two different stores. They get connected because the LWC **sends its id along with every log payload**, and `TritonLwc.saveComponentLogs` resumes that id and calls `withCache()` — copying it into Org cache. From that point on, Apex and Flow can resume it from the cache.
{% endhint %}

## `TritonCacheManager` — the cache behind it all

`TritonCacheManager` is the thin layer that centralizes every platform-cache operation. Triton uses a single partition — **Org cache** — for both things it caches:

| Method group | Methods                           | What Triton uses it for                                                                                 |
| ------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Value cache  | `put`, `get`, `getKeys`, `remove` | Storing the **transaction id** (per-user key) and buffering **Flow logs** for the deferred flush batch. |
| Availability | `available`                       | Gatekeeper for `withCache()`.                                                                           |

Org cache is the right home for both: it isn't tied to a user session, so the transaction id survives across separate transactions and asynchronous contexts, and Flow logs remain discoverable by a batch job that runs later. Triton namespaces the transaction-id key by user id so entries don't collide in the org-wide cache.

### `available()`

The gatekeeper for `withCache()`. It probes `Cache.Org` and returns `false` if Org cache can't be reached (for example, when no Org cache capacity is allocated). This is what lets Triton degrade gracefully: no cache, no problem — logging continues without cross-context propagation.

{% code lineNumbers="true" %}

```apex
if (TritonCacheManager.available()) {
    // safe to rely on cached transaction ids
}
```

{% endcode %}

{% hint style="info" %}
**Mock mode for tests.** In test context, `TritonCacheManager` swaps the real `Cache.Org` for an in-memory map (and `available()` returns `true`). This sidesteps a platform quirk where cache `put()` succeeds silently but `get()` returns `null` under test — so your transaction-correlation logic is actually exercisable in unit tests.
{% endhint %}

## When the cache is not available

Because Triton uses **Org cache** — which is not tied to a user session — cache-backed correlation works in interactive contexts (an LWC calling Apex, a user running a screen Flow) **and** in asynchronous ones (Batch, Queueable, Scheduled, `@future`, platform-event triggers). The cache is only "unavailable" when the org has no Org cache capacity allocated or the platform can't reach it.

Triton handles that case for you: `withCache()` checks `available()` first and, when the cache is out of reach, quietly falls back to a plain in-memory transaction for that Apex transaction. Nothing breaks; you simply don't get the automatic cross-transaction hand-off.

{% hint style="success" %}
**Do** rely on `withCache()` across LWC → Apex → Flow chains and asynchronous jobs alike.

**Don't** forget that the cached id is keyed **per user**. When a downstream context runs as a *different* user (for example, a platform-event subscriber running as the Automated Process user), it won't resume the caller's id — **pass it explicitly** and call `resumeTransaction(id)` there.
{% endhint %}

## How the id travels: LWC → Apex → Flow

Putting the pieces together, here's the journey of a single transaction id through the canonical chain:

1. **LWC** generates a transaction id (UUID v4), stored in the browser's `sessionStorage`, and tags every log with it via `this.triton.transactionId`.
2. When those logs flush, **`TritonLwc.saveComponentLogs`** resumes that id and calls `Triton.withCache()`, copying it into platform **Org** cache.
3. Your **Apex controller** calls `Triton.withCache()` (or `Triton.resumeTransaction(idFromLwc)` if you passed it explicitly) and adopts the same id.
4. The controller's DML fires an **autolaunched Flow** in the *same* Apex transaction. The Flow's `Add Log` action inherits `TRANSACTION_ID` automatically — no parameter needed.
5. Flow logs are drained to `TritonFlowFlushBatch` and published shortly after, still under the same id.

The result is one grouped trace in Pharos spanning all three technologies. The step-by-step controller and component code lives in the [End-to-End walkthrough](/end-to-end.md).

## Quick reference

{% code lineNumbers="true" %}

```apex
// Lifecycle
Triton.startTransaction()        // mint a new UUID id, return it
Triton.resumeTransaction(id)     // adopt an existing id (re-caches it if caching is on)
Triton.stopTransaction()         // clear the id (and remove from cache if caching is on)
Triton.TRANSACTION_ID            // the current id (static, per Apex transaction)

// Platform cache
Triton.withCache()               // resume cached id, or start + cache a new one (if Org cache is available)

// Cache manager (usually called for you, not directly)
TritonCacheManager.available()   // is Org cache reachable?
```

{% endcode %}

## Related reading

* [End-to-End Logging](/end-to-end.md) — the full LWC → Apex → Flow walkthrough.
* [Spans & Distributed Tracing](/end-to-end/spans-and-tracing.md) — structure *within* a transaction.
* [Buffering, Flushing & Deferred Publishing](/core-concepts/buffering-flushing.md) — how Flow logs reach the deferred batch via `drain()`.
* [The Static Logging API](/core-concepts/static-logging-api.md) — why the transaction id can live on a static field.
