Skip to content

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.

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(),
})

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)

Pass nothing (or "*") to fetch everything, or pass an object to fetch only specific fields:

// All fields
await publicClient.select().where(eq('type', 'note')).fetch()
// Only the fields you need
await 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 // ✅ Hex
entity.toJson() // ✅ payload was selected
entity.creator // ❌ compile error — not selected
MethodDescription
.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

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 condition
const result = await publicClient
.select({ key: true, payload: true })
.where(eq('type', 'note'))
.where(gt('priority', 3))
.fetch()
// Varargs — equivalent to above
const result = await publicClient
.select({ key: true, payload: true })
.where(eq('type', 'note'), gt('priority', 3))
.fetch()
// Array syntax — also equivalent
const result = await publicClient
.select({ key: true, payload: true })
.where([eq('type', 'note'), gt('priority', 3)])
.fetch()
import { eq, gt, lt, gte, lte } from "@arkiv-network/sdk/query"
eq('type', 'note') // type = "note"
gt('priority', 3) // priority > 3
lt('price', 1000) // price < 1000
gte('created', timestamp) // created >= timestamp
lte('expiration', limit) // expiration <= limit

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')])

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 owner
const 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()

Retrieve a specific entity by key:

const entity = await publicClient.getEntity(entityKey)
// Parse the payload
const data = entity.toJson() // JSON payload
const text = entity.toText() // Text payload
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}`)
}
}

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 pages
if (result.hasNextPage()) {
await result.next()
console.log('Page 2:', result.entities.length)
}

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 entity
const priorityOf = (entity: (typeof entities)[number]) =>
Number(entity.attributes.find((attr) => attr.key === 'priority')?.value ?? 0)
// Highest priority first
entities.sort((a, b) => priorityOf(b) - priorityOf(a))