Querying Data
Read entities from Arkiv using the TypeScript SDK’s public client and its chainable query builder — with support for field selection, filtering, pagination, and payload retrieval.
PublicClient Setup
Section titled “PublicClient Setup”All queries use the read-only public client:
import { createPublicClient } from "@arkiv-network/sdk"import { braga } from "@arkiv-network/sdk/chains"import { http } from "viem"
const publicClient = createPublicClient({ chain: braga, transport: http(),})Building Queries
Section titled “Building Queries”Use select() to declare which entity fields you want returned, then chain filters and execute with .fetch():
import { eq, gt } from "@arkiv-network/sdk/query"
const result = await publicClient .select({ key: true, payload: true, attributes: true }) .where(eq('type', 'note'), gt('created', Date.now() - 86400000)) .limit(10) .fetch()
console.log('Found:', result.entities.length)Selecting Fields
Section titled “Selecting Fields”Pass nothing (or "*") to fetch everything, or pass an object to fetch only specific fields:
// All fieldsawait publicClient.select().where(eq('type', 'note')).fetch()
// Only the fields you needawait publicClient .select({ key: true, owner: true, payload: true }) .where(eq('type', 'note')) .fetch()Available fields: key, owner, creator, contentType, payload, attributes, expiresAtBlock, createdAtBlock, lastModifiedAtBlock, transactionIndexInBlock, operationIndexInTransaction.
The result type is inferred from your selection. The toText() / toJson() payload helpers are available only when you select payload.
const [entity] = (await publicClient.select({ owner: true, payload: true }).fetch()).entities
entity.owner // ✅ Hexentity.toJson() // ✅ payload was selectedentity.creator // ❌ compile error — not selectedQuery Builder Methods
Section titled “Query Builder Methods”| Method | Description |
|---|---|
.where(...conditions) | Add filter conditions (chainable) |
.ownedBy(address) | Filter by current owner |
.createdBy(address) | Filter by original creator (immutable) |
.limit(n) | Limit number of results |
.fetch() | Execute the query |
Passing Multiple Conditions
Section titled “Passing Multiple Conditions”You can chain .where() calls, pass several conditions as arguments, or pass an array — all conditions are combined with AND:
// Chained — each .where() adds an AND conditionconst result = await publicClient .select({ key: true, payload: true }) .where(eq('type', 'note')) .where(gt('priority', 3)) .fetch()
// Varargs — equivalent to aboveconst result = await publicClient .select({ key: true, payload: true }) .where(eq('type', 'note'), gt('priority', 3)) .fetch()
// Array syntax — also equivalentconst result = await publicClient .select({ key: true, payload: true }) .where([eq('type', 'note'), gt('priority', 3)]) .fetch()Query Operators
Section titled “Query Operators”import { eq, gt, lt, gte, lte } from "@arkiv-network/sdk/query"
eq('type', 'note') // type = "note"gt('priority', 3) // priority > 3lt('price', 1000) // price < 1000gte('created', timestamp) // created >= timestamplte('expiration', limit) // expiration <= limitCombining with and() / or()
Section titled “Combining with and() / or()”Nest conditions with and() and or(). Both accept the predicates as separate arguments or as a single array:
import { and, or, eq, gt } from "@arkiv-network/sdk/query"
// type = "note" AND (priority > 3 OR pinned = "true")const result = await publicClient .select({ key: true, payload: true }) .where(eq('type', 'note'), or(gt('priority', 3), eq('pinned', 'true'))) .fetch()
// Array form works too: or([gt('priority', 3), eq('pinned', 'true')])Filtering by Owner and Creator
Section titled “Filtering by Owner and Creator”Every entity has two metadata fields:
$owner— The wallet that currently owns the entity. Can change via ownership transfer.$creator— The wallet that originally created the entity. Immutable — can never change.
// Filter by current ownerconst owned = await publicClient .select({ key: true, owner: true, payload: true }) .where(eq('type', 'note')) .ownedBy('0xOwnerAddress') .fetch()
// Filter by original creator (tamper-proof)const created = await publicClient .select({ key: true, creator: true, payload: true }) .where(eq('type', 'note')) .createdBy('0xCreatorAddress') .fetch()Getting a Single Entity
Section titled “Getting a Single Entity”Retrieve a specific entity by key:
const entity = await publicClient.getEntity(entityKey)
// Parse the payloadconst data = entity.toJson() // JSON payloadconst text = entity.toText() // Text payloadReading Results
Section titled “Reading Results”const result = await publicClient .select({ key: true, payload: true, attributes: true }) .where(eq('type', 'note')) .fetch()
for (const entity of result.entities) { // Access payload (payload was selected) const data = entity.toJson() console.log(data.title, data.content)
// Access entity key (key was selected) console.log('Key:', entity.key)
// Access attributes (attributes were selected) for (const attr of entity.attributes) { console.log(`${attr.key}: ${attr.value}`) }}Pagination
Section titled “Pagination”Arkiv uses cursor-based pagination. Results per page are capped at 200.
const result = await publicClient .select({ key: true, payload: true }) .where(eq('type', 'note')) .limit(10) .fetch()
console.log('Page 1:', result.entities.length)
// Check for more pagesif (result.hasNextPage()) { await result.next() console.log('Page 2:', result.entities.length)}Sorting Results
Section titled “Sorting Results”The network always returns matching entities newest first. Ordering by anything else is not supported server-side. To sort by an attribute, fetch the entities and sort them in JavaScript:
const { entities } = await publicClient .select({ key: true, payload: true, attributes: true }) .where(eq('type', 'note')) .fetch()
// Read the numeric "priority" attribute off an entityconst priorityOf = (entity: (typeof entities)[number]) => Number(entity.attributes.find((attr) => attr.key === 'priority')?.value ?? 0)
// Highest priority firstentities.sort((a, b) => priorityOf(b) - priorityOf(a))