Snapshot Projections
Projections.Snapshot<T>() is a convenient shortcut for registering a self-aggregating projection: it builds and registers a SingleStreamProjection<T, TId> internally, with the identity type resolved automatically from the document's Id property.
How it works under the hood
Snapshot<T>() is purely a registration shortcut — it does not introduce a separate "snapshot" storage path. Internally it builds a SingleStreamProjection<T, TId> against the appropriate identity type and registers it through the same projection pipeline as any other projection. The aggregate is persisted to the document table for type T (the standard pc_doc_{type} table), not to a special snapshot column on pc_streams.
This mirrors Marten's Snapshot<T>() API exactly so the two ecosystems stay aligned.
Registration
The aggregate type must be self-aggregating — i.e. it must have its own static Create and instance Apply methods (or implement the appropriate aggregation conventions).
public class QuestParty
{
public Guid Id { get; set; }
public string Name { get; set; } = "";
public List<string> Members { get; set; } = new();
public static QuestParty Create(QuestStarted e) => new() { Name = e.Name };
public void Apply(MembersJoined e) => Members.AddRange(e.Members);
}
var store = DocumentStore.For(opts =>
{
opts.Connection("...");
// Inline — snapshot updated in the same transaction as the events
opts.Projections.Snapshot<QuestParty>(SnapshotLifecycle.Inline);
});If you need to subclass SingleStreamProjection<TDoc, TId> to override behavior, register it directly via opts.Projections.Add<MyProjection>(ProjectionLifecycle.Inline) instead.
Lifecycles
// Updated in the same transaction as the appended events
opts.Projections.Snapshot<QuestParty>(SnapshotLifecycle.Inline);
// Updated asynchronously by the async projection daemon
opts.Projections.Snapshot<QuestParty>(SnapshotLifecycle.Async);SnapshotLifecycle.Live is intentionally not supported — for live aggregation, use AggregateStreamAsync<T>() directly without registering a projection.
Reading the Aggregate
Because Snapshot<T>() registers a regular SingleStreamProjection, you read the result the same way you would for any other projected document:
await using var session = store.QuerySession();
// Loads from the projected document table (pc_doc_questparty)
var party = await session.LoadAsync<QuestParty>(streamId);
// Or fetch the latest version from the event store
var latest = await session.Events.FetchLatest<QuestParty>(streamId);Snapshot in a Composite Projection
Composite projections support the same shortcut via composite.Snapshot<T>():
opts.Projections.CompositeProjectionFor("UserLifecycle", composite =>
{
composite.Snapshot<User>(); // stage 1 (default)
composite.Snapshot<UserStats>(2); // stage 2
});Inside a composite projection, snapshots always run as Async — composite projections are themselves async-only.
Choosing Between Snapshot and Add
- Use
Snapshot<T>()whenTis a self-aggregating aggregate type with conventionalCreate/Applymethods. This is the simplest registration. - Use
Add<TProjection>(...)when you have a dedicatedSingleStreamProjection<TDoc, TId>subclass that overrides projection behavior, or when registering any other projection type (multi-stream, event projections, flat tables, etc).

JasperFx provides formal support for Polecat and other Critter Stack libraries. Please check our