---
title: Provide data to a command handler
editUrl: https://github.com/Cratis/Arc/edit/main/Documentation/scenarios/provide-data-to-a-command.mdx
description: Fetch the data a command's decision needs in a Provide method so Handle stays a pure, easily tested function of its arguments.
---

import { Tabs, TabItem } from '@astrojs/starlight/components';


**Goal:** your command's `Handle()` needs data it doesn't carry — usually data from another service, such as a credit bureau, pricing service, inventory lookup, risk model, or tenant configuration — and you don't want that IO tangled into the decision logic, where it's hard to test.

## Lift the IO out of `Handle`

Add `Provide()` next to `Handle()` on C#, or `provide()` next to `handle()` on JVM and TypeScript. `Provide` fetches or computes the data the decision needs, and its return value is passed into `Handle()` as an argument. `Handle` can then be a pure function of its arguments. To separate the write side as well on the C# backend, return a [command operation](/arc/backend/csharp/commands/operations/) describing the required service work, or return events with the optional Chronicle integration. `Provide` does not make persistence automatic.

:::tip[Acquire, decide, then perform]
Use `Provide()` to acquire decision inputs, `Handle()` to decide, and — on the C# backend — returned command operations to perform inline side effects. Do not move writes into `Provide()` merely to make `Handle()` look pure: such direct writes are outside operation compensation. [Operation testing](/arc/backend/csharp/testing/command-operations/) covers the decision and real execution separately.
:::

`Provide` runs before `Handle`, after validation and authorization pass. Its parameters are resolved from dependency injection just like `Handle`'s, and `this` is the command, so it can read the command's own data.

The following are **alternative domain fragments**, not a complete loan system. `LoanId`, `ApplicantId`, `CreditScore`, `RiskBand`, `LoanAssessment`, and the bureau/risk/rates interfaces are application-owned types. Supply their declarations and DI implementations; import `Cratis.Arc.Commands.ModelBound`, `Cratis.Arc.Validation`, and `Cratis.Monads` for the Arc attributes and result examples. `LoanAssessment` is an ordinary response DTO, not a persisted approval or Chronicle event.

<Tabs syncKey="arc-backend-language">
<TabItem label="C#">

```csharp
[Command]
public record AssessLoan(LoanId LoanId, ApplicantId Applicant)
{
    public CreditScore Provide(ICreditBureau bureau) => bureau.GetScore(Applicant);

    public LoanAssessment Handle(CreditScore creditScore) => new(LoanId, creditScore);
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Arc/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/assess-loan.md)

</TabItem>
<TabItem label="Java">

```java
@Command
public record AssessLoan(LoanId loanId, ApplicantId applicant) {
    public CreditScore provide(CreditBureau bureau) {
        return bureau.score(applicant);
    }

    public LoanAssessment handle(CreditScore creditScore) {
        return new LoanAssessment(loanId, creditScore);
    }
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets-java/scenarios/provide-data-to-a-command/assess-loan.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
@Command
data class AssessLoan(val loanId: LoanId, val applicant: ApplicantId) {
    suspend fun provide(bureau: CreditBureau): CreditScore = bureau.score(applicant)

    fun handle(creditScore: CreditScore): LoanAssessment = LoanAssessment(loanId, creditScore)
}
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/assess-loan.md)

</TabItem>
<TabItem label="TypeScript (preview)">

```typescript
import { field } from '@cratis/fundamentals';
import { command, currentServices } from '@cratis/arc.core';

@command()
export class AssessLoan {
    @field(LoanId) loanId!: LoanId;
    @field(ApplicantId) applicant!: ApplicantId;

    // provide() takes no parameters, so it resolves its services from the execution scope.
    async provide(): Promise<CreditScore> {
        const bureau = await currentServices().resolve(CreditBureau);
        return bureau.scoreFor(this.applicant);
    }

    handle(creditScore: CreditScore): LoanAssessment {
        return new LoanAssessment(this.loanId, creditScore);
    }
}
```

[View TypeScript (preview) snippet source on GitHub](https://github.com/Cratis/Arc.TypeScript/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/assess-loan.md)

</TabItem>
</Tabs>

## Why this matters: testability

Because the IO lives in `Provide`, testing the decision needs no mocking — construct the command and call the handler directly with the values it would have received:

<Tabs syncKey="arc-backend-language">
<TabItem label="C#">

```csharp
[Fact] void should_return_the_assessment() =>
    new AssessLoan(LoanId.New(), ApplicantId.New())
        .Handle(new CreditScore(800))
        .ShouldBeOfExactType<LoanAssessment>();
```

[View C# snippet source on GitHub](https://github.com/Cratis/Arc/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/test-the-decision.md)

</TabItem>
<TabItem label="Java">

```java
@Test
void handleReturnsTheAssessment() {
    var loanId = new LoanId(UUID.randomUUID());
    var command = new AssessLoan(loanId, new ApplicantId(UUID.randomUUID()));

    var assessment = command.handle(new CreditScore(800));

    assertEquals(new LoanAssessment(loanId, new CreditScore(800)), assessment);
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets-java/scenarios/provide-data-to-a-command/test-the-decision.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
@Test
fun `handle returns the assessment`() {
    val loanId = LoanId(UUID.randomUUID())
    val command = AssessLoan(loanId, ApplicantId(UUID.randomUUID()))

    val assessment = command.handle(CreditScore(800))

    assertEquals(LoanAssessment(loanId, CreditScore(800)), assessment)
}
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/test-the-decision.md)

</TabItem>
<TabItem label="TypeScript (preview)">

```typescript
import { describe, expect, it } from 'vitest';
import { AssessLoan } from './AssessLoan.js';

describe('when assessing a loan', () => {
    it('should return the assessment', () => {
        const command = new AssessLoan();
        command.loanId = LoanId.create();
        command.applicant = ApplicantId.create();

        expect(command.handle(new CreditScore(800))).toBeInstanceOf(LoanAssessment);
    });
});
```

[View TypeScript (preview) snippet source on GitHub](https://github.com/Cratis/Arc.TypeScript/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/test-the-decision.md)

</TabItem>
</Tabs>

`Provide` has a single job — acquire data — so it is easy to test in isolation too, with a stubbed credit bureau.

## Return more than one value

On C#, return a tuple; on Kotlin, return a `Pair` to feed several typed handler parameters. On TypeScript, return `tuple(a, b)` from `provide()` and mark each `handle()` parameter with `@inject(provided(TypeA), provided(TypeB))`; Arc matches the values by runtime type. Returning a single object for `handle()` to destructure also works:

<Tabs syncKey="arc-backend-language">
<TabItem label="C#">

```csharp
public (CreditScore, RiskBand) Provide(ICreditBureau bureau, IRiskModel risk) =>
    (bureau.GetScore(Applicant), risk.Band(Applicant));

public LoanAssessment Handle(CreditScore score, RiskBand band) => new(LoanId, score, band);
```

[View C# snippet source on GitHub](https://github.com/Cratis/Arc/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/several-values.md)

</TabItem>
<TabItem label="Java">

```java
public CommandProvidedValues provide(CreditBureau bureau, RiskModel risk) {
    return CommandProvidedValues.of(bureau.score(applicant), risk.band(applicant));
}

public LoanAssessment handle(CreditScore score, RiskBand band) {
    return new LoanAssessment(loanId, score, band);
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets-java/scenarios/provide-data-to-a-command/several-values.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
suspend fun provide(bureau: CreditBureau, risk: RiskModel): Pair<CreditScore, RiskBand> =
    bureau.score(applicant) to risk.band(applicant)

fun handle(score: CreditScore, band: RiskBand): LoanAssessment = LoanAssessment(loanId, score, band)
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/several-values.md)

</TabItem>
<TabItem label="TypeScript (preview)">

```typescript
// One typed object keeps RiskBand as its enum; provide() can also return tuple(score, band) for provided(...) handle parameters.
async provide(): Promise<CreditProfile> {
    const bureau = await currentServices().resolve(CreditBureau);
    const risk = await currentServices().resolve(RiskModel);
    return { score: await bureau.scoreFor(this.applicant), band: await risk.bandFor(this.applicant) };
}

handle({ score, band }: CreditProfile): LoanAssessment {
    return new LoanAssessment(this.loanId, score, band);
}
```

[View TypeScript (preview) snippet source on GitHub](https://github.com/Cratis/Arc.TypeScript/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/several-values.md)

</TabItem>
</Tabs>

In C#, `Provide` may be synchronous or `async` (`Task<T>` / `ValueTask<T>`). On the JVM it may be ordinary, `suspend`, or return a `CompletionStage`, and a `Triple` or `CommandProvidedValues` carries more than two values.

## Use cancellation when the work can stop

<Tabs syncKey="arc-backend-language">
<TabItem label="C#">

```csharp
[Command]
public record AssessLoan(LoanId LoanId, ApplicantId Applicant)
{
    public Task<CreditScore> Provide(ICreditBureau bureau, CancellationToken cancellationToken) =>
        bureau.GetScore(Applicant, cancellationToken);

    public Task<LoanAssessment> Handle(CreditScore creditScore, CancellationToken cancellationToken) =>
        Task.FromResult(new LoanAssessment(LoanId, creditScore));
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Arc/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/cancellation.md)

</TabItem>
<TabItem label="Java">

```java
@Command
public record AssessLoan(LoanId loanId, ApplicantId applicant) {
    // No token parameter: Arc awaits the returned stage inside its command coroutine and cancels
    // that stage when the request is cancelled. It cannot stop work the stage does not own.
    public CompletionStage<CreditScore> provide(CreditBureau bureau) {
        return bureau.scoreAsync(applicant);
    }

    public CompletionStage<LoanAssessment> handle(CreditScore creditScore) {
        return CompletableFuture.completedFuture(new LoanAssessment(loanId, creditScore));
    }
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets-java/scenarios/provide-data-to-a-command/cancellation.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
@Command
data class AssessLoan(val loanId: LoanId, val applicant: ApplicantId) {
    // No token parameter: a suspending provide or handle runs inside Arc's command
    // coroutine, so cancelling the request cancels the work it is awaiting.
    suspend fun provide(bureau: CreditBureau): CreditScore = bureau.score(applicant)

    suspend fun handle(creditScore: CreditScore): LoanAssessment {
        currentCoroutineContext().ensureActive()
        return LoanAssessment(loanId, creditScore)
    }
}
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/cancellation.md)

</TabItem>
<TabItem label="TypeScript (preview)">

```typescript
import { field } from '@cratis/fundamentals';
import { command, currentContext, currentServices } from '@cratis/arc.core';

@command()
export class AssessLoan {
    @field(LoanId) loanId!: LoanId;
    @field(ApplicantId) applicant!: ApplicantId;

    // No token parameter: the execution context carries the request's AbortSignal.
    async provide(): Promise<CreditScore> {
        const bureau = await currentServices().resolve(CreditBureau);
        return bureau.scoreFor(this.applicant, currentContext()?.signal);
    }

    handle(creditScore: CreditScore): LoanAssessment {
        currentContext()?.signal.throwIfAborted();
        return new LoanAssessment(this.loanId, creditScore);
    }
}
```

[View TypeScript (preview) snippet source on GitHub](https://github.com/Cratis/Arc.TypeScript/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/cancellation.md)

</TabItem>
</Tabs>

Cancellation differs by backend. In C#, `Provide` and `Handle` can each take a `CancellationToken` and Arc supplies the current command execution token — the request-aborted token for HTTP endpoints, or the token you pass to `ICommandPipeline`. You do not register `CancellationToken` in the service container; Arc treats it as part of the command execution context.

TypeScript has no `CancellationToken`; inject the request's `AbortSignal` with
`@inject(abortSignal())` on `provide()` or `handle()`, or read `currentContext()?.signal`,
and pass it to cancellable work. See [model-bound commands](/arc/backend/typescript/commands/model-bound/). On the JVM there is **no token parameter at all**: a `suspend` `provide` or `handle` already runs inside Arc's command coroutine, so cancelling the request cancels everything it awaits. Check `ensureActive()` when a long computation has no suspension point of its own.

## Short-circuit before `Handle` runs

`Provide` can stop the command before `Handle` is ever called. The table below
shows C# return shapes; TypeScript returns `rejected(validation(...))` from
`provide()` for a typed failure, as its tab demonstrates.

| Return / do | Result |
| --- | --- |
| `ValidationResult.Error("…")` | command fails validation (HTTP 400) |
| an `AuthorizationResult` that is not authorized | command is unauthorized (HTTP 403) |
| throw | command fails with the exception (HTTP 500) |
| a value | flows into `Handle` as an argument |

Use `Result<,>` to reject or proceed when the data acquisition itself determines whether the command can continue — return the error to stop, or the value to continue:

<Tabs syncKey="arc-backend-language">
<TabItem label="C#">

```csharp
public Result<CreditScore, ValidationResult> Provide(ICreditBureau bureau)
{
    var score = bureau.GetScore(Applicant);
    return score is null
        ? ValidationResult.Error("No credit history", [nameof(Applicant)])
        : score;
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Arc/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/short-circuit.md)

</TabItem>
<TabItem label="Java">

```java
// An Object return lets provide hand back either the value handle consumes or a
// control signal; a returned ValidationResult rejects before handle runs.
public Object provide(CreditBureau bureau) {
    var score = bureau.score(applicant);
    return score == null
        ? ValidationResult.error("No credit history", List.of("applicant"))
        : score;
}

public LoanAssessment handle(CreditScore creditScore) {
    return new LoanAssessment(loanId, creditScore);
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets-java/scenarios/provide-data-to-a-command/short-circuit.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
// An Any return lets provide hand back either the value handle consumes or a
// control signal; a returned ValidationResult rejects before handle runs.
suspend fun provide(bureau: CreditBureau): Any =
    bureau.score(applicant)
        ?: ValidationResult.error("No credit history", listOf("applicant"))

fun handle(creditScore: CreditScore): LoanAssessment = LoanAssessment(loanId, creditScore)
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/short-circuit.md)

</TabItem>
<TabItem label="TypeScript (preview)">

```typescript
// A rejected(...) outcome stops the command with 400 before handle() runs.
async provide(): Promise<CreditScore | Outcome<never>> {
    const bureau = await currentServices().resolve(CreditBureau);
    const score = await bureau.findScore(this.applicant);
    return score ?? rejected(validation('No credit history', ['applicant']));
}

handle(creditScore: CreditScore): LoanAssessment {
    return new LoanAssessment(this.loanId, creditScore);
}
```

[View TypeScript (preview) snippet source on GitHub](https://github.com/Cratis/Arc.TypeScript/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/short-circuit.md)

</TabItem>
</Tabs>

C# states the alternative in the return type with `Result<,>`. Arc.Kotlin recognizes the control value at runtime instead: a `provide` declared to return `Any` may hand back either the value the handler consumes or a `ValidationResult`, `AuthorizationResult`, or `CommandResult`, and Arc short-circuits on the control value. TypeScript uses an `Outcome` such as
`rejected(validation(...))` instead of the C# `Result<,>` shape.

A provided value that no handler parameter consumes is almost always a mistake. C#'s ARC0005 analyzer flags it; TypeScript's [`arc-core/arc0005`](/arc/backend/typescript/code-analysis/arc0005/) ESLint rule flags a direct `provide()` return when `handle()` takes no parameters.

For ordinary command validation, including read-model existence checks, prefer a `CommandValidator<>`. Use `Provide()` when `Handle()` needs acquired data as an input to the decision.

## Provider-owned state can already be available

With a configured by-key read-model provider and a usable command key, `Provide` need not fetch that model again. Arc resolves it as a parameter alongside the services it uses — handy when existing state determines _what_ to fetch. Without those prerequisites, inject your application service or context and fetch explicitly:

<Tabs syncKey="arc-backend-language">
<TabItem label="C#">

```csharp
public Task<ShippingQuote> Provide(OrderReadModel? order, IShippingRates rates) =>
    order is null
        ? Task.FromResult(ShippingQuote.None)
        : rates.Quote(order.Destination, order.TotalWeight);
```

[View C# snippet source on GitHub](https://github.com/Cratis/Arc/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/provider-owned-state.md)

</TabItem>
<TabItem label="Java">

```java
// An Optional parameter preserves owned read-model absence as Optional.empty().
public ShippingQuote provide(Optional<OrderView> order, ShippingRates rates) {
    return order
        .map(view -> rates.quote(view.destination(), view.totalWeight()))
        .orElse(ShippingQuote.NONE);
}
```

[View Java snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets-java/scenarios/provide-data-to-a-command/provider-owned-state.md)

</TabItem>
<TabItem label="Kotlin">

```kotlin
suspend fun provide(order: OrderView?, rates: ShippingRates): ShippingQuote =
    if (order == null) {
        ShippingQuote.NONE
    } else {
        rates.quote(order.destination, order.totalWeight)
    }
```

[View Kotlin snippet source on GitHub](https://github.com/Cratis/Arc.Kotlin/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/provider-owned-state.md)

</TabItem>
<TabItem label="TypeScript (preview)">

```typescript
import { field } from '@cratis/fundamentals';
import { command, commandReadModel, inject, key, readModel } from '@cratis/arc.core';

@readModel()
class LoanState { @field(String) id = ''; @field(Number) score = 0; }

@command()
class AssessFromCurrentState {
    @field(String) @key() id = '';

    @inject(commandReadModel(LoanState))
    provide(state: LoanState): number { return state.score; }

    handle(score: number): number { return score; }
}
// Register LoanState with the owning Chronicle or MongoDB integration before executing.
```

[View TypeScript (preview) snippet source on GitHub](https://github.com/Cratis/Arc.TypeScript/blob/main/Documentation/client-snippets/scenarios/provide-data-to-a-command/provider-owned-state.md)

</TabItem>
</Tabs>

See [Use current state in a command](/arc/scenarios/use-current-state-in-a-command/) for how that resolution works and what a nullable parameter means.

## Cross-cutting values

`Provide` is per-command. For a value that many handlers need — the current tenant's settings, say — register it as a scoped service and take it as a `Handle` parameter. Each backend resolves declared handler or provider dependencies from its service container; on TypeScript, use `@inject(...)` or generated metadata for erased parameter bindings.

## When not to use it

If `Handle` already has everything it needs from the command and its injected services, you don't need `Provide`. Reach for it specifically when a decision depends on data you must fetch — most often external or application-service data — where moving the fetch out buys you the testability.

## See also

- [Return a result or an error](/arc/scenarios/return-a-result-or-error/) — the shapes `Handle()` itself can return.
- [Test a command](/arc/scenarios/test-a-command/) — testing a slice through the real pipeline.
- [Validate a command](/arc/scenarios/validate-a-command/) — where `ValidationResult.Error` comes from.
- [Use current state in a command](/arc/scenarios/use-current-state-in-a-command/) — the read model Arc resolves for you, in all three positions.
