Omnihedron

API Reference

Example GraphQL queries and responses for every operation omnihedron supports

This document covers every query pattern supported by the omnihedron GraphQL API. All examples use a hypothetical schema with transfers and accounts tables. Adapt entity names, field names, and filter types to match your own indexed schema.


Table of Contents


Connection Queries (List)

Connection queries return a paginated list of records. Every entity table generates a root query field named after the plural form of the entity (e.g., transfers).

Response Shape

All connection queries return the same structure:

{Entity}Connection {
  nodes: [{Entity}]
  edges: [{Entity}Edge]     # each edge has { cursor, node }
  pageInfo: PageInfo         # { hasNextPage, hasPreviousPage, startCursor, endCursor }
  totalCount: Int
  aggregates: {Entity}Aggregates   # when --aggregate is enabled
}

Basic List Query

query {
  transfers(first: 10) {
    totalCount
    nodes {
      id
      fromId
      toId
      amount
      blockNumber
    }
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 248,
      "nodes": [
        {
          "id": "0x001a2b3c",
          "fromId": "0xAlice",
          "toId": "0xBob",
          "amount": "1000000000",
          "blockNumber": 100
        },
        {
          "id": "0x002d4e5f",
          "fromId": "0xBob",
          "toId": "0xCharlie",
          "amount": "500000000",
          "blockNumber": 101
        }
      ]
    }
  }
}

Forward Cursor Pagination (first / after)

Fetch the first page, then use endCursor to get subsequent pages.

# Page 1
query {
  transfers(first: 5) {
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      id
      amount
    }
  }
}
{
  "data": {
    "transfers": {
      "pageInfo": {
        "hasNextPage": true,
        "endCursor": "eyJpZCI6IjB4MDA1In0="
      },
      "nodes": [
        { "id": "0x001", "amount": "1000000000" },
        { "id": "0x002", "amount": "2000000000" },
        { "id": "0x003", "amount": "500000000" },
        { "id": "0x004", "amount": "750000000" },
        { "id": "0x005", "amount": "100000000" }
      ]
    }
  }
}
# Page 2
query {
  transfers(first: 5, after: "eyJpZCI6IjB4MDA1In0=") {
    pageInfo {
      hasNextPage
      hasPreviousPage
      endCursor
    }
    nodes {
      id
      amount
    }
  }
}
{
  "data": {
    "transfers": {
      "pageInfo": {
        "hasNextPage": true,
        "hasPreviousPage": true,
        "endCursor": "eyJpZCI6IjB4MDEwIn0="
      },
      "nodes": [
        { "id": "0x006", "amount": "300000000" },
        { "id": "0x007", "amount": "900000000" },
        { "id": "0x008", "amount": "120000000" },
        { "id": "0x009", "amount": "450000000" },
        { "id": "0x010", "amount": "670000000" }
      ]
    }
  }
}

Backward Cursor Pagination (last / before)

Fetch the last N records, or the N records before a given cursor.

query {
  transfers(last: 3) {
    pageInfo {
      hasPreviousPage
      startCursor
    }
    nodes {
      id
      amount
    }
  }
}
{
  "data": {
    "transfers": {
      "pageInfo": {
        "hasPreviousPage": true,
        "startCursor": "eyJpZCI6IjB4MjQ2In0="
      },
      "nodes": [
        { "id": "0x246", "amount": "800000000" },
        { "id": "0x247", "amount": "150000000" },
        { "id": "0x248", "amount": "990000000" }
      ]
    }
  }
}

Offset Pagination

Use offset to skip a fixed number of records. Can be combined with first.

query {
  transfers(first: 5, offset: 10) {
    totalCount
    nodes {
      id
    }
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 248,
      "nodes": [
        { "id": "0x011" },
        { "id": "0x012" },
        { "id": "0x013" },
        { "id": "0x014" },
        { "id": "0x015" }
      ]
    }
  }
}

Ordering

Use orderBy with enum values in the format {COLUMN_NAME}_ASC or {COLUMN_NAME}_DESC. Multiple columns are supported. Use orderByNull to control null placement.

query {
  transfers(
    first: 5
    orderBy: [BLOCK_NUMBER_DESC, ID_ASC]
    orderByNull: NULLS_LAST
  ) {
    nodes {
      id
      blockNumber
    }
  }
}
{
  "data": {
    "transfers": {
      "nodes": [
        { "id": "0x248", "blockNumber": 9999 },
        { "id": "0x247", "blockNumber": 9998 },
        { "id": "0x246", "blockNumber": 9997 },
        { "id": "0x245", "blockNumber": 9996 },
        { "id": "0x244", "blockNumber": 9995 }
      ]
    }
  }
}

Valid orderByNull values: NULLS_FIRST, NULLS_LAST.

Distinct

Use distinct to deduplicate results by one or more columns. The distinct columns are automatically prepended to the ORDER BY clause.

query {
  transfers(first: 10, distinct: [FROM_ID]) {
    nodes {
      id
      fromId
      amount
    }
  }
}
{
  "data": {
    "transfers": {
      "nodes": [
        { "id": "0x001", "fromId": "0xAlice", "amount": "1000000000" },
        { "id": "0x002", "fromId": "0xBob", "amount": "500000000" },
        { "id": "0x010", "fromId": "0xCharlie", "amount": "670000000" }
      ]
    }
  }
}

Count-Only Query

When you only request totalCount (no nodes or edges), the server skips fetching rows and runs only a count query.

query {
  transfers {
    totalCount
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 248
    }
  }
}

Edges with Cursors

Each edge contains a cursor and a node. Use edges when you need individual cursors per record.

query {
  transfers(first: 3) {
    edges {
      cursor
      node {
        id
        amount
      }
    }
  }
}
{
  "data": {
    "transfers": {
      "edges": [
        {
          "cursor": "eyJpZCI6IjB4MDAxIn0=",
          "node": { "id": "0x001", "amount": "1000000000" }
        },
        {
          "cursor": "eyJpZCI6IjB4MDAyIn0=",
          "node": { "id": "0x002", "amount": "500000000" }
        },
        {
          "cursor": "eyJpZCI6IjB4MDAzIn0=",
          "node": { "id": "0x003", "amount": "750000000" }
        }
      ]
    }
  }
}

Single Record Queries

Lookup by ID

Each entity has a singular root query field that accepts an id argument.

query {
  transfer(id: "0x001a2b3c") {
    id
    fromId
    toId
    amount
    blockNumber
    nodeId
  }
}
{
  "data": {
    "transfer": {
      "id": "0x001a2b3c",
      "fromId": "0xAlice",
      "toId": "0xBob",
      "amount": "1000000000",
      "blockNumber": 100,
      "nodeId": "WyJ0cmFuc2ZlcnMiLCJhM2YyYzFkOC0xMjM0Il0="
    }
  }
}

Lookup by nodeId

Every entity also exposes a {entity}ByNodeId query. The nodeId is a base64-encoded JSON array ["table_name", "_id_uuid"].

query {
  transferByNodeId(nodeId: "WyJ0cmFuc2ZlcnMiLCJhM2YyYzFkOC0xMjM0Il0=") {
    id
    fromId
    toId
    amount
  }
}
{
  "data": {
    "transferByNodeId": {
      "id": "0x001a2b3c",
      "fromId": "0xAlice",
      "toId": "0xBob",
      "amount": "1000000000"
    }
  }
}

Filters

Every entity generates a {Entity}Filter input type. Filters are passed via the filter argument on connection queries. Each field in the filter corresponds to a column and accepts a type-specific filter object.

Scalar Filter Operators

The available operators depend on the field's scalar type. The following table lists common operators:

OperatorTypesDescription
equalToAllExact match
notEqualToAllNot equal
isNullAllTrue if column IS NULL, false if IS NOT NULL
inAll except Boolean/JSONValue is in the given list
notInAll except Boolean/JSONValue is not in the given list
greaterThanString, Int, BigInt, Float, BigFloat, Date, DatetimeGreater than
greaterThanOrEqualToString, Int, BigInt, Float, BigFloat, Date, DatetimeGreater than or equal
lessThanString, Int, BigInt, Float, BigFloat, Date, DatetimeLess than
lessThanOrEqualToString, Int, BigInt, Float, BigFloat, Date, DatetimeLess than or equal
distinctFromAllNot equal (treats NULL as a comparable value)
notDistinctFromAllEqual (treats NULL as a comparable value)
includesStringContains substring (case-sensitive)
notIncludesStringDoes not contain substring
includesInsensitiveStringContains substring (case-insensitive)
notIncludesInsensitiveStringDoes not contain substring (case-insensitive)
startsWithStringStarts with prefix
notStartsWithStringDoes not start with prefix
startsWithInsensitiveStringStarts with prefix (case-insensitive)
endsWithStringEnds with suffix
notEndsWithStringDoes not end with suffix
endsWithInsensitiveStringEnds with suffix (case-insensitive)
likeStringSQL LIKE pattern (use % for wildcard)
notLikeStringNot matching LIKE pattern
likeInsensitiveStringSQL ILIKE pattern (case-insensitive)
notLikeInsensitiveStringNot matching ILIKE pattern
equalToInsensitiveStringExact match (case-insensitive)
notEqualToInsensitiveStringNot equal (case-insensitive)

equalTo

query {
  transfers(filter: { fromId: { equalTo: "0xAlice" } }) {
    totalCount
    nodes {
      id
      fromId
      amount
    }
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 42,
      "nodes": [
        { "id": "0x001", "fromId": "0xAlice", "amount": "1000000000" },
        { "id": "0x003", "fromId": "0xAlice", "amount": "500000000" }
      ]
    }
  }
}

notEqualTo

query {
  transfers(filter: { fromId: { notEqualTo: "0xAlice" } }) {
    totalCount
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 206
    }
  }
}

in / notIn

query {
  transfers(filter: { id: { in: ["0x001", "0x002", "0x003"] } }) {
    nodes {
      id
      amount
    }
  }
}
{
  "data": {
    "transfers": {
      "nodes": [
        { "id": "0x001", "amount": "1000000000" },
        { "id": "0x002", "amount": "500000000" },
        { "id": "0x003", "amount": "750000000" }
      ]
    }
  }
}

greaterThan / lessThan

query {
  transfers(filter: { blockNumber: { greaterThan: 500, lessThan: 1000 } }) {
    totalCount
    nodes {
      id
      blockNumber
    }
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 73,
      "nodes": [
        { "id": "0x050", "blockNumber": 501 },
        { "id": "0x051", "blockNumber": 502 }
      ]
    }
  }
}

like / likeInsensitive

Use % as a wildcard character. like is case-sensitive; likeInsensitive is case-insensitive.

query {
  accounts(filter: { id: { like: "0xAlice%" } }) {
    nodes {
      id
    }
  }
}
{
  "data": {
    "accounts": {
      "nodes": [
        { "id": "0xAlice01" },
        { "id": "0xAlice02" }
      ]
    }
  }
}

includes / includesInsensitive (contains)

query {
  transfers(filter: { id: { includes: "2c5edd" } }) {
    nodes {
      id
    }
  }
}
{
  "data": {
    "transfers": {
      "nodes": [
        { "id": "0x2c5edd8ec6" }
      ]
    }
  }
}

startsWith / endsWith

query {
  transfers(filter: { id: { startsWith: "0x2c5" } }) {
    nodes {
      id
    }
  }
}
{
  "data": {
    "transfers": {
      "nodes": [
        { "id": "0x2c5edd8ec6" }
      ]
    }
  }
}

isNull

query {
  transfers(filter: { toId: { isNull: true } }) {
    totalCount
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 5
    }
  }
}

Logical Operators: and / or / not

Combine multiple filter conditions with and, or, and not. These accept arrays of filter objects (or a single filter object for not).

and

query {
  transfers(
    filter: {
      and: [
        { fromId: { equalTo: "0xAlice" } }
        { blockNumber: { greaterThan: 500 } }
      ]
    }
  ) {
    totalCount
    nodes {
      id
      fromId
      blockNumber
    }
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 18,
      "nodes": [
        { "id": "0x060", "fromId": "0xAlice", "blockNumber": 501 },
        { "id": "0x061", "fromId": "0xAlice", "blockNumber": 602 }
      ]
    }
  }
}

or

query {
  transfers(
    filter: {
      or: [
        { fromId: { equalTo: "0xAlice" } }
        { fromId: { equalTo: "0xBob" } }
      ]
    }
  ) {
    totalCount
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 95
    }
  }
}

not

query {
  transfers(
    filter: {
      not: { fromId: { equalTo: "0xAlice" } }
    }
  ) {
    totalCount
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 206
    }
  }
}

Relation Filters

Filter parent records based on properties of their related records.

Forward Relation Filter

Filter by properties of a related parent entity (FK column points to another table).

query {
  transfers(
    filter: {
      account: { id: { equalTo: "0xAlice" } }
    }
  ) {
    nodes {
      id
      amount
    }
  }
}

Exists Filter

Check whether a related record exists at all.

query {
  transfers(
    filter: {
      accountExists: true
    }
  ) {
    totalCount
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 243
    }
  }
}

Backward Relation Filters: some / none / every

Filter parent records based on their child records (one-to-many relations).

some -- at least one child matches:

query {
  accounts(
    filter: {
      transfersByFromId: {
        some: { amount: { greaterThan: "5000000000" } }
      }
    }
  ) {
    nodes {
      id
    }
  }
}
{
  "data": {
    "accounts": {
      "nodes": [
        { "id": "0xAlice" },
        { "id": "0xDave" }
      ]
    }
  }
}

none -- no children match:

query {
  accounts(
    filter: {
      transfersByFromId: {
        none: { amount: { greaterThan: "5000000000" } }
      }
    }
  ) {
    nodes {
      id
    }
  }
}

every -- all children match:

query {
  accounts(
    filter: {
      transfersByFromId: {
        every: { blockNumber: { greaterThan: 100 } }
      }
    }
  ) {
    nodes {
      id
    }
  }
}

List (Array) Column Filters

For array-typed columns, use list filter operators: every, some, none, isEmpty.

query {
  accounts(
    filter: {
      tags: { some: { equalTo: "validator" } }
    }
  ) {
    nodes {
      id
    }
  }
}

Aggregates

When the server is started with --aggregate (enabled by default), every connection type includes an aggregates field. Aggregates are computed against the same filter as the connection query.

Available Aggregate Fields

FieldReturn TypeDescription
countBigInt (string)Total row count matching the filter
distinctCountPer-column BigInt (string)Count of distinct values per column
sumPer-numeric-column BigFloat (string)Sum of values
averagePer-numeric-column BigFloat (string)Average of values
minPer-numeric-column (Int/Float as number, BigInt/BigFloat as string)Minimum value
maxPer-numeric-column (Int/Float as number, BigInt/BigFloat as string)Maximum value
stddevSamplePer-numeric-column BigFloat (string)Sample standard deviation
stddevPopulationPer-numeric-column BigFloat (string)Population standard deviation
varianceSamplePer-numeric-column BigFloat (string)Sample variance
variancePopulationPer-numeric-column BigFloat (string)Population variance

Basic Aggregates

query {
  transfers {
    aggregates {
      count
      sum {
        amount
        blockNumber
      }
      average {
        amount
      }
      min {
        blockNumber
      }
      max {
        blockNumber
      }
    }
  }
}
{
  "data": {
    "transfers": {
      "aggregates": {
        "count": "248",
        "sum": {
          "amount": "98500000000000",
          "blockNumber": "1234500"
        },
        "average": {
          "amount": "397177419354.84"
        },
        "min": {
          "blockNumber": 1
        },
        "max": {
          "blockNumber": 9999
        }
      }
    }
  }
}

Distinct Count

query {
  transfers {
    aggregates {
      distinctCount {
        fromId
        toId
      }
    }
  }
}
{
  "data": {
    "transfers": {
      "aggregates": {
        "distinctCount": {
          "fromId": "15",
          "toId": "22"
        }
      }
    }
  }
}

Statistical Aggregates

query {
  transfers {
    aggregates {
      stddevSample {
        blockNumber
      }
      stddevPopulation {
        blockNumber
      }
      varianceSample {
        blockNumber
      }
      variancePopulation {
        blockNumber
      }
    }
  }
}
{
  "data": {
    "transfers": {
      "aggregates": {
        "stddevSample": {
          "blockNumber": "2886.7513459481287"
        },
        "stddevPopulation": {
          "blockNumber": "2880.9238454923"
        },
        "varianceSample": {
          "blockNumber": "8333309.12345"
        },
        "variancePopulation": {
          "blockNumber": "8299731.98765"
        }
      }
    }
  }
}

Aggregates with Filters

Aggregates respect the connection's filter argument.

query {
  transfers(filter: { fromId: { equalTo: "0xAlice" } }) {
    totalCount
    aggregates {
      count
      sum {
        amount
      }
    }
  }
}
{
  "data": {
    "transfers": {
      "totalCount": 42,
      "aggregates": {
        "count": "42",
        "sum": {
          "amount": "42000000000000"
        }
      }
    }
  }
}

Grouped Aggregates

Use groupedAggregates with a groupBy argument to compute aggregates per group. Supports time-truncation variants: {COLUMN}_TRUNCATED_TO_HOUR and {COLUMN}_TRUNCATED_TO_DAY.

query {
  transfers {
    groupedAggregates(groupBy: [FROM_ID]) {
      keys
      sum {
        amount
      }
    }
  }
}
{
  "data": {
    "transfers": {
      "groupedAggregates": [
        {
          "keys": ["0xAlice"],
          "sum": { "amount": "42000000000000" }
        },
        {
          "keys": ["0xBob"],
          "sum": { "amount": "28000000000000" }
        }
      ]
    }
  }
}

Relations

Relations are automatically derived from foreign key constraints in the database schema.

Forward Relation (Many-to-One)

When a table has a foreign key column (e.g., transfers.from_id references accounts.id), the entity type gets a field that resolves to the single related parent record. The field name is derived from the FK column with _id stripped (e.g., from_id becomes from).

query {
  transfers(first: 3) {
    nodes {
      id
      amount
      from {
        id
        balance
      }
      to {
        id
        balance
      }
    }
  }
}
{
  "data": {
    "transfers": {
      "nodes": [
        {
          "id": "0x001",
          "amount": "1000000000",
          "from": {
            "id": "0xAlice",
            "balance": "50000000000000"
          },
          "to": {
            "id": "0xBob",
            "balance": "30000000000000"
          }
        },
        {
          "id": "0x002",
          "amount": "500000000",
          "from": {
            "id": "0xBob",
            "balance": "30000000000000"
          },
          "to": {
            "id": "0xCharlie",
            "balance": "10000000000000"
          }
        }
      ]
    }
  }
}

Backward Relation (One-to-Many as Connection)

When another table's FK points to this table, the entity type gets a connection field. The field is named {childEntities}By{FkColumn} (e.g., transfersByFromId).

This field supports the full set of connection arguments: first, last, after, before, offset, filter, orderBy, orderByNull, distinct.

query {
  account(id: "0xAlice") {
    id
    balance
    transfersByFromId(first: 5, orderBy: [BLOCK_NUMBER_DESC]) {
      totalCount
      nodes {
        id
        amount
        blockNumber
      }
    }
  }
}
{
  "data": {
    "account": {
      "id": "0xAlice",
      "balance": "50000000000000",
      "transfersByFromId": {
        "totalCount": 42,
        "nodes": [
          { "id": "0x200", "amount": "800000000", "blockNumber": 9500 },
          { "id": "0x198", "amount": "300000000", "blockNumber": 9200 },
          { "id": "0x180", "amount": "1500000000", "blockNumber": 8800 },
          { "id": "0x170", "amount": "200000000", "blockNumber": 8500 },
          { "id": "0x150", "amount": "950000000", "blockNumber": 8000 }
        ]
      }
    }
  }
}

One-to-One Backward Relation

When a foreign key has a unique constraint, the backward relation resolves to a single record instead of a connection.

query {
  account(id: "0xAlice") {
    id
    profileByAccountId {
      displayName
      avatarUrl
    }
  }
}
{
  "data": {
    "account": {
      "id": "0xAlice",
      "profileByAccountId": {
        "displayName": "Alice",
        "avatarUrl": "https://example.com/alice.png"
      }
    }
  }
}

Nested Relation Queries

Relations can be nested arbitrarily deep.

query {
  accounts(first: 2) {
    nodes {
      id
      transfersByFromId(first: 2) {
        nodes {
          id
          amount
          to {
            id
            balance
            transfersByFromId(first: 1) {
              nodes {
                id
                amount
              }
            }
          }
        }
      }
    }
  }
}
{
  "data": {
    "accounts": {
      "nodes": [
        {
          "id": "0xAlice",
          "transfersByFromId": {
            "nodes": [
              {
                "id": "0x001",
                "amount": "1000000000",
                "to": {
                  "id": "0xBob",
                  "balance": "30000000000000",
                  "transfersByFromId": {
                    "nodes": [
                      { "id": "0x002", "amount": "500000000" }
                    ]
                  }
                }
              }
            ]
          }
        }
      ]
    }
  }
}

Metadata

The _metadata and _metadatas queries provide information about the indexer state and the indexed chain.

Single Metadata Query

query {
  _metadata {
    lastProcessedHeight
    lastProcessedTimestamp
    targetHeight
    chain
    specName
    genesisHash
    indexerHealthy
    indexerNodeVersion
    queryNodeVersion
    dynamicDatasources
    deployments
    rowCountEstimate
    dbSize
  }
}
{
  "data": {
    "_metadata": {
      "lastProcessedHeight": 1234567,
      "lastProcessedTimestamp": "2025-01-15T10:30:00Z",
      "targetHeight": 1234600,
      "chain": "11155111",
      "specName": "sepolia",
      "genesisHash": "0xabcdef1234567890",
      "indexerHealthy": true,
      "indexerNodeVersion": "4.7.1",
      "queryNodeVersion": "0.2.0",
      "dynamicDatasources": "[]",
      "deployments": { "1234567": "QmXyz..." },
      "rowCountEstimate": [
        { "table": "transfers", "estimate": 248 },
        { "table": "accounts", "estimate": 50 }
      ],
      "dbSize": "52428800"
    }
  }
}

Metadata with chainId Filter

For multi-chain projects, pass the chainId argument to select a specific chain's metadata.

query {
  _metadata(chainId: "11155111") {
    chain
    lastProcessedHeight
  }
}
{
  "data": {
    "_metadata": {
      "chain": "11155111",
      "lastProcessedHeight": 1234567
    }
  }
}

All Metadatas (Connection)

The _metadatas query returns metadata for all chains in a connection format.

query {
  _metadatas {
    totalCount
    nodes {
      chain
      lastProcessedHeight
      queryNodeVersion
    }
    edges {
      cursor
      node {
        chain
      }
    }
  }
}
{
  "data": {
    "_metadatas": {
      "totalCount": 2,
      "nodes": [
        {
          "chain": "11155111",
          "lastProcessedHeight": 1234567,
          "queryNodeVersion": "0.2.0"
        },
        {
          "chain": "80002",
          "lastProcessedHeight": 987654,
          "queryNodeVersion": "0.2.0"
        }
      ],
      "edges": [
        { "cursor": "WzBd", "node": { "chain": "11155111" } },
        { "cursor": "WzFd", "node": { "chain": "80002" } }
      ]
    }
  }
}

Historical Queries

Tables that track historical state (those with a _block_range column) expose a blockHeight argument on their connection queries. This argument lets you query the state of entities at a specific block height.

When blockHeight is provided, only records whose _block_range contains that block height are returned. Without blockHeight, only the current (latest) version of each record is returned.

Query at a Specific Block Height

query {
  accounts(
    blockHeight: "5000000"
    first: 10
  ) {
    totalCount
    nodes {
      id
      balance
    }
  }
}
{
  "data": {
    "accounts": {
      "totalCount": 35,
      "nodes": [
        { "id": "0xAlice", "balance": "25000000000000" },
        { "id": "0xBob", "balance": "18000000000000" }
      ]
    }
  }
}

Historical Queries with Relations

Block height propagates to nested relation queries automatically. When you query transfers at a specific block height, forward and backward relations also respect that block height.

query {
  transfers(blockHeight: "5000000", first: 5) {
    nodes {
      id
      amount
      from {
        id
        balance
      }
    }
  }
}
{
  "data": {
    "transfers": {
      "nodes": [
        {
          "id": "0x050",
          "amount": "500000000",
          "from": {
            "id": "0xAlice",
            "balance": "25000000000000"
          }
        }
      ]
    }
  }
}

Note: Some schemas use timestamp instead of blockHeight for historical queries. The argument name is determined by the historicalStateEnabled metadata key. Pass a stringified integer in either case.


If your SubQuery project defines full-text search functions, they are exposed as root query fields. Each search function returns a connection of the target entity type.

Search Query

query {
  searchTransfers(search: "Alice Bob", first: 10) {
    totalCount
    nodes {
      id
      fromId
      toId
      amount
    }
  }
}
{
  "data": {
    "searchTransfers": {
      "totalCount": 15,
      "nodes": [
        {
          "id": "0x001",
          "fromId": "0xAlice",
          "toId": "0xBob",
          "amount": "1000000000"
        },
        {
          "id": "0x045",
          "fromId": "0xBob",
          "toId": "0xAlice",
          "amount": "200000000"
        }
      ]
    }
  }
}

Arguments:

ArgumentTypeDescription
searchString!The search query string. Internally sanitized and converted to a PostgreSQL tsquery.
firstIntMaximum number of results to return.
offsetIntNumber of results to skip.

Subscriptions

When the server is started with --subscription, entities expose real-time subscription fields over WebSocket (GraphQL over WebSocket protocol).

Each entity generates a subscription field named after the plural entity (e.g., transfers). The subscription streams {Entity}SubscriptionPayload events containing the affected record's id, the mutation_type, and the full _entity object.

Subscribe to All Changes

subscription {
  transfers {
    id
    mutation_type
    _entity {
      id
      fromId
      toId
      amount
      blockNumber
    }
  }
}

Example event pushed to the client:

{
  "data": {
    "transfers": {
      "id": "0x300",
      "mutation_type": "INSERT",
      "_entity": {
        "id": "0x300",
        "fromId": "0xAlice",
        "toId": "0xEve",
        "amount": "750000000",
        "blockNumber": 10050
      }
    }
  }
}

Subscribe with Filters

Filter subscriptions by id (list of IDs) and/or mutation (list of mutation types: INSERT, UPDATE, DELETE).

subscription {
  transfers(
    id: ["0x001", "0x002"]
    mutation: [INSERT, UPDATE]
  ) {
    id
    mutation_type
    _entity {
      id
      amount
    }
  }
}

WebSocket Connection

Connect using the graphql-ws protocol:

ws://localhost:3000/graphql

Example connection init message:

{
  "type": "connection_init",
  "payload": {}
}

Example subscribe message:

{
  "id": "1",
  "type": "subscribe",
  "payload": {
    "query": "subscription { transfers { id mutation_type } }"
  }
}

Batch Queries

Send multiple GraphQL operations in a single HTTP request by POSTing a JSON array. Each operation in the array is executed independently and returns its own result.

Request

curl -X POST http://localhost:3000/graphql \
  -H "Content-Type: application/json" \
  -d '[
    {
      "query": "{ transfers(first: 2) { nodes { id amount } } }"
    },
    {
      "query": "{ accounts(first: 2) { nodes { id balance } } }"
    }
  ]'

Response

[
  {
    "data": {
      "transfers": {
        "nodes": [
          { "id": "0x001", "amount": "1000000000" },
          { "id": "0x002", "amount": "500000000" }
        ]
      }
    }
  },
  {
    "data": {
      "accounts": {
        "nodes": [
          { "id": "0xAlice", "balance": "50000000000000" },
          { "id": "0xBob", "balance": "30000000000000" }
        ]
      }
    }
  }
]

Batch with Variables

curl -X POST http://localhost:3000/graphql \
  -H "Content-Type: application/json" \
  -d '[
    {
      "query": "query GetTransfers($count: Int!) { transfers(first: $count) { nodes { id } } }",
      "variables": { "count": 5 }
    },
    {
      "query": "query GetAccount($id: ID!) { account(id: $id) { id balance } }",
      "variables": { "id": "0xAlice" }
    }
  ]'

Node Interface

Every entity type implements the Node interface, which exposes a globally unique nodeId field. The root node query allows looking up any entity by its nodeId, regardless of type.

Fetching nodeId

The nodeId field is available on every entity. It is a base64-encoded JSON array: ["table_name", "_id_uuid"].

query {
  transfer(id: "0x001") {
    nodeId
    id
  }
}
{
  "data": {
    "transfer": {
      "nodeId": "WyJ0cmFuc2ZlcnMiLCJhM2YyYzFkOC0xMjM0LTU2NzgtOWFiYy1kZWYwMTIzNDU2NzgiXQ==",
      "id": "0x001"
    }
  }
}

Global Node Lookup

Use node(nodeId: ID!) with inline fragments to resolve any entity type.

query {
  node(nodeId: "WyJ0cmFuc2ZlcnMiLCJhM2YyYzFkOC0xMjM0LTU2NzgtOWFiYy1kZWYwMTIzNDU2NzgiXQ==") {
    ... on Transfer {
      id
      fromId
      toId
      amount
      blockNumber
    }
    ... on Account {
      id
      balance
    }
  }
}
{
  "data": {
    "node": {
      "id": "0x001",
      "fromId": "0xAlice",
      "toId": "0xBob",
      "amount": "1000000000",
      "blockNumber": 100
    }
  }
}

The node query decodes the nodeId to determine the table and internal UUID, then fetches the record and returns it with the correct GraphQL type so that inline fragments resolve properly.


Scalar Types

GraphQL TypeJSON SerializationNotes
String"text"
Int12332-bit integer
Float1.2364-bit floating point
Booleantrue / false
BigInt"9950040000"Serialized as a string to preserve precision
BigFloat"1234.5678"Serialized as a string to preserve precision
Date"2025-01-15"ISO 8601 date
Datetime"2025-01-15T10:30:00Z"RFC 3339 timestamp
JSON{...} / [...]Arbitrary JSON
Cursor"eyJpZCI6IjB4MDAxIn0="Base64-encoded JSON
ID"0x001"String identifier

On this page