Skip to content

Props

Props is the recipe for creating an actor. Not the actor itself — a description of what to make, which the framework uses when it spawns and again on every restart.

The minimum:

import { Props } from 'actor-ts';
const props = Props.create(() => new MyActor());

A factory function that returns a fresh MyActor instance. That’s all you need for an actor with default supervision, default dispatcher, and the default unbounded FIFO mailbox.

For anything beyond that, chain with… builders:

const props = Props.create(() => new Worker())
.withSupervisorStrategy(stoppingStrategy)
.withDispatcher(new MicrotaskDispatcher())
.withMailbox(() => new BoundedMailbox({ capacity: 100, overflow: 'drop-head' }));

Each with… returns a new Props — the originals are immutable. Build up the recipe, then hand it to system.actorOf or context.spawn.

Props.create takes a factory () => new MyActor(...), not the instance directly. This matters because the actor is rebuilt on every restart:

class Counter extends Actor<...> {
private count = 0;
// ...
}
// Don't do this:
const counter = new Counter();
const props = Props.create(() => counter); // returns the SAME instance every time
// Do this:
const props = Props.create(() => new Counter()); // fresh instance per call

If you reuse the same instance on restart, the actor doesn’t reset — it inherits the broken state from the failed run. The factory form forces a clean slate, which is the whole point of “let it crash.”

The factory closes over any constructor arguments you need:

class Worker extends Actor<...> {
constructor(
private readonly db: ActorRef<DbMsg>,
private readonly cfg: Config,
) { super(); }
}
const props = Props.create(() => new Worker(db, cfg));

db and cfg are captured by the closure; every restart sees the same wiring.

class Props<TMsg> {
static create<TMsg>(factory: () => Actor<TMsg>): Props<TMsg>;
withSupervisorStrategy(strategy: SupervisorStrategy): Props<TMsg>;
withDispatcher(dispatcher: Dispatcher): Props<TMsg>;
withMailboxCapacity(capacity: number): Props<TMsg>;
withMailbox(factory: () => Mailbox<TMsg>): Props<TMsg>;
}
import { OneForOneStrategy, Directive, stoppingStrategy } from 'actor-ts';
Props.create(() => new Worker())
.withSupervisorStrategy(stoppingStrategy);
Props.create(() => new Db())
.withSupervisorStrategy(new OneForOneStrategy(
(err) => err instanceof TransientError ? Directive.Resume : Directive.Restart,
{ maxRetries: 5, withinTimeRangeMs: 60_000 },
));

The strategy attached here decides what happens when THIS actor fails — its supervisor’s strategy. Two strategies are involved in every actor’s life:

  • The one on this actor’s Props — applied to this actor’s failures by its parent.
  • The one inside this actor’s class (override supervisorStrategy) — applied to this actor’s children’s failures.

Two different things; the Props form is for the first. Most actors don’t need to override it: the parent’s default strategy (or system root’s defaultStrategy) handles the failure with a Restart + 10/minute cap. Reach for withSupervisorStrategy when that’s not right — e.g. a worker that should be stopped on failure rather than restarted because its parent will spawn a replacement.

See Supervision for the full strategy semantics.

import { MicrotaskDispatcher, ThroughputDispatcher } from 'actor-ts';
Props.create(() => new Crunchy())
.withDispatcher(new MicrotaskDispatcher());
Props.create(() => new BulkProcessor())
.withDispatcher(new ThroughputDispatcher(100));

Override the system-wide dispatcher for this specific actor. Useful when:

  • One actor is CPU-heavy and benefits from microtask scheduling (no I/O fairness needed).
  • One actor is latency-sensitive and needs the default immediate dispatcher while the rest of the system runs on a high-throughput one.

For most apps, the system-level dispatcher applies uniformly and you never need this. See Dispatchers.

Props.create(() => new SlowConsumer()).withMailboxCapacity(500);

Shorthand for “the default FIFO mailbox, but bounded at 500.” The default overflow policy (reject) applies — full mailbox throws MailboxFullError at the tell site.

This is the most common mailbox override: a simple cap with the “sender feels the pressure” semantic.

import { BoundedMailbox, PriorityMailbox } from 'actor-ts';
// Bounded + drop-head:
Props.create(() => new Telemetry())
.withMailbox(() => new BoundedMailbox({ capacity: 1_000, overflow: 'drop-head' }));
// Priority:
Props.create(() => new Worker())
.withMailbox(() => new PriorityMailbox<Msg>({
priorityFor: (m) => m.kind === 'urgent' ? 0 : 5,
}));

Full control — return any Mailbox subclass. The factory is called once per actor incarnation; on restart, a fresh empty mailbox-data-structure is created.

See Mailboxes for the overflow policies and priority semantics.

The actor name is passed to actorOf / spawn, not stored on Props:

const props = Props.create(() => new Worker());
system.actorOf(props, 'worker-1'); // → /user/worker-1
system.actorOf(props, 'worker-2'); // → /user/worker-2
this.context.actorOf(props, 'sub-worker'); // → /user/<this>/sub-worker

The reason: Props is intended to be reusable. The same recipe can spawn many actors with different names. If name were on Props, you’d need a fresh Props per actor — a footgun for actors that get a unique-name-per-instance.

Omit the name and the framework synthesizes one ('$1', '$2', …).

For families of actors with different constructor args, wrap Props.create in a small helper:

function workerProps(db: ActorRef<DbMsg>): Props<WorkerMsg> {
return Props.create(() => new Worker(db))
.withSupervisorStrategy(stoppingStrategy)
.withMailboxCapacity(500);
}
// Spawn many with the same recipe:
for (let i = 0; i < 8; i++) {
this.context.actorOf(workerProps(db), `worker-${i}`);
}

This keeps the configuration in one place and the call sites clean.

The typed-actor API has its own Props-equivalent (the typed Behaviors.setup(...) flow); see Typed actors. The untyped Props covered here is the more general — accepts a factory returning Actor<TMsg>, where the message type comes from the actor’s own class declaration.

  • Actor — the base class whose constructor the factory invokes.
  • Supervision — the strategy you pass to withSupervisorStrategy.
  • Dispatchers — the scheduler you pass to withDispatcher.
  • Mailboxes — the queue you pass to withMailbox / withMailboxCapacity.
  • ActorSystem — the system.actorOf(props, name?) that consumes Props.

The Props API reference covers the full builder set.