- How to use state to define a graph’s schema
- How to use reducers to control how state updates are processed
Define state
State in LangGraph is defined with theStateSchema 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.
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:MessagesValue reducer appends it to the existing list and sets extraField.
Define a simple graph with this node. Use StateGraph for a graph that operates on this state, then addNode to populate the graph:
- Invocation started by updating a single key of the state.
- The invocation result includes the entire state.
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 usedMessagesValue, which includes a built-in reducer. For custom fields, use ReducedValue to define how updates are applied.
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:
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.
View example trace
Open a public LangSmith run for this example.
Overwrite to bypass the reducer for a single update, while keeping the field’s normal merging behavior for every other update:
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.
"__overwrite__":
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:
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 whatinvoke 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.
bar channel:
View example trace
Open a public LangSmith run for this example.
outputKeys to graph.stream:
streamMode: "updates" instead. See Streaming.
Alternative state definitions
PreferStateSchema 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:
@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 plainz.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
Connect these docs to your agent of choice via MCP for real-time answers.

