Emitting Events
Move code can interact with objects stored on Sui. Some applications track an object's activity and trigger certain workflows within the application based on that activity, such as application updates based on how many times an NFT is minted or the amount of SUI a smart contract generates.
Move supports activity monitoring through emitting events. Emitting events consists of three steps:
-
Defining an event struct in your Move module that captures the data you want to track.
-
Using the
emitfunction to emit events when relevant actions occur. -
Processing events using either a custom indexer or by polling the network.
Defining event struct
Event structs have the copy and drop abilities. In the event, define the fields you need to capture relevant data about the action.
public struct TestEvent has copy, drop {
message: ascii::String,
value: u64,
}
Using the emit function
Use the sui::event::emit function to emit an event when the action you want to monitor occurs:
fun init(_ctx: &mut TxContext) {
event::emit(TestEvent {
message: ascii::string(b"Package published successfully!"),
value: 42,
});
}
Processing events
After your Move code emits events, you need to process them. There are 2 approaches:
-
Custom indexer: Stream checkpoints and filter events continuously for real-time processing.
-
Polling: Query the Sui network periodically for emitted events. This approach requires a database to store retrieved data.
Event object structure
When you process events, each event object contains the following attributes:
-
id: JSON object containing the transaction digest ID and event sequence. -
packageId: The object ID of the package that emits the event. -
transactionModule: The module that performs the transaction. -
sender: The Sui network address that triggered the event. -
type: The type of event being emitted. -
parsedJson: JSON object describing the event. -
bcs: Binary canonical serialization value. -
timestampMs: Unix epoch timestamp in milliseconds.
public fun lock<T: key + store>(obj: T, ctx: &mut TxContext): (Locked<T>, Key) {
let key = Key { id: object::new(ctx) };
let mut lock = Locked {
id: object::new(ctx),
key: object::id(&key),
};
event::emit(LockCreated {
lock_id: object::id(&lock),
key_id: object::id(&key),
creator: ctx.sender(),
item_id: object::id(&obj),
});
dof::add(&mut lock.id, LockedObjectKey {}, obj);
(lock, key)
}
Querying events with gRPC
Use getTransaction with include: { events: true } to retrieve events for a specific transaction. For continuous event processing, stream finalized checkpoints with SubscriptionService.SubscribeCheckpoints and read events from each checkpoint's transactions. See the Event indexer example for a worked example.
import { SuiGrpcClient } from '@mysten/sui/grpc';
const client = new SuiGrpcClient({
baseUrl: 'https://fullnode.mainnet.sui.io:443',
network: 'mainnet',
});
async function getEventsForTransaction(digest: string) {
const result = await client.getTransaction({
digest,
include: { events: true },
});
if (result.$kind === 'Transaction') {
return result.Transaction.events ?? [];
}
return [];
}
Querying events with GraphQL
Use Query.events with an EventFilter to query events by type, sender, module, or checkpoint range.
Event connection
{
events(
filter: {type: "0x3164fcf73eb6b41ff3d2129346141bd68469964c2d95a5b1533e8d16e6ea6e13::Market::ChangePriceEvent<0x2::sui::SUI>"}
) {
nodes {
transactionModule {
name
package {
digest
}
}
sender {
address
}
timestamp
contents {
type {
repr
}
json
}
eventBcs
}
}
}
Querying events in Rust
Use the sui-rust-sdk crate to read events over gRPC. Fetch a transaction with its events through the LedgerService client, or stream checkpoints through SubscriptionService and read events from each checkpoint's transactions. See the sui-rust-sdk README for client setup and the gRPC service reference for the available RPCs.
The legacy sui_sdk::SuiClientBuilder JSON-RPC client is deprecated. Use sui-rust-sdk for new integrations.
JSON-RPC suix_queryEvents (deprecated)
The JSON-RPC suix_queryEvents method is deprecated and is planned for deactivation in July 2026. Migrate to gRPC or GraphQL. See the JSON-RPC Migration Guide for the full method mapping and shutoff timeline.
Filtering event queries
To filter the events that your queries return, use the following data structures.
JSON-RPC (deprecated)
These filters apply to the deprecated suix_queryEvents JSON-RPC method. For new code, use GraphQL EventFilter or read events from gRPC transaction and checkpoint responses.
| Query | Description | JSON-RPC parameter example |
|---|---|---|
All | All events | {"All": []} |
Any | Events emitted from any of the given filter | {"Any": SuiEventFilter[]} |
Transaction | Events emitted from the specified transaction | {"Transaction":"DGUe2TXiJdN3FI6MH1FwghYbiHw+NKu8Nh579zdFtUk="} |
MoveModule | Events emitted from the specified Move module | {"MoveModule":{"package":"<PACKAGE-ID>", "module":"nft"}} |
MoveEventModule | Events emitted, defined on the specified Move module | {"MoveEventModule": {"package": "<DEFINING-PACKAGE-ID>", "module": "nft"}} |
MoveEventType | Move struct name of the event | {"MoveEventType":"::nft::MintNFTEvent"} |
Sender | Query by sender address | {"Sender":"0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"} |
TimeRange | Return events emitted in [start_time, end_time] interval | {"TimeRange":{"startTime":1669039504014, "endTime":1669039604014}} |
GraphQL
To filter events queried using GraphQL, use one of the following workflows.
Filter events by sender using a GraphQL query
query ByTxSender {
events(
first: 1
filter: {
sender: "0xdff57c401e125a7e0e06606380560b459a179aacd08ed396d0162d57dbbdadfb"
}
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
transactionModule {
name
}
contents {
type {
repr
}
json
}
sender {
address
}
timestamp
eventBcs
}
}
}
The TypeScript SDK also can be used to interact with the Sui GraphQL service and filter events.
Filter events using the TypeScript SDK
import { SuiGraphQLClient } from '@mysten/sui/graphql';
import { graphql } from '@mysten/sui/graphql/schema';
const gqlClient = new SuiGraphQLClient({
url: 'https://graphql.mainnet.sui.io/graphql',
network: 'mainnet',
});
const queryEventsByType = graphql(`
query EventsByType($eventType: String!, $first: Int) {
events(filter: { type: $eventType }, first: $first) {
nodes {
transactionModule {
name
package { address }
}
sender { address }
contents {
type { repr }
json
}
timestamp
}
pageInfo {
hasNextPage
endCursor
}
}
}
`);
async function getEventsByType(eventType: string, first: number = 10) {
const result = await gqlClient.query({
query: queryEventsByType,
variables: { eventType, first },
});
return result.data?.events?.nodes ?? [];
}
Filtering events
Use the GraphQL EventFilter to narrow event queries. The available filter fields are type, module, sender, afterCheckpoint, atCheckpoint, and beforeCheckpoint. Note that you cannot combine module and type in the same filter.
{
events(
filter: {
type: "0xPACKAGE::swap::SwapEvent"
}
after: null
first: 50
) {
nodes {
contents { json }
timestamp
sender { address }
}
pageInfo {
hasNextPage
endCursor
}
}
}
Event limits
A single transaction can emit a maximum of 1,024 events (set by the max_num_event_emit protocol config). If your Move code exceeds this limit, the transaction aborts.
Pagination
Event queries return paginated results. In GraphQL, use the after parameter with the endCursor value from the previous response. The first field sets the page size (maximum items per response):
import { SuiGraphQLClient } from '@mysten/sui/graphql';
import { graphql } from '@mysten/sui/graphql/schema';
const gqlClient = new SuiGraphQLClient({
url: 'https://graphql.mainnet.sui.io/graphql',
network: 'mainnet',
});
const eventsQuery = graphql(`
query PaginatedEvents($eventType: String!, $first: Int, $after: String) {
events(filter: { type: $eventType }, first: $first, after: $after) {
nodes {
contents { json }
timestamp
}
pageInfo {
hasNextPage
endCursor
}
}
}
`);
let cursor = null;
let allEvents = [];
do {
const result = await gqlClient.query({
query: eventsQuery,
variables: { eventType: '0x...::module::EventType', first: 50, after: cursor },
});
const page = result.data?.events;
allEvents.push(...(page?.nodes ?? []));
cursor = page?.pageInfo.hasNextPage ? page.pageInfo.endCursor : null;
} while (cursor);
Troubleshooting
Events emitted but not returned by query
- Wrong package ID: Verify you query the correct package address (not the upgrade cap or transaction digest).
- Indexer lag: The indexer might not have processed the transaction yet. Add a short delay or use
waitForTransactionbefore querying. - Filter mismatch: Event type strings are case-sensitive and must match exactly, including the full
package::module::EventStructformat.
Public endpoints
These rate-limited endpoints are for development and public-good access. For production, run your own full node or use a dedicated provider. Both gRPC and GraphQL endpoints are available for each network.
gRPC (full node)
| Network | URL |
|---|---|
| Mainnet | https://fullnode.mainnet.sui.io:443 |
| Testnet | https://fullnode.testnet.sui.io:443 |
| Devnet | https://fullnode.devnet.sui.io:443 |
GraphQL RPC
| Network | URL |
|---|---|
| Mainnet | https://graphql.mainnet.sui.io/graphql |
| Testnet | https://graphql.testnet.sui.io/graphql |
| Devnet | https://graphql.devnet.sui.io/graphql |
For archival data beyond the recent window, see Archival Store and Service.