Skip to main content

GraphQL Migration Cookbook

Each recipe pairs a JSON-RPC call with its GraphQL replacement and explains the key differences. For the full method mapping and decision criteria, see the JSON-RPC Migration Guide. For GraphQL concepts, pagination, and scope, see GraphQL for Sui RPC.

info

JSON-RPC is deprecated. Migrate to either gRPC or GraphQL RPC before the week of July 27, 2026, when JSON-RPC is disabled on Sui Foundation mainnet full nodes. Full decommission, including code removal, is planned for mid-October 2026. For a method mapping, decision criteria, and the full timeline, see the JSON-RPC Migration Guide.

Refer to the list of RPC or data providers that have enabled gRPC on their full nodes or offer GraphQL RPC. Contact a provider directly to request access. If your RPC or data provider doesn’t yet support these data access methods, ask them to enable support or contact the Sui Foundation team on Discord, Telegram, or Slack for help.

Before you start

GraphQL queries run against the online IDE or any GraphQL endpoint. Access the service at:

For curl examples, replace <GRAPHQL_ENDPOINT> with your provider's GraphQL URL. See GraphQL for Sui RPC for headers, variables, fragments, and pagination details.

curl -X POST <GRAPHQL_ENDPOINT> \
-H "Content-Type: application/json" \
-d '{ "query": "{ epoch { referenceGasPrice } }" }'

Get a single object

Replace sui_getObject with Query.object. Select only the fields you need instead of toggling showContent, showOwner booleans.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getObject",
"params": [
"0xOBJECT_ID",
{ "showContent": true, "showOwner": true }
]
}'

Key differences:

  • JSON-RPC uses showContent, showOwner booleans. GraphQL uses field selection: request only the fields you need.
  • GraphQL returns typed owner information. Use inline fragments (... on AddressOwner, ... on Shared) to access owner details.
  • To fetch an object at a specific version, pass version as an argument: object(address: "0x...", version: 42).

Multi-get objects

Replace sui_multiGetObjects with Query.multiGetObjects.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_multiGetObjects",
"params": [
["0xOBJECT_A", "0xOBJECT_B", "0xOBJECT_C"],
{ "showContent": true }
]
}'

Key differences:

  • GraphQL applies the same field selection to all objects in the batch.
  • Each key can optionally include a version to fetch a specific version of that object.

Get a transaction

Replace sui_getTransactionBlock with Query.transaction.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getTransactionBlock",
"params": [
"Hay2tj3GcDYcE3AMHrej5WDsHGPVAYsegcubixLUvXUF",
{ "showEffects": true, "showEvents": true }
]
}'

Key differences:

  • JSON-RPC uses showEffects, showEvents booleans. GraphQL uses field selection: request effects, events, gasInput, or any combination.
  • GraphQL returns structured, typed data. For example, effects.checkpoint gives you the checkpoint directly without needing a separate lookup.

Multi-get transactions

Replace sui_multiGetTransactionBlocks with Query.multiGetTransactions.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_multiGetTransactionBlocks",
"params": [
["DIGEST_A", "DIGEST_B"],
{ "showEffects": true, "showEvents": true }
]
}'

Get total transactions

Replace sui_getTotalTransactionBlocks with Checkpoint.networkTotalTransactions.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getTotalTransactionBlocks",
"params": []
}'

Key differences:

  • Without an argument, checkpoint returns the latest checkpoint. Read networkTotalTransactions from it for the current total.

Query filtered transactions

Replace suix_queryTransactionBlocks with Query.transactions and a TransactionFilter.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_queryTransactionBlocks",
"params": [
{ "filter": { "FromAddress": "0xADDRESS" } },
null, 10, false
]
}'

Key differences:

  • GraphQL supports rich filters: sentAddress, affectedAddress, affectedObject, function, kind, and more.
  • Pagination uses first/after (forward) or last/before (backward) with cursors from pageInfo.
  • JSON-RPC's positional [filter, cursor, limit, descending] array is replaced by named arguments.

Query events

Replace suix_queryEvents with Query.events and an EventFilter.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_queryEvents",
"params": [
{ "MoveEventType": "0xPACKAGE::module::MyEvent" },
null, 50, false
]
}'

Key differences:

  • GraphQL supports server-side event filtering by type, module, sender, and checkpoint bounds (afterCheckpoint, beforeCheckpoint, atCheckpoint). No client-side filtering needed.
  • JSON-RPC's MoveEventType filter becomes type in the GraphQL EventFilter.
  • For events from a known transaction, query the transaction directly and select events.

Events from a known transaction

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_queryEvents",
"params": [
{ "Transaction": "8NB8sXb4m9PJhCyLB7eVH4onqQWoFFzVUrqPoYUhcQe2" },
null, 50, false
]
}'

Look up a single checkpoint

Replace sui_getCheckpoint with Query.checkpoint.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getCheckpoint",
"params": ["164329987"]
}'

Key differences:

  • Without a sequenceNumber argument, checkpoint returns the latest.
  • GraphQL can also look up a checkpoint by digest.

Paginate checkpoints

Replace sui_getCheckpoints polling with Query.checkpoints pagination.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getCheckpoints",
"params": [null, 10, false]
}'

Key differences:

  • GraphQL uses cursor-based pagination. Pass pageInfo.endCursor as the $after variable for the next page.
  • For reverse order, use last and before instead of first and after.
  • For real-time streaming, use gRPC SubscribeEvents or SubscribeTransactions instead. GraphQL has subscription infrastructure but gRPC is the production streaming path today.

Get balances

Replace suix_getBalance and suix_getAllBalances with Address.balance and Address.balances.

# Single coin type
curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getBalance",
"params": ["0xADDRESS", "0x2::sui::SUI"]
}'

# All coin types
curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getAllBalances",
"params": ["0xADDRESS"]
}'

Key differences:

  • balance returns a single balance for a specific coin type. balances returns a paginated list of all coin type balances.
  • The GraphQL balance includes addressBalance, coinBalance, and totalBalance fields. Read totalBalance for the combined amount.

List owned coins

Replace suix_getCoins and suix_getAllCoins with Address.objects filtered by coin type.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getCoins",
"params": ["0xADDRESS", "0x2::sui::SUI", null, 50]
}'

Key differences:

  • There is no dedicated coin-listing query. Use Address.objects with a Coin<T> type filter.
  • Pagination uses cursor-based first/after instead of positional [cursor, limit] parameters.

Get coin metadata

Replace suix_getCoinMetadata with Query.coinMetadata.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getCoinMetadata",
"params": ["0x2::sui::SUI"]
}'

List owned objects

Replace suix_getOwnedObjects with Address.objects.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getOwnedObjects",
"params": ["0xADDRESS", null, null, 50]
}'

Key differences:

  • GraphQL supports filter on objects to narrow by type, such as filter: { type: "0x2::coin::Coin" }.
  • Pagination is consistent: cursors encode the checkpoint at which the query was first executed, so later pages reflect the same state.

List dynamic fields

Replace suix_getDynamicFields with Object.dynamicFields.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getDynamicFields",
"params": ["0xPARENT_OBJECT_ID", null, 50]
}'

Key differences:

  • GraphQL returns both the name and value of each dynamic field in one query. JSON-RPC required a separate suix_getDynamicFieldObject call for each value.
  • For a specific dynamic field by name, use dynamicField(name: { type: "...", bcs: "..." }) or dynamicObjectField(name: { type: "...", bcs: "..." }) instead of paginating.

Get a specific dynamic field object

Replace suix_getDynamicFieldObject with Object.dynamicObjectField.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getDynamicFieldObject",
"params": [
"0xPARENT_OBJECT_ID",
{ "type": "0x1::string::String", "value": "my_key" }
]
}'

Execute a transaction

Replace sui_executeTransactionBlock with Mutation.executeTransaction.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_executeTransactionBlock",
"params": [
"<BASE64_TX_BYTES>",
["<BASE64_SIGNATURE>"],
{ "showEffects": true }
]
}'

Key differences:

  • GraphQL uses a mutation, not a query.
  • The transactionDataBcs parameter takes the serialized unsigned transaction data. Use the Sui Client CLI --serialize-unsigned-transaction flag or the TypeScript SDK to generate it.
  • Select fields from effects in the same mutation to read execution results immediately without waiting for a separate indexed query.
  • The JSON-RPC unsafe_* builder methods (unsafe_moveCall, unsafe_transferObject, and others) have no GraphQL equivalent. Build transactions with programmable transaction blocks (PTBs) using the SDK, then submit the bytes.

Simulate a transaction (dry run)

Replace sui_dryRunTransactionBlock and sui_devInspectTransactionBlock with Query.simulateTransaction.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_dryRunTransactionBlock",
"params": ["<BASE64_TX_BYTES>"]
}'

Key differences:

  • simulateTransaction accepts a JSON transaction matching the Sui gRPC transaction schema, or a BCS-encoded transaction with {"bcs": {"value": "<base64>"}}.
  • Set doGasSelection: true to have the service automatically select gas coins for the simulation.

Get reference gas price

Replace suix_getReferenceGasPrice with Epoch.referenceGasPrice.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getReferenceGasPrice",
"params": []
}'

Get staking positions

Replace suix_getStakes and suix_getStakesByIds with Address.objects filtered by the StakedSui type.

curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getStakes",
"params": ["0xADDRESS"]
}'

Get Move module information

Replace sui_getNormalizedMoveModule, sui_getNormalizedMoveFunction, and sui_getNormalizedMoveStruct with Query.package and nested module, function, and struct fields.

# Get a normalized Move module
curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getNormalizedMoveModule",
"params": ["0x2", "coin"]
}'

Key differences:

  • GraphQL lets you query modules, functions, and structs in a single request through nested fields.
  • Use Query.packageVersions to browse all versions of a package.

Common patterns

Paginating through all owned objects

Use the cursor from pageInfo.endCursor to fetch subsequent pages:

# First page
query {
address(address: "0xADDRESS") {
objects(first: 50) {
pageInfo {
hasNextPage
endCursor
}
nodes {
address
}
}
}
}

# Next page: pass endCursor as the $after variable
query ($after: String!) {
address(address: "0xADDRESS") {
objects(first: 50, after: $after) {
pageInfo {
hasNextPage
endCursor
}
nodes {
address
}
}
}
}

Cursors for live object queries guarantee consistent pagination. They encode the checkpoint at which the query was first executed, so later pages reflect the same snapshot even as new checkpoints arrive.

Time-travel queries with checkpoint scope

Pin a query to a specific checkpoint to see historical state:

query {
checkpoint(sequenceNumber: 164329987) {
query {
address(address: "0xADDRESS") {
balance(coinType: "0x2::sui::SUI") {
totalBalance
}
}
}
}
}

Combining multiple lookups in one request

GraphQL can join data from multiple resources in a single request. For example, fetch a transaction, its events, and an object in one query:

query {
transaction(digest: "DIGEST") {
effects {
status
events {
nodes {
contents { json }
}
}
}
}
object(address: "0xOBJECT_ID") {
asMoveObject {
contents { json }
}
}
}

Checking data retention before paginating

Before starting a long pagination run, check the available checkpoint range:

query {
serviceConfig {
availableRange(
type: "Query",
field: "transactions",
filters: ["affectedAddress"]
) {
first { sequenceNumber }
last { sequenceNumber }
}
}
}

Migration gotchas

Use gRPC for production streaming

The GraphQL service has subscription infrastructure but it is not yet generally available for production use. For real-time streaming, use gRPC: SubscribeEvents for events and SubscribeTransactions for transactions. Both support server-side filtering so your client only receives matching data. For historical event queries over a range, use Query.events with an EventFilter.

Pagination uses typed cursors

GraphQL passes cursors through after or before fields on connections, with page size in first or last. Do not reuse JSON-RPC nextCursor values. Fetch fresh cursors from the GraphQL response's pageInfo.

Retention windows

Different queries hit different data sources with different retention. Live object set queries (balances, owned objects) rely on the Consistent Store with ~1 hour retention. Historical queries (transactions, events) rely on the database with ~30–90 days retention. Point lookups can route to the Archival Service for indefinite retention when configured. Use serviceConfig.availableRange to check retention before querying.

Public endpoints are not production endpoints

The public URLs at https://graphql.<NETWORK>.sui.io/graphql are rate-limited and intended for development. Production apps should use a provider endpoint or operate their own GraphQL and Indexer stack.