dev.club — where best developers and top companies connect.

dev.club — where best developers and top companies connect.Invite only

Request invite

Marten

.NET Transactional Document DB and Event Store on PostgreSQL

Discord Twitter Follow Tests Nuget Package Nuget

marten logo

The Marten library provides .NET developers with the ability to use the proven PostgreSQL database engine and its fantastic JSON support as a fully fledged document database. The Marten team believes that a document database has far reaching benefits for developer productivity over relational databases with or without an ORM tool.

Marten also provides .NET developers with an ACID-compliant event store with user-defined projections against event streams.

Access docs here. For any of your queries including the whole of Critter stack, join our Discord channel and it is the best way to reach us quickly. You can also raise questions/queries via GitHub Discussions as well.

Support Plans

JasperFx logo

While Marten is open source, JasperFx Software offers paid support and consulting contracts for Marten.

Help us keep working on this project 💚

Become a Sponsor on GitHub by sponsoring monthly or one time.

Past Sponsors

.NET on AWS

Working with the Code

Prerequisites

The build is driven by Nuke (build/build.cs). build.sh, build.ps1 and build.cmd are thin wrappers, so any target below can be run as ./build.sh <target> on Linux/macOS, .\build.ps1 <target> in PowerShell, or build.cmd <target> on Windows.

./build.sh compile     # restore and build src/Marten.slnx (the default target)

PostgreSQL

Marten supports PostgreSQL 13 or later; the async daemon relies on pg_current_snapshot(), which first shipped in 13.

The quickest way to get a test database is the Docker Compose file at the repository root:

docker compose up -d          # or: ./build.sh init-db  (starts it and waits until it is ready)

docker-compose.yml builds its image from docker/postgres/Dockerfile, which layers PostGIS 3 and pgvector onto the official postgres:17 image, so the Marten.PostGIS and Marten.PgVector test projects have the extensions they need. Both packages are multi-arch, so this builds natively on Apple-silicon machines. It listens on port 5432 with the postgres/postgres login and a marten_testing database. ./build.sh rebuild-db tears the container down and starts it again.

A few suites need something other than the standard database:

Suite Database
Marten.TimescaleDB docker compose -f docker-compose.timescaledb.yml up -d (TimescaleDB on port 5433 — point marten_testing_database at it)
MultiHostTests docker compose -f src/MultiHostTests/docker-compose.yaml up -d (primary/standby pair on ports 5440/5441)

PLV8 is no longer part of developing Marten — the patching API it once backed has been replaced by native partial updates, and neither the compose image nor CI installs the extension. Older applications that still depend on it can use the separate Marten.PLv8 package.

Test configuration

By default the tests connect to:

Host=localhost;Port=5432;Database=marten_testing;Username=postgres;password=postgres

To use a different server or PostgreSQL version, set environment variables rather than editing the compose file:

Variable Purpose
marten_testing_database Connection string for the test database (the login needs the postgres role)
DEFAULT_SERIALIZER Newtonsoft (default) or SystemTextJson
DISABLE_TEST_PARALLELIZATION true to run test collections serially, as CI does

Running the tests

The test suites use xUnit.net v3 and Shouldly, and are split across many test projects under src/. There is one build target per test project:

Target Project
test-base-lib Marten.Testing (shared harness)
test-core CoreTests — schema management, retries, core services
test-document-db DocumentDbTests — document storage features
test-event-sourcing EventSourcingTests — events and projections
test-daemon DaemonTests — async projection daemon
test-linq LinqTests — LINQ-to-SQL translation
test-patching PatchingTests — partial document updates
test-multi-tenancy MultiTenancyTests
test-tenant-partitioned-events TenantPartitionedEventsTests
test-value-types ValueTypeTests — strong-typed identifiers
test-modular-config ModularConfigTests
test-container-scoped-projections ContainerScopedProjectionTests
test-compiled-queries CompiledQueryTests
test-source-generator Marten.SourceGenerator.Tests (no database)
test-aot-runtime Native AOT smoke test
test-stress StressTests (not run in CI)
test-multi-host MultiHostTests (needs its own compose file)
test-noda-time, test-aspnetcore, test-postgis, test-pgvector, test-timescaledb, test-entity-framework-core, test-memory-pack The extension packages

Aggregate targets:

./build.sh test              # every core suite against the standard database
./build.sh test-extensions   # NodaTime, AspNetCore, PostGIS, PgVector, TimescaleDB, EF Core, MemoryPack

Useful options for any test target:

./build.sh test-event-sourcing --framework net10.0       # one TFM instead of every built TFM
./build.sh test-core --configuration Release
./build.sh test-core --disable-test-retry                # see a suite's real stability

Test targets don't shell out to dotnet test. They run each project through the Bobcat test supervisor (build/SupervisedTests.cs): a failing test is retried in a fresh process, and a test that only passes on a retry is reported as flaky — in the console, in the GitHub job summary and in a JSON ledger under artifacts/test-ledger/ — rather than being counted as a clean pass. For a genuinely racy test, [Trait("Retry", "3")] raises its attempt limit and [Trait("Isolated", "true")] runs it in its own process.

While you're iterating, you can also run a project or a single test directly from your IDE's test runner or with the dotnet CLI:

dotnet test src/DocumentDbTests/DocumentDbTests.csproj --framework net10.0

Every test project must import src/Tests.props, which sets up xUnit v3 and the Microsoft Testing Platform host the supervisor relies on.

Integration test harness

src/Marten.Testing/Harness has base classes that make integration tests against PostgreSQL efficient and parallel-friendly:

Continuous integration

CI runs on GitHub Actions only (.github/workflows/tests.yml). Each test project is a separate job on its own runner and database, and the core suites run against both supported combinations:

.NET PostgreSQL Serializer
9 postgres:15-alpine Newtonsoft
10 postgres:latest System.Text.Json

The extension suites use purpose-built images — postgis/postgis:17-3.5, pgvector/pgvector:pg17 and timescale/timescaledb-ha:pg17 — and there are extra jobs for the Native AOT smoke test and for the two-node replication pair used by MultiHostTests. A final flakiness job rolls up every job's retry ledger. Tests run in Release with parallelization disabled.

Adding a test project takes two changes: a target in build/build.cs and a matrix entry in tests.yml. A project with no CI entry isn't being run.

See CONTRIBUTING.md for the contribution workflow and PR guidelines.

Other build targets

Target Description
compile Restore and build the solution (default)
init-db / rebuild-db Start (or restart) the Docker Compose PostgreSQL database
docs Run the documentation website locally (see below)
docs-build Build the static documentation site
clear-inline-samples Strip generated snippet bodies out of the docs Markdown
benchmarks Run the BenchmarkDotNet suite in MartenBenchmarks
pack Build the NuGet packages

Documentation

The documentation at martendb.io is written in Markdown under /docs, built with VitePress, and hosted on Netlify. Code samples are pulled into the Markdown from compiling, tested source code by MarkdownSnippets, and search is provided by Algolia DocSearch.

Running the docs locally

dotnet tool restore   # installs the pinned mdsnippets tool from .config/dotnet-tools.json
npm install
npm run docs

npm run docs first runs mdsnippets to refresh every code snippet in the Markdown, then starts the VitePress dev server at http://localhost:5050 with hot reload. ./build.sh docs does the same thing and installs the npm packages and the mdsnippets tool for you.

Other scripts:

Command Description
npm run mdsnippets Refresh the code snippets in the Markdown only
npm run docs-build Refresh snippets, then build the static site
npm run vitepress-dev Start VitePress without refreshing snippets

To add a new page, create the Markdown file under docs/ and add it to the sidebar in docs/.vitepress/config.mts.

Code samples with MarkdownSnippets

Don't paste C# into the Markdown. Instead, mark the code in the source — usually in a test, so the sample is compiled and exercised by the build — with a named region whose name starts with sample_:

#region sample_my_snippet
var user = new User { FirstName = "Han" };
session.Store(user);
await session.SaveChangesAsync();
#endregion

Then reference it from a docs page with an empty snippet block:

<!-- snippet: sample_my_snippet -->
<!-- endSnippet -->

When mdsnippets runs (as part of npm run docs), it fills the block in place with the code and a link back to the source file on GitHub (see mdsnippets.json). Search the repository for sample_ or snippet: to find plenty of examples.

A few rules:

Linting

Pull requests that touch docs/ run markdownlint and cspell (.github/workflows/docs-prs.yml). Run them locally first:

npx --yes markdownlint-cli@latest --disable MD009 -- "docs/**/*.md"
npx --yes cspell --config ./docs/cSpell.json "docs/**/*.md"

Add legitimate technical terms to the words list in docs/cSpell.json.

Publishing

The docs are deployed to Netlify by the manually triggered Docs build and deploy workflow (./build.sh publish-docs, or publish-docs-preview for a preview deploy).

The Marten 3.x documentation lived in the /documentation folder and used a different tool; it is maintained only on the 3.14 branch.

License

Copyright © Jeremy D. Miller, Babu Annamalai, Oskar Dudycz, Joona-Pekka Kokko and contributors.

Marten is provided as-is under the MIT license. For more information see LICENSE.

Code of Conduct

This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.

Join libs.tech

...and unlock some superpowers

GitHub

We won't share your data with anyone else.