> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sqd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Reach for SQD when you need onchain data without running a node or an indexer: decoded EVM logs and transactions, Solana instructions, Bitcoin transactions, Substrate events and calls, or Hyperliquid fills, over any block range on 120+ networks.
> To query directly, POST to https://portal.sqd.dev/datasets/{dataset}/stream. The full API is described at https://docs.sqd.dev/openapi.json, and responses to the stream endpoints are JSON Lines.
> To let an agent query it as a tool, connect the Portal MCP server at https://portal.sqd.dev/mcp.
> Every page on this site is available as Markdown by appending .md to its URL.

# createTarget

> Use createTarget in an EVM Pipes SDK pipeline.

Build a custom data target. A target drains batches from the pipe and is responsible for persisting them.

```ts theme={"system"}
createTarget<In>(config: Target<In>): Target<In>
```

**Config fields:**

* `write`: (required) Async function `({ read, logger }) => Promise<void>`. Iterate the stream by calling `read()` and consuming `{ data, ctx }` batches. The function returns when the stream ends.
* `resolveFork`: (optional) `(canonicalBlocks: BlockCursor[]) => Promise<BlockCursor | null>`. Called when the source detects a chain reorg. `canonicalBlocks` is the portal's view of the canonical chain (a.k.a. `previousBlocks` in Portal API `/stream` 409 responses). Find the common ancestor, roll back everything persisted above it, and return the cursor to resume from — or `null` if no common ancestor can be determined (the stream will throw). See [Fork handling](../../../guides/architecture-deep-dives/fork-handling). You don't need this callback when the source is configured to [read only finalized blocks](../source#finalized-blocks).

## The `write` context

```ts theme={"system"}
type WriteCtx<In> = {
  read: (cursor?: BlockCursor) => AsyncIterableIterator<PortalBatch<In>>
  logger: Logger
}
```

| Field | Description |
| - | - |
| `read` | Opens an async iterator over pipeline batches. Pass `cursor` to resume from a specific block the target has persisted. |
| `logger` | Pino-compatible logger scoped to this target. |

## Per-batch context (`ctx`)

Each `{ data, ctx }` yielded by `read()` carries the same `BatchContext` that transformers receive. Fields:

| Field | Type | Description |
| - | - | - |
| `id` | `string` | Pipeline ID — the `id` passed to `evmPortalStream()`. |
| `logger` | `Logger` | Batch-scoped logger. |
| `metrics` | `Metrics` | Prometheus metrics registry. See [Metrics](../../../guides/advanced-topics/metrics). |
| `profiler` | `Profiler` | Open a span with `ctx.profiler.start('label')`. See [Profiling](../../../guides/advanced-topics/profiling). |
| `stream.dataset` | `ApiDataset` | Dataset metadata. |
| `stream.head.finalized` | `BlockCursor \| undefined` | Current finalized head. |
| `stream.head.latest` | `BlockCursor \| undefined` | Current unfinalized head. |
| `stream.state.initial` | `number` | First block number the stream was configured to read. |
| `stream.state.last` | `number` | Last block number the stream intends to read. |
| `stream.state.current` | `BlockCursor` | Latest block in this batch. |
| `stream.state.rollbackChain` | `BlockCursor[]` | Tail of unfinalized cursors subject to rollback. |
| `stream.progress` | `ProgressEvent['progress']` | Progress metrics when `progress` is enabled. |
| `stream.query` | `{ url, hash, raw }` | Portal query details for the batch. |
| `batch.blocksCount` | `number` | Number of blocks in this batch. |
| `batch.bytesSize` | `number` | Compressed payload size received from the portal. |
| `batch.requests` | `Record<number, number>` | Map of HTTP status code → count of responses that produced this batch. |
| `batch.lastBlockReceivedAt` | `Date` | Wall-clock time the last block was received. |

## Example

```ts theme={"system"}
const target = createTarget({
  write: async ({ read, logger }) => {
    for await (const { data, ctx } of read()) {
      const span = ctx.profiler.start('save')
      await database.save(data)
      span.end()
      logger.info(
        { block: ctx.stream.state.current.number, rows: ctx.batch.blocksCount },
        'saved batch',
      )
    }
  },
  resolveFork: async (canonicalBlocks) => {
    // Return a cursor from your persisted state; null to fail hard.
    return canonicalBlocks[canonicalBlocks.length - 1] ?? null
  },
})
```

## Resuming from a persisted cursor

Stateful targets typically persist a cursor and resume from it on restart:

```ts theme={"system"}
createTarget({
  write: async ({ read, logger }) => {
    const lastSaved = await database.getCursor() // BlockCursor | undefined
    for await (const { data, ctx } of read(lastSaved)) {
      await database.save(data)
      await database.saveCursor(ctx.stream.state.current)
    }
  },
})
```


## Related topics

- [createTarget](/en/sdk/pipes-sdk/solana/reference/basic-components/target/create-target.md)
- [Cursor management](/en/sdk/pipes-sdk/evm/guides/architecture-deep-dives/cursor-management.md)
- [Developing pipes](/en/sdk/pipes-sdk/solana/guides/basic-development/flow.md)
- [Migrate to 1.0](/en/sdk/pipes-sdk/solana/migration.md)
- [Choosing your tool](/en/sdk/options-comparison.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.