---
title: Getting Started
description: Detailed setup with explicit C# configuration and the most common runtime options.
order: 13
---

# Getting Started

This guide expands on Quick Start and focuses on the most common production options.

All runtime configuration is set in C#.

Most teams can rely on defaults for many options. This example sets them explicitly so the behavior is easy to understand.

## Recommended baseline setup

```csharp
using DurableStack.Core.Options;
using DurableStack.Hosting.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);
var connectionString = "Host=localhost;Port=5432;Database=durable_stack;Username=postgres;Password=postgres";
var workerName = $"orders-api-{Environment.MachineName}-{Environment.ProcessId}";

builder.Services.AddDurableStackPostgres(connectionString, options =>
{
    options.JobActivation = DurableStackJobActivationMode.ScopedPerExecution; // Default: ScopedPerExecution
    options.WorkerName = workerName; // Default: "{HOSTNAME-or-machine}-{processId}"

    options.PollIntervalSeconds = 5; // Default: 5
    options.BatchSize = 50; // Default: 50
    options.MaxConcurrentRuns = 10; // Default: 5
    options.LeaseDurationSeconds = 30; // Default: 30

    options.RetryDelay = TimeSpan.FromSeconds(5); // Default: 00:00:05
    options.RetryMaxDelay = TimeSpan.FromMinutes(60); // Default: 01:00:00
    options.RetryJitterEnabled = true; // Default: false
    options.RetryJitterRatio = 0.20; // Default: 0.2

    options.Recurring.CatchUpPolicy = RecurringCatchUpPolicy.SkipMissed; // Default: SkipMissed
    options.Recurring.RegistrationSync.ExistingJobBehavior = ExistingRecurringJobBehavior.KeepDatabase; // Default: KeepDatabase
    options.Recurring.RegistrationSync.OrphanedJobBehavior = OrphanedRecurringJobBehavior.Disable; // Default: Disable

    options.Retention.Enabled = true; // Default: true
    options.Retention.RunRetentionSeconds = 86400; // Default: null (effective 86400 for durable DB providers)
    options.Retention.SweepIntervalSeconds = 300; // Default: 300
    options.Retention.DeleteBatchSize = 1000; // Default: 1000

    options.Eventing.TenantId = null; // Default: null
    options.Eventing.ClientSecret = null; // Default: null
});
```

## Option groups that matter most

### Worker and throughput

- `WorkerName` (default: `"{HOSTNAME-or-machine}-{processId}"`): unique identity used for lease ownership and diagnostics.
- `PollIntervalSeconds` (default: `5`): how often workers check for available runs.
- `BatchSize` (default: `50`): maximum runs claimed per poll cycle.
- `MaxConcurrentRuns` (default: `5`): concurrency limit per worker instance.
- `LeaseDurationSeconds` (default: `30`): ownership timeout before another worker can recover a stalled run.

### Retry behavior

- `RetryDelay` (default: `00:00:05`): baseline delay between attempts.
- `RetryMaxDelay` (default: `01:00:00`): cap for backoff growth.
- `RetryJitterEnabled` (default: `false`) and `RetryJitterRatio` (default: `0.2`): spread retry pressure across time.
- `PollJitterEnabled` (default: `true`) and `PollJitterRatio` (default: `0.2`): spread poll pressure across workers.

### Recurring behavior

- `Recurring.CatchUpPolicy` (default: `SkipMissed`): avoids large catch-up bursts after downtime.
- `ExistingJobBehavior` (default: `KeepDatabase`): keeps existing schedule definitions stable by default.
- `OrphanedJobBehavior` (default: `Disable`): safely disables schedules removed from code.

### Retention

- `Retention.Enabled` (default: `true`): enables cleanup for terminal runs.
- `RunRetentionSeconds` (default: `null`, effective `86400` for durable DB providers and `3600` for in-memory): how long completed/failed runs remain queryable.
- `SweepIntervalSeconds` (default: `300`) and `DeleteBatchSize` (default: `1000`): cleanup cadence and batch sizing.

### Optional hosted observability

- `Eventing.TenantId` (default: `null`) and `Eventing.ClientSecret` (default: `null`): enable ingestion when both are set.
- When ingestion is enabled, runtime information is included in posted ingestion data.

## Defaults-first guidance

In most projects, you only need to set:

- provider selection and connection string
- optional `WorkerName` convention
- any non-default values that match your workload goals

Everything else can stay at defaults until you have data that justifies tuning.

## What to tune first in production

- Increase `MaxConcurrentRuns` and `BatchSize` carefully as workload grows.
- Keep `LeaseDurationSeconds` longer than normal job execution for your slowest routine jobs.
- Use retry jitter to reduce synchronized retries during external outages.
- Set retention windows based on audit/debug needs and database cost profile.

## Full options reference

```csharp
using DurableStack.Core.Options;
using DurableStack.Hosting.DependencyInjection;

builder.Services.AddDurableStack(options =>
{
    options.StorageProvider = DurableStackStorageProvider.InMemory; // Backing store selection when using property-based config; default: InMemory.

    options.Postgres.ConnectionString = "Host=localhost;Port=5432;Database=durable_stack;Username=postgres;Password=postgres"; // PostgreSQL connection string used when StorageProvider is Postgres; default: "".
    options.SqlServer.ConnectionString = "Server=localhost;Database=durable_stack;Trusted_Connection=True;TrustServerCertificate=True"; // SQL Server connection string used when StorageProvider is SqlServer; default: "".
    options.Sqlite.ConnectionString = "Data Source=durable_stack.db"; // SQLite connection string used when StorageProvider is Sqlite; default: "".
    options.MySql.ConnectionString = "Server=localhost;Port=3306;Database=durable_stack;Uid=root;Pwd=password"; // MySQL connection string used when StorageProvider is MySql; default: "".

    options.JobActivation = DurableStackJobActivationMode.ScopedPerExecution; // Job activation model (ScopedPerExecution or RootProvider); default: ScopedPerExecution.
    options.WorkerName = DurableStackOptions.CreateDefaultWorkerName(); // Unique worker identity for leasing and diagnostics; default: "{HOSTNAME-or-machine}-{processId}".
    options.DatabaseTablePrefix = null; // Optional prefix for durable database objects; default: null.

    options.PollInterval = TimeSpan.FromSeconds(5); // Polling cadence for fetching available work; default: 00:00:05.
    options.PollIntervalSeconds = 5; // Same as PollInterval but in seconds (<= 0 resets to default 5); default: 5.
    options.BatchSize = 50; // Max runs leased per poll cycle; default: 50.
    options.LeaseDuration = TimeSpan.FromSeconds(30); // Lease TTL before another worker can recover stalled runs; default: 00:00:30.
    options.LeaseDurationSeconds = 30; // Same as LeaseDuration but in seconds (<= 0 resets to default 30); default: 30.

    options.PollJitterEnabled = true; // Enables random jitter on poll delays; default: true.
    options.PollJitterRatio = 0.2; // Jitter ratio applied when PollJitterEnabled is true; default: 0.2.
    options.RetryDelay = TimeSpan.FromSeconds(5); // Base retry delay for failed attempts; default: 00:00:05.
    options.RetryMaxDelay = TimeSpan.FromHours(1); // Retry backoff upper bound; default: 01:00:00.
    options.RetryJitterEnabled = false; // Enables random jitter on retry delays; default: false.
    options.RetryJitterRatio = 0.2; // Jitter ratio applied when RetryJitterEnabled is true; default: 0.2.

    options.Eventing.TenantId = null; // Tenant ID for hosted ingestion auth; default: null.
    options.Eventing.ClientSecret = null; // Client secret for hosted ingestion auth; default: null.
    options.Eventing.IngestionApiBaseUrl = "https://api.durablestack.com"; // Base URL for event ingestion API; default: https://api.durablestack.com.
    options.Eventing.IngestionPath = "/v1/events/batch"; // Relative ingestion endpoint path; default: /v1/events/batch.
    options.Eventing.IngestionMaxBatchSize = 100; // Max events sent per ingestion request; default: 100.
    options.Eventing.IngestionMaxRequestBodyBytes = 1_000_000; // Max request body size per ingestion post; default: 1,000,000.
    options.Eventing.IngestionMaxRetryAttempts = 5; // Max retry attempts for failed ingestion posts; default: 5.
    options.Eventing.IngestionFlushInterval = TimeSpan.FromSeconds(5); // Flush interval for telemetry publishing; default: 00:00:05.
    options.Eventing.IngestionFlushIntervalSeconds = 5; // Same as IngestionFlushInterval in seconds (<= 0 resets to default 5); default: 5.
    options.Eventing.IncludeErrorDetail = false; // Includes captured error detail text in events when true; default: false.
    options.Eventing.MaxErrorDetailLength = 4096; // Max captured error-detail characters per event (<= 0 uses 4096); default: 4096.

    options.Recurring.CatchUpPolicy = RecurringCatchUpPolicy.SkipMissed; // Recurring schedule behavior after downtime (SkipMissed or CatchUp); default: SkipMissed.
    options.Recurring.RegistrationSync.ExistingJobBehavior = ExistingRecurringJobBehavior.KeepDatabase; // Existing recurring definitions: keep DB values or update from code; default: KeepDatabase.
    options.Recurring.RegistrationSync.OrphanedJobBehavior = OrphanedRecurringJobBehavior.Disable; // Jobs removed from code: disable or ignore in DB; default: Disable.

    options.Retention.Enabled = true; // Enables automatic cleanup of terminal runs; default: true.
    options.Retention.RunRetentionSeconds = null; // Terminal run retention window in seconds; default: null (effective default = 3600 in-memory, 86400 durable stores).
    options.Retention.SweepIntervalSeconds = 300; // Retention cleanup sweep interval in seconds (<= 0 uses 300); default: 300.
    options.Retention.DeleteBatchSize = 1000; // Max rows removed per retention cleanup batch (<= 0 uses 1000); default: 1000.

    options.JobRegistration.AutoDiscoverJobsFromAssembly = true; // Auto-registers public IDurableJob/IDurableJob<TArgs> from the app assembly; default: true.
});
```

## Store helper methods

Use one of these helpers inside the same `AddDurableStack` callback when you prefer method-based store selection.

```csharp
builder.Services.AddDurableStack(options =>
{
    options.UseInMemory(); // Selects in-memory store; default if no store is selected.
    // options.UsePostgres("Host=..."); // Selects PostgreSQL store and sets connection string; use only one Use* store method at a time (last call wins).
    // options.UseSqlServer("Server=..."); // Selects SQL Server store and sets connection string; use only one Use* store method at a time (last call wins).
    // options.UseSqlite("Data Source=..."); // Selects SQLite store and sets connection string; use only one Use* store method at a time (last call wins).
    // options.UseMySql("Server=..."); // Selects MySQL store and sets connection string; use only one Use* store method at a time (last call wins).
});
```

## Notes

- Setting both `PollInterval` and `PollIntervalSeconds` is redundant; use one style.
- Setting both `LeaseDuration` and `LeaseDurationSeconds` is redundant; use one style.
- Setting both `Eventing.IngestionFlushInterval` and `Eventing.IngestionFlushIntervalSeconds` is redundant; use one style.
- Event ingestion is automatically enabled when both `Eventing.TenantId` and `Eventing.ClientSecret` are set.
- Runtime event ingestion payloads include runtime information when ingestion is enabled.

## Supported .NET versions

- `.NET 9`
- `.NET 10`
