Sui exposes three distinct data-access surfaces: JSON-RPC, a legacy method-oriented interface; gRPC, a typed HTTP/2 + Protobuf surface used by the current SDK; and GraphQL, an indexer-backed query surface with a relational view and cursor pagination. They are not competing brands of the same thing but different trade-offs in typing, freshness, flexibility, and tooling. JSON-RPC is universal but chattier and being de-emphasised; gRPC offers efficient typed clients and streaming but requires code generation; GraphQL lets clients specify response shape and fetch nested data in one round-trip but can lag the chain tip because it is indexer-backed. Because GraphQL is indexer-backed, the same logical query can return data at a slightly different chain position than a direct node read, so do not mix surfaces in one consistency-critical flow without pinning checkpoints. This guide provides per-surface profiles, a decision table, runnable Node.js comparisons, a reproducible results table, and troubleshooting for choosing the right surface for your task.
Sui's Three Data-Access Surfaces at a Glance
Sui currently exposes three primary data-access surfaces, each with a different design centre. JSON-RPC is the legacy method-oriented surface: you call named methods like sui_getObject or sui_executeTransactionBlock and receive JSON responses. gRPC is the newer typed, HTTP/2 + Protobuf surface used by the current SDK; it defines services and messages in .proto files and generates client code. GraphQL is an indexer-backed query surface with a relational view and cursor pagination, letting clients request exactly the fields they need in a single round-trip.
These are not competing brands of the same thing. They differ in typing, freshness, flexibility, and tooling. JSON-RPC is universal and requires no schema, but it is chattier and being de-emphasised over time. gRPC is efficient and streaming-capable but needs code generation and HTTP/2. GraphQL is flexible and expressive but indexer-backed, so it can lag the chain tip. The Sui API references document these surfaces as distinct access paths, and the gRPC introduction explains the typed HTTP/2 RPC model that underpins the gRPC surface.
Choosing a surface is a decision about trade-offs, not a matter of one being universally better. For a broader orientation to Sui networking, see the Sui network page and the OnFinality Learn hub.
- JSON-RPC: method-oriented, universal, no schema, chattier, being de-emphasised.
- gRPC: typed HTTP/2 + Protobuf, efficient, streaming, requires codegen.
- GraphQL: client-specified shape, nested reads in one round-trip, indexer-backed so can lag the tip.
JSON-RPC: The Legacy Method-Oriented Surface
JSON-RPC on Sui follows the JSON-RPC 2.0 specification: you send a request object with jsonrpc, method, params, and id, and receive a response with either result or error. The surface is method-oriented—each operation is a named method such as sui_getObject, sui_getTransactionBlock, or sui_getLatestCheckpointSequenceNumber. Because it is schema-less at the transport level, you can call it from any HTTP client without code generation.
The trade-off is chattiness. Fetching an object with nested fields may require multiple calls: one for the object, another for its dynamic fields, another for the transaction that created it. There is no way to specify a response shape; you get the method's fixed JSON structure. JSON-RPC is also being de-emphasised over time, so new work should prefer gRPC or GraphQL where available. For a practical walkthrough of the JSON-RPC surface, see the Sui RPC guide (RPC Assistant).
Provider support for JSON-RPC is documented but varies by provider and network version. Some providers expose the full method set; others may restrict or deprecate specific methods. Always check your provider's documentation for the current method list.
- Universal: works from any HTTP client, no codegen.
- Chatty: nested reads may require multiple round-trips.
- Fixed response shape: no client-specified field selection.
- De-emphasised: new work should prefer gRPC or GraphQL.
gRPC: The Typed HTTP/2 + Protobuf Surface
gRPC is the newer typed surface used by the current Sui SDK. It uses HTTP/2 for transport and Protobuf for message serialisation, with services and messages defined in .proto files. The gRPC introduction describes the model: a client calls a method on a service, and the generated client handles serialisation, framing, and connection management. This gives you compile-time type checking and efficient binary payloads.
gRPC supports streaming, which is useful for ledger and checkpoint services. It also tends to be more efficient than JSON-RPC for high-volume reads because Protobuf payloads are smaller and HTTP/2 allows multiplexing. The cost is tooling: you need to generate client code from the .proto files, and your runtime must support HTTP/2. For a deeper dive, see the Sui gRPC API guide.
Like JSON-RPC, gRPC availability is documented but varies by provider and network version. Some providers expose the full service set; others may expose a subset. Check your provider's documentation for the current service and method list.
- Typed: compile-time checking via generated clients.
- Efficient: binary Protobuf payloads and HTTP/2 multiplexing.
- Streaming: supports server-side streaming for ledger and checkpoint data.
- Tooling cost: requires codegen and HTTP/2 support.
GraphQL: The Indexer-Backed Query Surface
GraphQL is an indexer-backed query surface with a relational view and cursor pagination. Unlike JSON-RPC and gRPC, GraphQL lets the client specify the response shape: you write a query that names exactly the fields you want, and the server returns only those fields. This is especially useful for nested reads—fetching an object along with its dynamic fields, owner, and recent transactions in a single round-trip. The GraphQL learn page explains the single-endpoint query model, schema, and introspection.
The key caveat is freshness. Because GraphQL is indexer-backed, it can lag the chain tip. The indexer ingests checkpoints and builds a relational view; there is a delay between a transaction being finalised on-chain and appearing in the index. This means the same logical query can return data at a slightly different chain position than a direct node read. For a deeper dive, see the Sui GraphQL API query surface.
GraphQL is well suited to historical aggregation, nested reads, and client-specified shapes. It is less suited to consistency-critical flows that must read at the exact tip. Provider support for GraphQL is documented but varies by provider and network version.
- Client-specified shape: request exactly the fields you need.
- Nested reads: fetch related data in one round-trip.
- Indexer-backed: can lag the chain tip.
- Cursor pagination: efficient for large result sets.
Decision Table: Mapping Common Tasks to Surfaces
The following table maps common Sui data tasks to the surface that best fits them. This is a starting point, not a rigid rule; your provider's supported surfaces and your consistency requirements may shift the choice.
For sending or inspecting a transaction, gRPC or JSON-RPC are the natural choices because they interact directly with the node. For fetching an object with nested fields, GraphQL is often more efficient because it avoids multiple round-trips. For streaming checkpoints, the ledger/stream service (typically exposed via gRPC streaming) is the right tool. For historical aggregation, GraphQL or an indexer is usually best.
- Send/inspect a transaction → gRPC or JSON-RPC.
- Fetch an object with nested fields → GraphQL.
- Stream checkpoints → ledger/stream service (gRPC streaming).
- Historical aggregation → GraphQL or indexer.
- Real-time push notifications → subscription channel (WebSocket), not any of the three request/response surfaces.
Consistency Caveat Across Surfaces
Because GraphQL is indexer-backed, the same logical query can return data at a slightly different chain position than a direct node read. If you mix surfaces in one consistency-critical flow—for example, reading an object via GraphQL and then submitting a transaction via gRPC—you may observe a state that is behind the tip. This is not a bug; it is an inherent property of an indexer-backed surface.
To avoid inconsistency, pin checkpoints. Read the latest checkpoint sequence number from a direct node surface (gRPC or JSON-RPC), then use that checkpoint as a consistency boundary for your GraphQL queries. Alternatively, keep consistency-critical flows on a single surface. The Sui checkpoint stream and ledger service provides a way to observe checkpoints as they are produced.
Do not assume that all surfaces are at the same chain position at the same wall-clock time. Treat freshness as a per-surface property and design your application accordingly.
- GraphQL can lag the tip because it is indexer-backed.
- Pin checkpoints when mixing surfaces in consistency-critical flows.
- Keep consistency-critical flows on a single surface where possible.
- Treat freshness as a per-surface property.
Runnable Node.js Comparison: JSON-RPC vs GraphQL
The following Node.js script runs one logical operation—fetching the same object—via a JSON-RPC call and via a GraphQL query. It prints the round-trips and response shape for each. Replace the endpoint URLs and object ID with values from your provider. This example uses the built-in fetch API available in modern Node.js.
The script measures wall-clock time for each call and prints the JSON response. It does not assert that one surface is faster; it gives you a reproducible method to compare on your own endpoint. Run it against your provider's JSON-RPC and GraphQL endpoints and record the results in the table in the next section.
// compare-jsonrpc-graphql.js
// Run with: node compare-jsonrpc-graphql.js
const JSONRPC_URL = 'https://your-provider.example/sui-jsonrpc';
const GRAPHQL_URL = 'https://your-provider.example/sui-graphql';
const OBJECT_ID = '0xYOUR_OBJECT_ID';
async function fetchJsonRpc() {
const body = {
jsonrpc: '2.0',
id: 1,
method: 'sui_getObject',
params: [OBJECT_ID, { showType: true, showOwner: true, showContent: true }]
};
const start = Date.now();
const res = await fetch(JSONRPC_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const json = await res.json();
const elapsed = Date.now() - start;
console.log('JSON-RPC round-trips: 1');
console.log('JSON-RPC elapsed ms:', elapsed);
console.log('JSON-RPC response shape:', JSON.stringify(json).slice(0, 200));
return json;
}
async function fetchGraphQL() {
const query = `
query GetObject($id: SuiAddress!) {
object(address: $id) {
address
version
digest
asMoveObject {
contents { type { repr } }
}
}
}
`;
const start = Date.now();
const res = await fetch(GRAPHQL_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables: { id: OBJECT_ID } })
});
const json = await res.json();
const elapsed = Date.now() - start;
console.log('GraphQL round-trips: 1');
console.log('GraphQL elapsed ms:', elapsed);
console.log('GraphQL response shape:', JSON.stringify(json).slice(0, 200));
return json;
}
(async () => {
await fetchJsonRpc();
await fetchGraphQL();
})();Runnable Node.js Comparison: gRPC Client (Optional)
If your provider exposes gRPC and you have generated the client from the .proto files, you can extend the comparison to include the gRPC surface. The following snippet assumes you have a generated client and a running gRPC endpoint. It fetches the same object and prints the round-trips and response shape. Because gRPC requires code generation, this example is illustrative; adapt it to your generated client's API.
The goal is not to declare a winner but to give you a reproducible method to measure round-trips, bytes, typing, and freshness against your own endpoint. Record your observations in the results table in the next section.
// compare-grpc.js (illustrative; requires generated client)
// Run with: node compare-grpc.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const PROTO_PATH = './sui.proto';
const GRPC_URL = 'your-provider.example:443';
const OBJECT_ID = '0xYOUR_OBJECT_ID';
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const suiProto = grpc.loadPackageDefinition(packageDefinition);
const client = new suiProto.sui.rpc.v2.LedgerService(
GRPC_URL,
grpc.credentials.createSsl()
);
const start = Date.now();
client.getObject({ objectId: OBJECT_ID }, (err, response) => {
const elapsed = Date.now() - start;
if (err) {
console.error('gRPC error:', err.message);
return;
}
console.log('gRPC round-trips: 1');
console.log('gRPC elapsed ms:', elapsed);
console.log('gRPC response shape:', JSON.stringify(response).slice(0, 200));
});Reproducible Results Table for Your Own Endpoint
Use the following table to record your own measurements. Run the scripts above against your provider's endpoints and fill in each row. Do not rely on published benchmark numbers; measure against the endpoint you actually use, because latency, throughput, and freshness vary by provider, region, and network version.
For each task, note the surface used, the number of round-trips, the approximate response bytes, whether the response is typed, the freshness relative to the chain tip, and any notes. This gives you a reproducible record that you can share with your team or revisit when your provider changes its supported surfaces.
- Task: the logical operation you performed (e.g., fetch object, send transaction).
- Surface used: JSON-RPC, gRPC, or GraphQL.
- Round-trips: number of network calls required.
- Bytes: approximate response size.
- Typed?: whether the response is type-checked (gRPC yes, JSON-RPC no, GraphQL schema-typed).
- Freshness vs tip: how far behind the chain tip the response was.
- Notes: any provider-specific caveats or errors.
Limitations and Trade-offs Across Surfaces
Surface availability varies by provider and network version. Some providers expose all three surfaces; others expose a subset. JSON-RPC is being phased down over time, so new work should prefer gRPC or GraphQL where available. GraphQL can lag the tip because it is indexer-backed. None of the three replaces a subscription channel for real-time push; for that, use a WebSocket subscription surface such as the one described in the Sui RPC WebSocket subscriptions guide.
There is no single surface that is best for every task. JSON-RPC is universal but chattier and de-emphasised. gRPC is typed and efficient but requires codegen and HTTP/2. GraphQL is flexible and expressive but indexer-backed and potentially stale. The right choice depends on your task, your consistency requirements, and your provider's supported surfaces.
When evaluating providers, consider the surfaces they expose, their documented freshness characteristics, and their pricing model. The RPC pricing page and the API service page describe OnFinality's offerings; always verify current surface support and limits with your provider.
- Availability varies by provider and network version.
- JSON-RPC is being phased down; prefer gRPC/GraphQL for new work.
- GraphQL can lag the tip; pin checkpoints for consistency.
- None of the three replaces a subscription channel for real-time push.
Troubleshooting Common Surface Selection Issues
If you see stale data from GraphQL, check whether you are reading at the tip. GraphQL is indexer-backed, so a query immediately after a transaction may not reflect it. Pin a checkpoint or wait for the indexer to catch up. If you need tip-consistent reads, use gRPC or JSON-RPC.
If your gRPC client fails to connect, verify that your runtime supports HTTP/2 and that you have generated the client from the correct .proto files. Some providers require TLS; others may use a different port. If JSON-RPC methods return errors, check whether the method is supported by your provider and whether it has been deprecated. Provider-specific behaviour is documented but varies, so consult your provider's documentation.
If you are mixing surfaces and seeing inconsistent results, isolate the flow to a single surface or pin checkpoints across surfaces. Record your observations in the results table so you can reproduce and diagnose the issue.
- Stale GraphQL data: pin checkpoints or use gRPC/JSON-RPC for tip reads.
- gRPC connection failures: check HTTP/2 support, codegen, TLS, and port.
- JSON-RPC errors: check method support and deprecation status.
- Inconsistent mixed-surface flows: isolate to one surface or pin checkpoints.
Next Steps for Building on Sui
Start by identifying the surfaces your provider exposes. Then map your tasks to the decision table above and run the comparison scripts against your endpoint. Record your results and revisit them when your provider changes its supported surfaces or when Sui network versions update.
For deeper dives, read the Sui gRPC API guide, the Sui GraphQL API query surface, and the Sui checkpoint stream and ledger service guide. For real-time push, see the Sui RPC WebSocket subscriptions guide. For provider-specific details, consult the Sui RPC guide (RPC Assistant) and the Sui network page.
As you build, keep consistency requirements explicit. Pin checkpoints when mixing surfaces, prefer gRPC or GraphQL for new work, and use subscription channels for real-time push. The OnFinality Learn hub collects related guides, and the API service page describes how to get started with managed endpoints.
- Identify your provider's supported surfaces.
- Map tasks to the decision table and measure with the scripts.
- Prefer gRPC or GraphQL for new work; use subscriptions for push.
- Pin checkpoints when mixing surfaces in consistency-critical flows.