Skip to main content
State is the shared schema that nodes read and write. Reducers control how each update merges into the current value. This page covers:
  1. How to use state to define a graph’s schema
  2. How to use reducers to control how state updates are processed

Define state

State in LangGraph is defined with the StateSchema class. It accepts standard schemas (such as Zod) for individual fields, plus value types like ReducedValue, MessagesValue, and UntrackedValue. Prefer StateSchema over the legacy alternatives. By default, a graph uses the same schema for input and output, and the state determines that schema. See Define input and output schemas for distinct input and output schemas. Start with a messages field. That shape fits many LLM applications. For more detail, see Working with messages in graph state.
This state tracks a list of message objects and an extra integer field. MessagesValue includes a built-in reducer, so a node can return only the new messages to append.

Update state

Build a graph with a single node. The node is a TypeScript function that reads graph state and returns updates. The first argument is always the state:
This node returns only the new message. The MessagesValue reducer appends it to the existing list and sets extraField.
Nodes should return updates to the state directly, instead of mutating the state.
Define a simple graph with this node. Use StateGraph for a graph that operates on this state, then addNode to populate the graph:
LangGraph provides built-in utilities for visualizing a graph. See Visualize your graph for detail.
This graph executes a single node. Invoke it:
Note that:
  • Invocation started by updating a single key of the state.
  • The invocation result includes the entire state.
Inspect message content by logging:

Process state updates with reducers

Each key in the state can have its own reducer function, which controls how updates from nodes are applied. If no reducer is specified, updates to that key replace the previous value. The earlier example already used MessagesValue, which includes a built-in reducer. For custom fields, use ReducedValue to define how updates are applied.
The node returns only the new message; the reducer appends it:

MessagesValue

In practice, lists of messages need more than plain concatenation:
  • Update an existing message in the state by ID.
  • Accept shorthand message formats, such as { role: "user", content: "..." }.
MessagesValue is a field helper with a message-aware reducer that handles these cases:
This shape fits applications that use chat models. Unlike Python’s MessagesState class, TypeScript has no separate prebuilt state type: put MessagesValue on the messages field of a StateSchema, as in the example above.

Resetting a reducer field

A common source of confusion with reducers is that with a merging reducer, returning an empty value does not clear the field. The reducer merges the right argument into the left one, so an empty update is merged in and previously accumulated values are kept. This pattern matters for error buffers or retry counters that must be cleared between retry attempts:

View example trace

Open a public LangSmith run for this example.
To let a node reset (clear) the field, define a custom reducer that replaces the accumulated value instead of merging it:

View example trace

Open a public LangSmith run for this example.
Alternatively, wrap the update with Overwrite to bypass the reducer for a single update, while keeping the field’s normal merging behavior for every other update:
For more information, see Bypass reducers with Overwrite.

Bypass reducers with Overwrite

In some cases, you may want to bypass a reducer and directly overwrite a state value. When a node returns a value wrapped with Overwrite, the reducer is bypassed and the channel is set directly to that value. This is useful when you want to reset or replace accumulated state rather than merge it with existing values. LangGraph provides the Overwrite type for this purpose.
You can also use JSON format with the special key "__overwrite__":
When nodes execute in parallel, only one node can use Overwrite on the same state key in a given super-step. If multiple nodes attempt to overwrite the same key in the same super-step, an InvalidUpdateError is raised.

Define input and output schemas

By default, StateGraph operates with a single schema, and all nodes communicate using that schema. You can also define distinct input and output schemas. When distinct schemas are specified, an internal schema is still used for communication between nodes. The input schema validates the provided input. The output schema filters the internal data so invoke returns only the fields in the output schema. Define distinct input and output schemas:
Notice that the output of invoke only includes the output schema.

Pass private state between nodes

Nodes sometimes need intermediate data that should not appear in the graph’s main input or output schema. Keep that data private so only the nodes that need it can read it. The following sequential graph has three nodes. Private data passes between the first two steps. The third step only sees the public overall state.

Private channels and streaming

Input, output, and private schemas constrain what each node reads and what invoke returns. They do not hide channels from streaming. Values streaming emits all state channels by default, including private ones. That applies to the values projection from event streaming (stream.values on stream_events / streamEvents with version="v3"), and to graph.stream with stream_mode="values" / streamMode: "values". Values streaming uses the full state, not the output schema. A private channel can be missing from invoke output but visible in the stream. This example uses distinct input, output, overall, and private schemas. invoke returns only the output schema:

View example trace

Open a public LangSmith run for this example.
Streaming with the values projection still includes the private bar channel:

View example trace

Open a public LangSmith run for this example.
To restrict streamed values to specific channels (for example, only the output schema), pass outputKeys to graph.stream:
If you only need the channels a node produced in each step (not the full accumulated state), use streamMode: "updates" instead. See Streaming.

Alternative state definitions

Prefer StateSchema for new graphs. LangGraph also supports the approaches below for legacy code and advanced channel control.

Channels API

The channels API provides low-level control over state management. LangGraph provides several built-in channel types: Using the object shorthand: When you pass an object with reducer and default, it creates a BinaryOperatorAggregate channel. Passing null creates a LastValue channel:
Using channel classes directly: For more control, you can instantiate channel classes from @langchain/langgraph/channels:

Annotation.Root

Annotation.Root provides a declarative way to define state with reducers. It is similar to StateSchema but uses a different syntax:

Zod objects with Zod v3

When using Zod v3, you can define state with plain z.object() schemas. LangGraph extends Zod v3 with a .langgraph plugin that provides .reducer() and .metadata() methods:

Zod objects with Zod v4

Zod v4 uses a registry-based approach. Use the LangGraph registry to attach metadata to schema fields:

Comparison table

See also