---
title: Getting Started
description: Detailed setup with explicit configuration and the most common runtime options.
order: 13
available_in: [dotnet, nodejs]
---

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

Examples are shown for both .NET and Node.js.

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

## Recommended baseline setup

:::code-group
```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
});
```

```typescript
import { createDurableStackPostgres } from "durablestack";

const { runtime, closeStore } = await createDurableStackPostgres(
  { connectionString: process.env.DATABASE_URL! },
  {
    workerName: `orders-api-${process.pid}`,
    pollIntervalSeconds: 5,
    claimBatchSize: 50,
    maxConcurrentRuns: 10,
    leaseDurationSeconds: 30,
    retryDelaySeconds: 5,
    retryMaxDelaySeconds: 3600,
    retryJitterEnabled: true,
    retryJitterRatio: 0.2,
    recurring: {
      catchUpPolicy: "SkipMissed",
      registrationSync: {
        existingJobBehavior: "KeepDatabase",
        orphanedJobBehavior: "Disable"
      }
    },
    retention: {
      enabled: true,
      runRetentionSeconds: 86400,
      sweepIntervalSeconds: 300,
      deleteBatchSize: 1000
    },
    eventing: {
      tenantId: "",
      clientSecret: ""
    }
  }
);

runtime.registerJob("sample-job", async () => {
  console.log("running sample-job");
});

await runtime.start();

process.on("SIGTERM", async () => {
  await runtime.stop();
  await closeStore();
});
```
:::

## 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.

### 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.

## 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

The full option list is runtime-specific. Use the .NET reference below for ASP.NET-style configuration and runtime APIs, and use the Node.js package reference (`durablestack-nodejs/README.md`) for Node option and method names.

:::code-group
```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.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.
});
```

```typescript
const { runtime } = await createDurableStackPostgres(
  { connectionString: process.env.DATABASE_URL! },
  {
    workerName: `orders-api-${process.pid}`,
    pollIntervalSeconds: 5,
    claimBatchSize: 50,
    leaseDurationSeconds: 30,
    retryDelaySeconds: 5,
    retryMaxDelaySeconds: 3600,
    retryJitterEnabled: false,
    retryJitterRatio: 0.2,
    eventing: {
      tenantId: "",
      clientSecret: "",
      ingestionApiBaseUrl: "https://api.durablestack.com",
      ingestionPath: "/v1/events/batch",
      ingestionMaxBatchSize: 100,
      ingestionMaxRequestBodyBytes: 1_000_000,
      ingestionMaxRetryAttempts: 5,
      ingestionFlushIntervalSeconds: 5,
      includeErrorDetail: false,
      maxErrorDetailLength: 4096
    },
    recurring: {
      catchUpPolicy: "SkipMissed",
      registrationSync: {
        existingJobBehavior: "KeepDatabase",
        orphanedJobBehavior: "Disable"
      }
    },
    retention: {
      enabled: true,
      runRetentionSeconds: 86400,
      sweepIntervalSeconds: 300,
      deleteBatchSize: 1000
    },
    autodiscovery: {
      enabled: true
    }
  }
);
```
:::

## Store helper methods

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

:::code-group
```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).
});
```

```typescript
import {
  createDurableStackInMemory,
  createDurableStackMySql,
  createDurableStackPostgres,
  createDurableStackSqlite,
  createDurableStackSqlServer
} from "durablestack";

const inMemory = await createDurableStackInMemory();
const pg = await createDurableStackPostgres({ connectionString: process.env.DATABASE_URL! });
const sqlServer = await createDurableStackSqlServer({ connectionString: process.env.SQLSERVER_URL! });
const sqlite = await createDurableStackSqlite({ connectionString: "Data Source=durable_stack.db" });
const mySql = await createDurableStackMySql({ connectionString: process.env.MYSQL_URL! });
```
:::

## 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.
