---
title: Cratis Chronicle bounded event reads for history views
editUrl: https://github.com/Cratis/Chronicle/edit/main/Documentation/events/paged-reads.mdx
description: Read bounded pages of Cratis Chronicle events forward or newest first, using sequence-number continuations and filters applied before the page limit.
lastUpdated: 2026-11-05T00:00:00.000Z
---

Use bounded event reads in Cratis Chronicle when a history view needs one page at a time rather than the whole sequence. In .NET, `IEventSequence.GetPage` and `IEventLog` use keyset pagination: the next request starts at a sequence number, not a row offset.

Each request returns at most `Count` events and a nullable `Next` sequence number. Events arrive in read order: oldest first for `Forward`, newest first for `Backward`. Chronicle reads one extra matching event to decide whether another page exists, without counting the entire sequence.

## Read from the beginning

Pass an existing event sequence and a cancellation token to this function. It follows `Next` while keeping the same direction, stop bound, and filters:

**C#**

```csharp
using Cratis.Chronicle.EventSequences;

public static class ForwardEventPages
{
    public static async Task ReadHistory(IEventSequence sequence, CancellationToken cancellationToken)
    {
        var request = EventSequencePageRequest.Forward(null, 100);
        while (true)
        {
            var page = await sequence.GetPage(request, cancellationToken);
            foreach (var appended in page.Events)
            {
                Console.WriteLine(appended.Context.SequenceNumber);
            }

            if (!page.HasMore) break;
            request = request with { From = page.Next };
        }
    }
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/events/getting-events/paged-forward.md)

`From` is inclusive. A null start means the beginning for forward reads and the tail for
backward reads. Do not pass `EventSequenceNumber.Unavailable` to mean the tail.

## Read the newest matching events

For an existing `IEventSequence sequence`, this reads at most 50 matching events, newest
first:

**C#**

```csharp
using Cratis.Chronicle.EventSequences;

public static class LatestEventPages
{
    public static async Task ReadLatest(IEventSequence sequence, CancellationToken cancellationToken)
    {
        var filter = new EventSequenceFilter(Tags: ["billing", "priority"]);
        var latest = await sequence.GetLatest(50, filter, cancellationToken);
        if (latest.HasMore)
        {
            var older = await sequence.GetPage(
                EventSequencePageRequest.Backward(latest.Next, 50, filter),
                cancellationToken);
            foreach (var appended in older.Events)
            {
                Console.WriteLine(appended.Context.SequenceNumber);
            }
        }
    }
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/events/getting-events/paged-latest.md)

`GetLatest` is shorthand for `GetPage(EventSequencePageRequest.Backward(null, 50, filter),
cancellationToken)`. To read older matching events, call `GetPage` with
`EventSequencePageRequest.Backward(latest.Next, 50, filter)` **only when** `latest.HasMore`
is true. Passing null again starts over at the tail.

## Bounds and continuation

`Until` is also inclusive. It is an upper bound for forward reads and a lower bound for
backward reads. For example, `EventSequencePageRequest.Backward(200, 50) with { Until = 100 }`
reads newest first within sequence numbers 100 through 200. Reversed bounds, an empty
sequence, or a filter with no matches return an empty page with no continuation.

`Next` is one sequence number past the last returned event in the read direction, and is
present only if another matching event exists inside the bounds. It need not identify an
existing event: sequence numbers and filtered results can have gaps. Use the returned
value unchanged instead of adding or subtracting `Count`. A full page can still have no
continuation.

Each page is an independent read, not a frozen snapshot. To exclude later appends during a forward traversal, capture the [tail sequence number](/chronicle/events/getting-state/) before starting and set `Until` to that value. Check that the sequence is nonempty before using its tail as a bound. The bound does not freeze event content: a revision or redaction between requests can still change what a later page returns.

## Filters and limits

`EventSequenceFilter` supports event source identifier, source type, stream type, stream
identifier, event types, and tags. Filters are combined; a tag filter matches **any** of
its supplied tags. Filtering happens in storage before the page limit. Null or empty
sets do not narrow the results. The usual unspecified/default routing sentinels also
mean no restriction.

`Count` must be between **1 and 1000**, inclusive. Invalid counts are rejected, not
clamped. Cancellation is supported by both client methods. Stored compliance-protected
content is released for returned events, just as for other event-content reads; a page
is not a metadata-only read. Authorize access to the store, namespace, and event content
before reading.

Existing `GetFromSequenceNumber` reads remain available; [Getting events](/chronicle/events/getting-events/) explains when to use them. If you supply a custom `IEventSequence` implementation, implement `GetPage` before using this API. Its default interface member preserves source compatibility but throws `NotSupportedException` when called. The in-process Testing package uses the same client and kernel query path.
