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.
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:
- Mainnet: https://graphql.mainnet.sui.io/graphql
- Testnet: https://graphql.testnet.sui.io/graphql
- Devnet: https://graphql.devnet.sui.io/graphql
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.
- JSON-RPC (deprecated)
- GraphQL
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 }
]
}'
query {
object(address: "0xOBJECT_ID") {
address
owner {
... on AddressOwner {
address { address }
}
... on Shared {
initialSharedVersion
}
}
asMoveObject {
contents {
type { repr }
json
}
}
}
}
Key differences:
- JSON-RPC uses
showContent,showOwnerbooleans. 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
versionas an argument:object(address: "0x...", version: 42).
Multi-get objects
Replace sui_multiGetObjects with Query.multiGetObjects.
- JSON-RPC (deprecated)
- GraphQL
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 }
]
}'
query {
multiGetObjects(keys: [
{ objectId: "0xOBJECT_A" },
{ objectId: "0xOBJECT_B" },
{ objectId: "0xOBJECT_C" }
]) {
address
asMoveObject {
contents {
type { repr }
json
}
}
}
}
Key differences:
- GraphQL applies the same field selection to all objects in the batch.
- Each key can optionally include a
versionto fetch a specific version of that object.
Get a transaction
Replace sui_getTransactionBlock with Query.transaction.
- JSON-RPC (deprecated)
- GraphQL
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 }
]
}'
query {
transaction(digest: "Hay2tj3GcDYcE3AMHrej5WDsHGPVAYsegcubixLUvXUF") {
gasInput {
gasSponsor { address }
gasPrice
gasBudget
}
effects {
status
timestamp
checkpoint { sequenceNumber }
}
}
}
Key differences:
- JSON-RPC uses
showEffects,showEventsbooleans. GraphQL uses field selection: requesteffects,events,gasInput, or any combination. - GraphQL returns structured, typed data. For example,
effects.checkpointgives you the checkpoint directly without needing a separate lookup.
Multi-get transactions
Replace sui_multiGetTransactionBlocks with Query.multiGetTransactions.
- JSON-RPC (deprecated)
- GraphQL
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 }
]
}'
query {
multiGetTransactions(keys: ["DIGEST_A", "DIGEST_B"]) {
digest
effects {
status
timestamp
}
}
}
Get total transactions
Replace sui_getTotalTransactionBlocks with Checkpoint.networkTotalTransactions.
- JSON-RPC (deprecated)
- GraphQL
curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getTotalTransactionBlocks",
"params": []
}'
query {
checkpoint {
networkTotalTransactions
}
}
Key differences:
- Without an argument,
checkpointreturns the latest checkpoint. ReadnetworkTotalTransactionsfrom it for the current total.
Query filtered transactions
Replace suix_queryTransactionBlocks with Query.transactions and a TransactionFilter.
- JSON-RPC (deprecated)
- GraphQL
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
]
}'
query {
transactions(
first: 10,
filter: { sentAddress: "0xADDRESS" }
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
digest
sender { address }
effects {
status
timestamp
}
}
}
}
Key differences:
- GraphQL supports rich filters:
sentAddress,affectedAddress,affectedObject,function,kind, and more. - Pagination uses
first/after(forward) orlast/before(backward) with cursors frompageInfo. - 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.
- JSON-RPC (deprecated)
- GraphQL
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
]
}'
query {
events(
first: 50,
filter: {
type: "0xPACKAGE::module::MyEvent"
}
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
sender { address }
timestamp
contents {
type { repr }
json
}
}
}
}
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
MoveEventTypefilter becomestypein the GraphQLEventFilter. - For events from a known transaction, query the transaction directly and select
events.
Events from a known transaction
- JSON-RPC (deprecated)
- GraphQL
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
]
}'
query {
transaction(digest: "8NB8sXb4m9PJhCyLB7eVH4onqQWoFFzVUrqPoYUhcQe2") {
effects {
events {
nodes {
sender { address }
contents {
type { repr }
json
}
}
}
}
}
}
Look up a single checkpoint
Replace sui_getCheckpoint with Query.checkpoint.
- JSON-RPC (deprecated)
- GraphQL
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"]
}'
query {
checkpoint(sequenceNumber: 164329987) {
digest
networkTotalTransactions
timestamp
}
}
Key differences:
- Without a
sequenceNumberargument,checkpointreturns the latest. - GraphQL can also look up a checkpoint by
digest.
Paginate checkpoints
Replace sui_getCheckpoints polling with Query.checkpoints pagination.
- JSON-RPC (deprecated)
- GraphQL
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]
}'
query ($after: String) {
checkpoints(first: 10, after: $after) {
pageInfo {
hasNextPage
endCursor
}
nodes {
sequenceNumber
digest
timestamp
}
}
}
Key differences:
- GraphQL uses cursor-based pagination. Pass
pageInfo.endCursoras the$aftervariable for the next page. - For reverse order, use
lastandbeforeinstead offirstandafter. - For real-time streaming, use gRPC
SubscribeEventsorSubscribeTransactionsinstead. 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.
- JSON-RPC (deprecated)
- GraphQL
# 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"]
}'
# Single coin type
query {
address(address: "0xADDRESS") {
balance(coinType: "0x2::sui::SUI") {
coinType { repr }
totalBalance
}
}
}
# All coin types
query {
address(address: "0xADDRESS") {
balances {
nodes {
coinType { repr }
totalBalance
}
}
}
}
Key differences:
balancereturns a single balance for a specific coin type.balancesreturns a paginated list of all coin type balances.- The GraphQL balance includes
addressBalance,coinBalance, andtotalBalancefields. ReadtotalBalancefor the combined amount.
List owned coins
Replace suix_getCoins and suix_getAllCoins with Address.objects filtered by coin type.
- JSON-RPC (deprecated)
- GraphQL
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]
}'
query {
address(address: "0xADDRESS") {
objects(
first: 50,
filter: { type: "0x2::coin::Coin<0x2::sui::SUI>" }
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
address
contents { json }
}
}
}
}
To list coins of every type, use filter: { type: "0x2::coin::Coin" } without a type parameter.
Key differences:
- There is no dedicated coin-listing query. Use
Address.objectswith aCoin<T>type filter. - Pagination uses cursor-based
first/afterinstead of positional[cursor, limit]parameters.
Get coin metadata
Replace suix_getCoinMetadata with Query.coinMetadata.
- JSON-RPC (deprecated)
- GraphQL
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"]
}'
query {
coinMetadata(coinType: "0x2::sui::SUI") {
name
symbol
decimals
description
iconUrl
}
}
List owned objects
Replace suix_getOwnedObjects with Address.objects.
- JSON-RPC (deprecated)
- GraphQL
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]
}'
query {
address(address: "0xADDRESS") {
objects(first: 50) {
pageInfo {
hasNextPage
endCursor
}
nodes {
address
digest
contents {
type { repr }
json
}
}
}
}
}
Key differences:
- GraphQL supports
filteronobjectsto narrow by type, such asfilter: { 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.
- JSON-RPC (deprecated)
- GraphQL
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]
}'
query {
object(address: "0xPARENT_OBJECT_ID") {
dynamicFields(first: 50) {
pageInfo {
hasNextPage
endCursor
}
nodes {
name {
type { repr }
json
}
value {
__typename
... on MoveValue {
type { repr }
json
}
... on MoveObject {
contents {
type { repr }
json
}
}
}
}
}
}
}
Key differences:
- GraphQL returns both the name and value of each dynamic field in one query. JSON-RPC required a separate
suix_getDynamicFieldObjectcall for each value. - For a specific dynamic field by name, use
dynamicField(name: { type: "...", bcs: "..." })ordynamicObjectField(name: { type: "...", bcs: "..." })instead of paginating.
Get a specific dynamic field object
Replace suix_getDynamicFieldObject with Object.dynamicObjectField.
- JSON-RPC (deprecated)
- GraphQL
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" }
]
}'
query {
object(address: "0xPARENT_OBJECT_ID") {
dynamicObjectField(
name: { type: "0x1::string::String", bcs: "..." }
) {
value {
... on MoveObject {
address
contents {
type { repr }
json
}
}
}
}
}
}
Execute a transaction
Replace sui_executeTransactionBlock with Mutation.executeTransaction.
- JSON-RPC (deprecated)
- GraphQL
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 }
]
}'
mutation ($tx: Base64!, $sigs: [Base64!]!) {
executeTransaction(transactionDataBcs: $tx, signatures: $sigs) {
effects {
status
epoch {
startTimestamp
}
gasEffects {
gasSummary {
computationCost
}
}
}
}
}
Variables:
{
"tx": "<BASE64_TX_BYTES>",
"sigs": ["<BASE64_SIGNATURE>"]
}
Key differences:
- GraphQL uses a mutation, not a query.
- The
transactionDataBcsparameter takes the serialized unsigned transaction data. Use the Sui Client CLI--serialize-unsigned-transactionflag or the TypeScript SDK to generate it. - Select fields from
effectsin 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.
- JSON-RPC (deprecated)
- GraphQL
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>"]
}'
query ($tx: JSON!) {
simulateTransaction(
transaction: $tx,
checksEnabled: true,
doGasSelection: true
) {
effects {
status
gasEffects {
gasSummary {
computationCost
storageCost
}
}
}
outputs {
returnValues {
value { json }
}
}
}
}
Key differences:
simulateTransactionaccepts a JSON transaction matching the Sui gRPC transaction schema, or a BCS-encoded transaction with{"bcs": {"value": "<base64>"}}.- Set
doGasSelection: trueto have the service automatically select gas coins for the simulation.
Get reference gas price
Replace suix_getReferenceGasPrice with Epoch.referenceGasPrice.
- JSON-RPC (deprecated)
- GraphQL
curl -X POST https://fullnode.mainnet.sui.io:443 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getReferenceGasPrice",
"params": []
}'
query {
epoch {
referenceGasPrice
}
}
Get staking positions
Replace suix_getStakes and suix_getStakesByIds with Address.objects filtered by the StakedSui type.
- JSON-RPC (deprecated)
- GraphQL
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"]
}'
query {
address(address: "0xADDRESS") {
objects(
filter: { type: "0x3::staking_pool::StakedSui" }
) {
nodes {
address
contents { json }
}
}
}
}
Get Move module information
Replace sui_getNormalizedMoveModule, sui_getNormalizedMoveFunction, and sui_getNormalizedMoveStruct with Query.package and nested module, function, and struct fields.
- JSON-RPC (deprecated)
- GraphQL
# 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"]
}'
query {
package(address: "0x2") {
module(name: "coin") {
functions {
nodes {
name
parameters {
repr
}
return {
repr
}
}
}
structs {
nodes {
name
abilities
fields {
name
type { repr }
}
}
}
}
}
}
Key differences:
- GraphQL lets you query modules, functions, and structs in a single request through nested fields.
- Use
Query.packageVersionsto 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.