Graph Node supports advanced configuration through TOML files for complex deployments requiring multiple databases, chains, or specialized node roles.
When to Use Configuration Files
Use TOML configuration files when you need:
- Multiple databases: Sharding across multiple PostgreSQL instances
- Multiple chains: Indexing several blockchain networks
- Read replicas: Distributing query load across replica databases
- Node specialization: Dedicated index nodes and query nodes
- Custom deployment rules: Control where subgraphs are stored and indexed
For simple single-database setups, use command-line arguments instead.
Configuration File Structure
The TOML configuration file has four main sections:
[general] # General node settings
[store] # Database configuration
[chains] # Blockchain provider configuration
[deployment] # Subgraph deployment rules
Using a Configuration File
Specify the configuration file with the --config flag:
graph-node --config /path/to/config.toml --node-id index-node-1
When using --config, you cannot use:
--postgres-url
--postgres-secondary-hosts
--postgres-host-weights
--ethereum-rpc (define in config file instead)
Environment Variable Expansion
Configuration files support environment variable expansion:
[store.primary]
connection = "postgresql://graph:${PGPASSWORD}@${PGHOST}/graph"
For complex templating, use envsubst:
envsubst < config.template.toml > config.toml
graph-node --config config.toml
Store Configuration
The [store] section defines PostgreSQL databases for storing subgraph data.
Single Database Setup
Minimal configuration equivalent to --postgres-url:
[store]
[store.primary]
connection = "postgresql://graph:password@localhost:5432/graph-node"
pool_size = 10
[deployment]
[[deployment.rule]]
indexers = [ "index-node-1", "index-node-2" ]
Multiple Databases (Sharding)
Split subgraph storage across multiple databases:
[store]
# Primary shard (required)
[store.primary]
connection = "postgresql://graph:${PGPASSWORD}@primary-db/graph"
weight = 0 # Don't use for queries
pool_size = 10
# Read replicas for primary shard
[store.primary.replicas.repl1]
connection = "postgresql://graph:${PGPASSWORD}@primary-replica1/graph"
weight = 1 # 50% of query traffic
[store.primary.replicas.repl2]
connection = "postgresql://graph:${PGPASSWORD}@primary-replica2/graph"
weight = 1 # 50% of query traffic
# Additional shard for VIP subgraphs
[store.vip]
connection = "postgresql://graph:${PGPASSWORD}@vip-db/graph"
weight = 1 # Use for 50% of queries
pool_size = 20
[store.vip.replicas.repl1]
connection = "postgresql://graph:${PGPASSWORD}@vip-replica/graph"
weight = 1 # Use for 50% of queries
Connection Pool Configuration
Number of database connections per Graph Node instanceCan be a fixed number or rules based on node ID:# Fixed pool size
pool_size = 10
# Dynamic based on node role
pool_size = [
{ node = "index_node_.*", size = 20 },
{ node = "query_node_.*", size = 80 }
]
Rules are evaluated in order; first match wins.
Query traffic distribution weight
0: Don’t send queries (indexing only)
- Higher values: More query traffic
Traffic is distributed proportionally across all databases/replicas.
Validating Pool Configuration
Use graphman to validate connection pool settings:
graphman --config config.toml config pools index-node-1 query-node-1
This shows total connections opened by all nodes.
Chain Configuration
The [chains] section configures blockchain network providers.
Basic Chain Setup
[chains]
ingestor = "block_ingestor_node" # Node responsible for block ingestion
[chains.mainnet]
shard = "primary" # Store chain data in 'primary' shard
protocol = "ethereum" # Default, can be omitted
provider = [
{ label = "mainnet-primary", url = "https://eth-mainnet.g.alchemy.com/v2/API_KEY", features = ["archive", "traces"] },
{ label = "mainnet-fallback", url = "https://mainnet.infura.io/v3/PROJECT_ID", features = [] }
]
Multi-Chain Configuration
[chains]
ingestor = "block_ingestor_node"
[chains.mainnet]
shard = "vip"
amp = "ethereum-mainnet" # Name used by AMP
provider = [
{ label = "mainnet1", url = "http://eth-node-1:8545", features = ["archive", "traces"], headers = { Authorization = "Bearer token" } },
{ label = "mainnet2", url = "http://eth-node-2:8545", features = ["archive"] }
]
[chains.sepolia]
shard = "primary"
provider = [
{ label = "sepolia", url = "https://sepolia.infura.io/v3/PROJECT_ID", features = [] }
]
[chains.polygon]
shard = "primary"
protocol = "ethereum"
polling_interval = 1000 # Poll every 1 second
provider = [
{ label = "polygon-rpc", url = "https://polygon-rpc.com", features = ["archive"] }
]
[chains.near-mainnet]
shard = "primary"
protocol = "near"
provider = [
{ label = "near-firehose", details = { type = "firehose", url = "https://firehose.example.com", key = "api-key", features = ["compression", "filters"] } }
]
Provider Configuration
Provider name (appears in logs)
Provider capabilities:
archive: Full historical state
traces: Supports debug_traceBlockByNumber
no_eip1898: Doesn’t support EIP-1898 (block by hash/number object)
no_eip2718: Doesn’t return type field in transaction receipts
compression/gzip: Supports gzip compression
compression/brotli: Supports brotli compression
compression/deflate: Supports deflate compression
HTTP headers to include in requestsheaders = { Authorization = "Bearer token", "X-API-Key" = "key" }
Maximum subgraphs that can use this providerAt least one provider should be unlimited.
Connection type: rpc, ws, or ipc
Provider Types
Provider implementation:
web3: Standard Ethereum JSON-RPC (default)
firehose: StreamingFast Firehose
web3call: Web3 provider for eth_call only
For Firehose providers:
provider = [
{
label = "firehose-mainnet",
details = {
type = "firehose",
url = "https://firehose.example.com",
token = "bearer-token",
# OR use key-based auth
key = "api-key",
features = ["compression", "filters"]
}
}
]
Advanced: Provider Limits per Node
Limit which nodes can use specific providers:
[chains.mainnet]
shard = "primary"
provider = [
{ label = "unlimited", url = "http://primary-node:8545", features = [] },
{
label = "limited",
url = "http://secondary-node:8545",
features = [],
match = [
{ name = "index_node_.*", limit = 10 }, # Max 10 subgraphs on index nodes
{ name = "query_node_.*", limit = 0 } # Never use on query nodes
]
}
]
If a node’s name doesn’t match any rule, it cannot use that provider.
Deployment Rules
The [deployment] section controls where new subgraphs are deployed.
Deployment Rule Structure
Rules are evaluated in order. First matching rule determines placement.
[deployment]
# VIP subgraphs go to dedicated shard
[[deployment.rule]]
match = { name = "(vip|important)/.*" }
shard = "vip"
indexers = [ "index_node_vip_0", "index_node_vip_1" ]
# Kovan network subgraphs
[[deployment.rule]]
match = { network = "kovan" }
indexers = [ "index_node_kovan_0" ]
# Multiple networks (xDai or POA Core)
[[deployment.rule]]
match = { network = [ "xdai", "poa-core" ] }
indexers = [ "index_node_other_0" ]
# Default rule (required, no match clause)
[[deployment.rule]]
shards = [ "sharda", "shardb" ] # Distribute across shards
indexers = [
"index_node_community_0",
"index_node_community_1",
"index_node_community_2"
]
Match Conditions
Regular expression matching subgraph namematch = { name = "uniswap/.*" }
Network name(s) the subgraph indexes# Single network
match = { network = "mainnet" }
# Multiple networks
match = { network = [ "mainnet", "sepolia" ] }
Shard Selection
Fixed shard name for deployment storage
List of shards; chooses the one with fewest deploymentsshards = [ "shard1", "shard2", "shard3" ]
Indexer Selection
List of node IDs eligible to index this deploymentGraph Node chooses the indexer with fewest assigned subgraphs.indexers = [ "index-node-1", "index-node-2" ]
Names must match the --node-id argument when starting nodes.
Simulating Deployment
Test where a subgraph would be deployed:
graphman --config config.toml config place my-org/my-subgraph mainnet
Shows the target shard and eligible indexers without making changes.
Query Node Configuration
Designate nodes as query-only (no indexing):
[general]
query = "query_node_.*" # Regex matching query node IDs
Query nodes:
- Only respond to GraphQL queries
- Don’t connect to blockchain providers
- Don’t index subgraphs
Complete Example
# Query-only nodes
[general]
query = "query_node_.*"
# Database shards with replicas
[store]
[store.primary]
connection = "postgresql://graph:${PGPASS}@primary-db:5432/graph"
weight = 0
pool_size = [
{ node = "index_.*", size = 20 },
{ node = "query_.*", size = 80 }
]
[store.primary.replicas.replica1]
connection = "postgresql://graph:${PGPASS}@primary-replica1:5432/graph"
weight = 1
[store.primary.replicas.replica2]
connection = "postgresql://graph:${PGPASS}@primary-replica2:5432/graph"
weight = 1
[store.vip]
connection = "postgresql://graph:${PGPASS}@vip-db:5432/graph"
weight = 1
pool_size = 30
# Blockchain providers
[chains]
ingestor = "index_node_mainnet_0"
[chains.mainnet]
shard = "vip"
amp = "ethereum-mainnet"
provider = [
{ label = "alchemy", url = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}", features = ["archive", "traces"] },
{ label = "infura", url = "https://mainnet.infura.io/v3/${INFURA_KEY}", features = ["archive"] }
]
[chains.sepolia]
shard = "primary"
provider = [
{ label = "sepolia", url = "https://sepolia.infura.io/v3/${INFURA_KEY}", features = [] }
]
[chains.polygon]
shard = "primary"
protocol = "ethereum"
polling_interval = 500
provider = [
{ label = "polygon", url = "https://polygon-rpc.com", features = ["archive"] }
]
# Deployment rules
[deployment]
# High-priority subgraphs
[[deployment.rule]]
match = { name = "(uniswap|aave|compound)/.*" }
shard = "vip"
indexers = [ "index_node_vip_0", "index_node_vip_1" ]
# Test network subgraphs
[[deployment.rule]]
match = { network = "sepolia" }
shard = "primary"
indexers = [ "index_node_testnet_0" ]
# Default for everything else
[[deployment.rule]]
shards = [ "primary" ]
indexers = [
"index_node_mainnet_0",
"index_node_mainnet_1",
"index_node_mainnet_2"
]
Starting Nodes with Configuration
Index Nodes
# Mainnet index nodes
graph-node --config config.toml --node-id index_node_mainnet_0
graph-node --config config.toml --node-id index_node_mainnet_1
# VIP index nodes
graph-node --config config.toml --node-id index_node_vip_0
Query Nodes
graph-node --config config.toml --node-id query_node_0
graph-node --config config.toml --node-id query_node_1
Query nodes automatically detected via [general] query regex.
Validation and Testing
Validate Configuration
graph-node --config config.toml config check
Checks for:
- Syntax errors
- Undefined shards referenced in deployment rules
- Missing required sections
Check Connection Pools
graphman --config config.toml config pools index_node_mainnet_0 query_node_0
Shows total database connections opened by specified nodes.
Simulate Deployment
graphman --config config.toml config place uniswap/v3 mainnet
Shows which shard and indexers would be selected.
Best Practices
Index nodes: 10-30 connections per shardQuery nodes: 50-100 connections per shardMonitor GRAPH_STORE_CONNECTION_WAIT_TIME to detect pool exhaustion.
Set main database weight to 0 for shards with replicas:[store.primary]
weight = 0 # Indexing only
[store.primary.replicas.repl1]
weight = 1 # All queries go to replicas
This reduces load on the primary database.
Always configure at least 2 providers per chain for failover:provider = [
{ label = "primary", url = "...", features = ["archive", "traces"] },
{ label = "fallback", url = "...", features = ["archive"] }
]
Place most specific rules first:# Specific subgraph
[[deployment.rule]]
match = { name = "uniswap/v3" }
# Subgraph pattern
[[deployment.rule]]
match = { name = "uniswap/.*" }
# Network-wide
[[deployment.rule]]
match = { network = "mainnet" }
# Catch-all (must be last)
[[deployment.rule]]
Troubleshooting
Configuration validation errors
Run validation to see specific issues:graph-node --config config.toml config check
Common issues:
- Undefined shard referenced in deployment rule
- Missing required
[deployment] section
- Invalid regex in deployment match
Subgraph deployed to wrong shard
Simulate deployment to check rules:graphman --config config.toml config place subgraph-name network-name
Verify deployment rules are ordered correctly (most specific first).
Connection pool exhaustion
Check pool usage:graphman --config config.toml config pools <node-ids...>
Increase pool size for heavily loaded nodes:pool_size = [
{ node = "query_node_heavy", size = 150 }
]
Next Steps