Choosing an encryption method
Basic encryption
For simple encryption with a single static key, set theLANGGRAPH_AES_KEY environment variable. LangGraph will automatically encrypt checkpoint blobs using AES.
-
Add
pycryptodometo your dependencies inlanggraph.json: -
Set the
LANGGRAPH_AES_KEYenvironment variable to a 16, 24, or 32-byte key (for AES-128, AES-192, or AES-256 respectively).
Encrypting JSON fields
To also encrypt specific JSON fields, setLANGGRAPH_AES_JSON_KEYS to a comma-separated list of keys to encrypt:
langgraph_version, langgraph_api_version, langgraph_plan, langgraph_host, langgraph_api_url, langgraph_request_id, langgraph_auth_user_id, and langgraph_auth_permissions.
Custom encryption
Requires Agent Server version 0.6.22+ and Python SDK version
langgraph-sdk>=0.3.1.- Per-tenant key isolation — different encryption keys for different customers
- KMS integration — AWS KMS, Google Cloud KMS, or HashiCorp Vault for key management, rotation, and audit logging
How it works
- Configure the encryption module path in
langgraph.json - Define your encryption module with handlers for blob and JSON encryption
- Pass encryption context (like tenant ID) via the
X-Encryption-Contextheader - LangGraph calls your handlers before storing and after retrieving data
Configuration
Add your encryption module tolanggraph.json:
If you’re migrating from basic encryption, keep
LANGGRAPH_AES_KEY configured. Custom encryption handles new writes while existing AES-encrypted data remains readable.Defining your encryption module
Blob encryption (checkpoints)
Blob handlers encrypt checkpoint data—the serialized state from graph execution. Here’s a simplified example using per-tenant keys with Fernet (a symmetric encryption scheme from thecryptography library):
ctx.metadata dict comes from the X-Encryption-Context header and is stored in plaintext alongside encrypted data, so the correct key is used on decryption.
JSON encryption (metadata)
JSON handlers encrypt structured data like thread metadata, assistant context, and run kwargs. Unlike blob encryption, you choose which fields to encrypt—keeping some unencrypted for search and filtering.JSON encryption considerations
Migration consideration: Use a recognizable prefix or format in encrypted values so your decryptor can detect and skip unencrypted data. This allows you to encrypt additional fields in the future without re-encrypting existing records. The example above uses this pattern.
Performance consideration: Per-key encryption means one encryption call per field. If your encryption involves round-trips to an external service (e.g., KMS), this can significantly impact latency. Consider caching data keys locally or using envelope encryption where you encrypt a local data key with KMS and use it for multiple fields.
tenant_id, owner) should generally be left unencrypted, as should fields used for search and filtering. Additionally, some system-managed fields will never be encrypted:
- Resource identifiers (
thread_id,run_id,assistant_id,graph_id,checkpoint_id,task_id) - Most fields beginning with
langgraph_(except forlanggraph_auth_user) - Required checkpoint metadata (
source,step,parents,run_attempt) - Internal fields used for scheduling and orchestration (
__after_seconds__,__request_start_time_ms__, most fields beginning with__pregel) - Run-level execution limits (
max_concurrency,recursion_limit) specified in a run’sconfig - Thread TTL updates (
ttl) specified in a run’sconfig.configurable
What gets encrypted
JSON handlers (@encryption.encrypt.json / @encryption.decrypt.json) are applied recursively to the following fields:
thread.metadata,thread.valuesassistant.metadata,assistant.contextrun.metadata,run.kwargscron.metadata,cron.payloadstore.value
@encryption.encrypt.blob / @encryption.decrypt.blob) are applied to checkpoint blobs (graph execution state).
Deriving context from authentication
Instead of passingX-Encryption-Context explicitly, derive encryption context from the authenticated user:
ctx.metadata for all encryption operations in that request.
Passing encryption context
Pass encryption context via theX-Encryption-Context header. The context is arbitrary data that you define—you control the schema and can include any fields your encryption logic needs (e.g., tenant_id, key_version). The context is available in your handlers as ctx.metadata and is stored in plaintext for use during decryption.
The encryption context is stored in plaintext. On decryption, it’s automatically restored—callers don’t need to pass the header when reading.
Envelope encryption with AWS Encryption SDK
For production deployments on AWS, use the AWS Encryption SDK with AWS KMS, or an equivalent within your cloud provider. This approach:- Handles envelope encryption automatically (no manual key packing)
- Provides key rotation and audit logging
- Binds ciphertext to encryption context (tenant isolation)
- Caches data keys locally to avoid repeated KMS calls, latency and rate limits
Complete example
encryption_context is cryptographically bound to the ciphertext via KMS—decryption fails if the context doesn’t match. The context is embedded in the ciphertext, so decrypt handlers don’t need to reference ctx.metadata.
Key rotation
KMS handles master key rotation automatically. When you enable automatic rotation on your KMS key, old encrypted data keys can still be decrypted while new operations use the rotated key material. No re-encryption of existing data is required.Related
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

