Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Network & Protocol Guides12 min read

Reading Sui Objects Over RPC: getObject, Dynamic Fields, and Pagination

Learn how to read Sui objects via JSON-RPC: getObject, multiGetObjects, dynamic fields, owned objects, and cursor pagination.

TL;DR

Reading Sui objects over JSON-RPC requires understanding the object model: each object has an ID, version, and digest, and ownership can be address, object, shared, or immutable. Use sui_getObject for single reads, suix_multiGetObjects for batches, suix_getDynamicFields to page through dynamic fields, and suix_getOwnedObjects to list an address's primary objects. This guide explains the mechanics, provides runnable curl examples, and includes a troubleshooting checklist for common issues like missing objects and pagination loops.

Direct Answer: How to Read Sui Objects Correctly

To read Sui objects over JSON-RPC, you must first understand that a Sui object is not a simple key-value pair. Each object is identified by a 32-byte object ID, has an incrementing version integer that changes on every write, and a digest (hash) that also changes with each write. Ownership can be Address-owned, Object-owned, Shared, or Immutable. To fetch the current state of an object, call suix_getObject (or the legacy sui_getObject) with the object ID. To fetch multiple objects in one request, use suix_multiGetObjects. To list the objects owned by an address, use suix_getOwnedObjects with an optional filter. Dynamic fields—which allow objects to nest arbitrary data—are read separately via suix_getDynamicFields (to page through them) and suix_getDynamicFieldObject (to fetch a single one). Pagination is cursor-based, and you must handle the nextCursor and hasNextPage fields correctly to avoid loops. This guide walks through each method with runnable examples and a troubleshooting checklist.

The Sui Object Model: ID, Version, Digest, and Ownership

In Sui, everything is an object. The Move-based model stores objects in a global map keyed by a 32-byte ObjectID. Each object has a version (a monotonically increasing integer) and a digest (a hash of the object's contents and version). When an object is mutated, its version increments and its digest changes. This triple—ID, version, digest—is the foundation of all reads. Ownership can be one of four types: Address-owned (controlled by a single address), Object-owned (owned by another object, enabling hierarchical structures), Shared (accessible by anyone, often used for shared state), and Immutable (cannot be mutated, such as published packages).

The official Sui documentation explains that objects can be wrapped inside other objects, which affects their visibility. For example, a wrapped object is no longer directly accessible by its ID; it is 'hidden' inside the parent object. This is a common source of confusion when a getObject call returns not-found even though the object exists on-chain.

When you read an object, you receive its current state, including the data layout (e.g., moveObject or package), the owner, and the reference (ID, version, digest). If you request only the reference (by setting showContent to false), you get a lightweight response that is useful for tracking changes without downloading full content.

Reading Objects: sui_getObject and suix_multiGetObjects

The primary method to read a single object is suix_getObject (the legacy sui_getObject is deprecated but still works on many nodes). It takes an object ID and a set of display options (e.g., showContent, showOwner, showType). The response includes the object's objectId, version, digest, owner, and the data field with the Move type and fields.

For batch reads, use suix_multiGetObjects with an array of IDs and the same display options. This is efficient when you need to fetch multiple objects in one round-trip, reducing latency and rate-limit usage. Note that the response order matches the request order, and if an object does not exist, the corresponding entry will be null.

Here is a runnable curl example using a public Sui RPC endpoint (replace the URL with your own provider if needed). The example fetches a known object (a Sui coin) and prints the response:

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

Listing Owned Objects: suix_getOwnedObjects and Filters

To enumerate the objects owned by an address, use suix_getOwnedObjects. This method returns the 'primary' owned objects of an address—that is, objects that are directly owned by the address and not wrapped inside another object. It does not return dynamic fields or coin balances by default. To filter by type or package, use the filter parameter with StructType or Package filters.

For example, to list all SUI coins owned by an address, you would filter by 0x2::coin::Coin<0x2::sui::SUI>. The response includes a cursor for pagination. Here is a curl example:

curl -X POST https://fullnode.mainnet.sui.io:443 \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "suix_getOwnedObjects",
    "params": [
      "0xYOUR_ADDRESS",
      {
        "filter": {
          "StructType": "0x2::coin::Coin<0x2::sui::SUI>"
        },
        "options": {
          "showContent": true,
          "showOwner": true
        },
        "limit": 10
      }
    ]
  }'

Dynamic Fields: Reading Nested Data

Sui objects can contain dynamic fields, which are key-value pairs stored on the object itself. These fields allow for flexible data structures that can be added or removed over time. Dynamic fields are not part of the object's fixed schema; they are stored separately and must be read with dedicated methods.

To page through all dynamic fields of a parent object, use suix_getDynamicFields with the parent object ID, a cursor, and a limit. The response includes a list of dynamic field names (base58-encoded) and their types, along with a nextCursor for pagination. To fetch a specific dynamic field's value, use suix_getDynamicFieldObject with the parent ID and the field name (as a DynamicFieldName object with type and value).

Here is an example of paging through dynamic fields:

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,  // cursor
      10     // limit
    ]
  }'

Pagination with Cursors: Avoiding Loops and Missing Data

All list methods in Sui RPC (e.g., suix_getOwnedObjects, suix_getDynamicFields) use cursor-based pagination. The response includes a nextCursor field and a hasNextPage boolean. To fetch the next page, pass the nextCursor as the cursor parameter in the next request. If hasNextPage is false, you have reached the end.

A common mistake is to use the same cursor repeatedly, causing an infinite loop. Always update the cursor from the response. Also, note that the limit parameter is capped by the node provider; the default is often 50, but it can vary. If you set a limit higher than the cap, the node may return an error or silently cap it. Check your provider's documentation for the exact limit.

Here is a fill-in results table to record your own pagination results:

  • | Page | Cursor Used | Objects Returned | nextCursor | hasNextPage |
  • |------|-------------|------------------|------------|-------------|
  • | 1 | null | [fill in] | [fill in] | [fill in] |
  • | 2 | [fill in] | [fill in] | [fill in] | [fill in] |
  • | ... | ... | ... | ... | ... |

Reading Historical Versions and Archive Nodes

To read a specific historical version of an object, you must request that version explicitly. The suix_getObject method accepts an optional version parameter (as part of the SuiObjectDataOptions? Actually, the standard method does not support version; you need to use suix_tryGetPastObject or similar). In the legacy API, sui_getObject does not support version; you must use sui_tryGetPastObject (now suix_tryGetPastObject) to fetch a past version. This method requires the object ID and the desired version number.

However, past versions are only available on archive nodes that store the full history. A fullnode that is not an archive node will only have the latest state and a limited checkpoint history. If you request a version that is not available, the node returns an error. For applications that need full object history, you must connect to an archive RPC provider. OnFinality offers Sui archive nodes and historical data that retain the complete history.

Alternatively, you can track object changes via events or the suix_getObject with showPreviousTransaction to see the last transaction that modified the object, but that does not give you the full history.

Troubleshooting Checklist: Common Failures and Fixes

When reading Sui objects, you may encounter several common issues. Use this checklist to diagnose and fix them:

  • Object not found: If suix_getObject returns null or an error, the object may have been wrapped, deleted, or never existed. Check the object ID for typos. If the object was wrapped, it is no longer directly accessible; you must read it through its parent object's dynamic fields.
  • Immutable object semantics: Immutable objects (like packages) never change. Their version is always 1, and the digest is constant. If you expect a version increment, you are likely looking at the wrong object.
  • Field does not exist: When using suix_getDynamicFieldObject, ensure the field name and type exactly match the stored key. Dynamic field names are base58-encoded and type-sensitive. A mismatch returns an error.
  • Version not available: If you request a historical version on a non-archive node, you get an error. Use an archive node or adjust your query to the latest version.
  • Cursor misuse: Always use the nextCursor from the previous response. If you pass the same cursor, you may loop infinitely. Also, ensure you handle hasNextPage correctly.
  • Pagination limit caps: The limit parameter is capped by the provider. If you exceed it, the node may return an error or truncate results. Check your provider's documentation for the maximum limit. On OnFinality, limits are documented and may vary by plan; see RPC pricing.

Limitations and Tradeoffs of Object Reads

Reading Sui objects over RPC has inherent limitations. First, suix_getOwnedObjects only returns primary owned objects; it does not include objects nested in dynamic fields or coins that are not directly owned. To get a complete inventory, you must recursively traverse dynamic fields, which can be expensive.

Second, object reads are point-in-time. To track changes over time, you must poll the object's version and digest, or subscribe to events. Polling can be rate-limit intensive; consider using WebSocket subscriptions for real-time updates. OnFinality's Sui WebSocket event subscriptions guide explains how to set this up.

Third, the JSON-RPC API is migrating toward a typed SDK. In Sui 2.0, many raw methods are being deprecated in favor of SDK methods that abstract the RPC layer. This migration is non-breaking for now, but you should plan to update your code. The official Sui JSON-RPC Migration Guide provides details.

Finally, the cost of reading many objects can add up. Batch reads with suix_multiGetObjects are more efficient than individual calls. For large-scale data extraction, consider using a dedicated data service or indexing.

Next Steps and Further Reading

Now that you understand how to read Sui objects, you can apply this knowledge to build asset trackers, inventory systems, or any application that needs to query on-chain state. To deepen your understanding, explore the following resources:

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started