Skip to main content
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

pool_size
number | rules
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.
weight
number
default:"1"
Query traffic distribution weight
  • 0: Don’t send queries (indexing only)
  • Higher values: More query traffic
Traffic is distributed proportionally across all databases/replicas.
connection
string
required
PostgreSQL libpq connection stringSupports environment variable expansion: ${VAR_NAME}

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

label
string
required
Provider name (appears in logs)
url
string
required
RPC endpoint URL
features
array
default:"[]"
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
headers
object
HTTP headers to include in requests
headers = { Authorization = "Bearer token", "X-API-Key" = "key" }
limit
number
Maximum subgraphs that can use this providerAt least one provider should be unlimited.
transport
string
default:"rpc"
Connection type: rpc, ws, or ipc

Provider Types

type
string
default:"web3"
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

match.name
regex
Regular expression matching subgraph name
match = { name = "uniswap/.*" }
match.network
string | array
Network name(s) the subgraph indexes
# Single network
match = { network = "mainnet" }

# Multiple networks
match = { network = [ "mainnet", "sepolia" ] }

Shard Selection

shard
string
default:"primary"
Fixed shard name for deployment storage
shards
array
List of shards; chooses the one with fewest deployments
shards = [ "shard1", "shard2", "shard3" ]

Indexer Selection

indexers
array
required
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

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
Simulate deployment to check rules:
graphman --config config.toml config place subgraph-name network-name
Verify deployment rules are ordered correctly (most specific first).
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

Build docs developers (and LLMs) love