Skip to main content
A TOML configuration file enables more complex Graph Node configurations than those exposed through CLI options. The configuration file location is specified with the --config command line flag.
When using a configuration file, you cannot use the --postgres-url, --postgres-secondary-hosts, and --postgres-host-weights CLI options.

Configuration Structure

The TOML configuration file consists of four main sections:

[chains]

Sets endpoints to blockchain clients

[store]

Describes available databases and shards

[ingestor]

Sets the node responsible for block ingestion

[deployment]

Describes how to place newly deployed subgraphs
Many configuration sections support environment variable expansion, especially Postgres connection strings. The official graph-node Docker image includes envsubst for complex use cases.

Basic Configuration

The simplest configuration is equivalent to using the --postgres-url command line option:
[store]
[store.primary]
connection = "postgresql://graph:password@localhost/graph"

[deployment]
[[deployment.rule]]
indexers = [ "index_node_1", "index_node_2" ]

Store Configuration

The [store] section defines database connections and sharding setup. At minimum, you must configure a primary shard.

Single Database Setup

[store]
[store.primary]
connection = "postgresql://graph:${PGPASSWORD}@primary/graph"
pool_size = 10

Connection String Format

The connection string must be a valid libpq connection string. Environment variables embedded in the string are expanded before passing to Postgres.
connection = "postgresql://graph:${PGPASSWORD}@${DB_HOST}/graph"

Connection Pool Size

Each shard must specify how many database connections each graph-node instance maintains in its connection pool.
Set a single value for all nodes:
[store.primary]
connection = "postgresql://graph:password@primary/graph"
pool_size = 10
Use different pool sizes for different node types based on node_id patterns:
[store.primary]
connection = "postgresql://graph:password@primary/graph"
pool_size = [
  { node = "index_node_general_.*", size = 20 },
  { node = "index_node_special_.*", size = 30 },
  { node = "query_node_.*", size = 80 }
]
Rules are checked in order, and the first matching rule is used. If no rule matches, configuration loading fails.
Always run graphman config pools $all_nodes after changing pool configuration to verify the expected connection counts. Replace $all_nodes with a space-separated list of all node names using this configuration.

Chain Configuration

The [chains] section controls blockchain provider connections and metadata storage.

Chain Structure

Configure a chain named mainnet in the [chains.mainnet] section:
[chains]
ingestor = "block_ingestor_node"

[chains.mainnet]
shard = "vip"
protocol = "ethereum"
polling_interval = 500
amp = "ethereum-mainnet"
provider = [
  { label = "mainnet1", url = "http://ethereum-node-1:8545", features = [] },
  { label = "mainnet2", url = "http://ethereum-node-2:8545", features = ["archive", "traces"] }
]

Chain Parameters

  • shard: Database shard where chain data is stored
  • protocol: Protocol type (ethereum, near, cosmos, arweave, starknet). Default: ethereum
  • polling_interval: Block ingestor polling interval in milliseconds. Default: 500
  • amp: Network name used by AMP for this chain. Defaults to chain name. Set when AMP uses different naming (e.g., amp = "ethereum-mainnet" for chain named mainnet)
  • provider: List of providers for this chain

Provider Configuration

Each provider has:
  • label: Provider name appearing in logs
  • details: Provider connection details
[chains.mainnet]
shard = "primary"
provider = [
  {
    label = "mainnet",
    url = "http://ethereum-node:8545",
    features = ["archive", "traces"],
    headers = { Authorization = "Bearer token123" }
  }
]

Provider Details

  • type: web3 (default), firehose, or web3call
  • transport: rpc (default), ws, or ipc
  • url: Provider endpoint URL
  • features: Array of supported features:
    • traces: Supports debug_traceBlockByNumber for call tracing
    • archive: Archive node with full historical state
    • no_eip1898: Doesn’t support EIP-1898 (block parameter by hash/number object)
    • no_eip2718: Doesn’t return type field in receipts (pre-EIP-2718 chains)
    • compression/<method>: Supports compression (gzip, brotli, or deflate)
    • For Firehose: compression and filters
  • headers: HTTP headers for every request. Default: none
  • limit: Maximum subgraphs using this provider. Default: unlimited
  • token: Bearer token for Firehose providers
  • key: API key for Firehose providers

Provider Limits (Experimental)

This feature is experimental and may be removed in future releases.
Limit the number of subgraphs per provider using node name patterns:
[chains.mainnet]
shard = "vip"
provider = [
  { label = "mainnet-0", url = "http://node-0:8545", features = [] },
  {
    label = "mainnet-1",
    url = "http://node-1:8545",
    features = [],
    match = [
      { name = "some_node_.*", limit = 10 },
      { name = "other_node_.*", limit = 0 }
    ]
  }
]
Nodes named some_node_.* use mainnet-1 for at most 10 subgraphs, then fall back to mainnet-0. Nodes named other_node_.* never use mainnet-1.
At least one provider should be unlimited. If a node’s name doesn’t match any rule, that provider is disabled for that node.

Deployment Configuration

The [deployment] section controls where new subgraph deployments are placed, determining both the storage shard and indexing node.

Deployment Rules

Rules are evaluated in order. The first matching rule determines placement.
[deployment]

# VIP subgraphs
[[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" }
# No shard specified = uses 'primary'
indexers = [ "index_node_kovan_0" ]

# Multiple networks
[[deployment.rule]]
match = { network = [ "xdai", "poa-core" ] }
indexers = [ "index_node_other_0" ]

# Default catch-all rule (no match = matches everything)
[[deployment.rule]]
shards = [ "sharda", "shardb" ]
indexers = [
  "index_node_community_0",
  "index_node_community_1",
  "index_node_community_2",
  "index_node_community_3",
  "index_node_community_4",
  "index_node_community_5"
]

Rule Matching

The match element can contain:
  • name: Regular expression matched against subgraph name
  • network: String or list of strings compared to deployment’s network
The last rule must not have a match statement to ensure all deployments have a valid placement.

Rule Actions

  • shard: Storage shard name (default: primary)
  • shards: List of shards (system chooses the one with fewest active deployments)
  • indexers: List of indexing nodes (deployment spreads evenly across listed nodes)
Indexer names must match the --node-id values used when starting index nodes.

Query Nodes

Configure nodes as query-only (no indexing) by adding to [general]:
[general]
query = "query_node_.*"
Nodes with --node-id matching the regex will:
  • Only respond to queries
  • Not connect to configured blockchain providers
  • Not perform any indexing

Configuration Validation

Validate configuration file syntax and consistency:
graph-node --config config.toml config check
This checks for:
  • Syntax errors
  • Internal inconsistencies
  • Undeclared shards referenced in deployment rules
  • Invalid configuration combinations

Simulating Deployment Placement

Test where a subgraph would be deployed without making changes:
graphman --config config.toml config place some/subgraph mainnet
Output shows:
  • Database shard for subgraph data
  • List of potential indexing nodes
  • Selection logic (node with fewest current subgraphs)

Complete Example

# General settings
[general]
query = "query_node_.*"

# Store configuration
[store]
[store.primary]
connection = "postgresql://graph:${PGPASSWORD}@primary-db/graph"
weight = 0
pool_size = [
  { node = "index_node_.*", size = 20 },
  { node = "query_node_.*", size = 80 }
]

[store.primary.replicas.repl1]
connection = "postgresql://graph:${PGPASSWORD}@primary-repl1/graph"
weight = 1

[store.primary.replicas.repl2]
connection = "postgresql://graph:${PGPASSWORD}@primary-repl2/graph"
weight = 1

[store.vip]
connection = "postgresql://graph:${PGPASSWORD}@vip-db/graph"
weight = 1
pool_size = 25

[store.vip.replicas.repl1]
connection = "postgresql://graph:${PGPASSWORD}@vip-repl1/graph"
weight = 1

# Chain configuration
[chains]
ingestor = "block_ingestor_node"

[chains.mainnet]
shard = "vip"
amp = "ethereum-mainnet"
polling_interval = 500
provider = [
  {
    label = "mainnet-archive",
    url = "http://eth-archive:8545",
    features = ["archive", "traces"],
    headers = { Authorization = "Bearer ${ETH_API_TOKEN}" }
  },
  {
    label = "mainnet-regular",
    url = "http://eth-regular:8545",
    features = []
  }
]

[chains.sepolia]
shard = "primary"
provider = [
  { label = "sepolia", url = "http://sepolia:8545", features = [] }
]

# Deployment rules
[deployment]

[[deployment.rule]]
match = { name = "(vip|production)/.*" }
shard = "vip"
indexers = [ "index_node_vip_0", "index_node_vip_1" ]

[[deployment.rule]]
match = { network = "sepolia" }
indexers = [ "index_node_test_0" ]

[[deployment.rule]]
# Default catch-all
shards = [ "primary" ]
indexers = [
  "index_node_community_0",
  "index_node_community_1",
  "index_node_community_2"
]

Build docs developers (and LLMs) love