> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withboundary.com/llms.txt
> Use this file to discover all available pages before exploring further.

# enforce() vs defineContract()

> Two entrypoints, one engine — when to pick which

`@withboundary/contract` exposes two entrypoints that do the same job:

```typescript theme={null}
// Reusable — define once, call many times.
const contract = defineContract({ name, schema, rules, ... });
await contract.accept(run);
await contract.accept(run);  // reuses definition

// One-off — define and run in one call.
await enforce(schema, run, { name, rules, ... });
```

Both run the same engine. Pick based on how you use the contract in your codebase.

## `defineContract` — the default

Use when:

* The contract is used in **more than one place** (multiple endpoints, multiple environments, shared across a service).
* You want a **named, typed object** to pass around.
* You want to **override options per call** without redefining.

```typescript theme={null}
// shared/contracts.ts
export const leadContract = defineContract({
  name: "lead-scoring",
  schema: leadSchema,
  rules: leadRules,
  retry: { maxAttempts: 3 },
  logger: appLogger,
});

// routes/score.ts
import { leadContract } from "../shared/contracts";
await leadContract.accept(run);

// routes/score-high-stakes.ts
await leadContract.accept(run, { retry: { maxAttempts: 5 } });  // per-call override
```

Every existing guide on this site uses `defineContract`.

## `enforce` — the shortcut

Use when:

* The contract is **one-off** (experimental notebook, a one-shot script, a migration job).
* You don't need to reuse the definition.
* You prefer a flat call with all options in one place.

```typescript theme={null}
import { enforce } from "@withboundary/contract";

const result = await enforce(schema, run, {
  name: "ad-hoc-extraction",
  rules: [...],
  retry: { maxAttempts: 2 },
});
```

`enforce` is implemented on top of `defineContract` — there's no behavioral difference. It's just a convenience when the extra line would be noise.

## Side-by-side

Same behavior, different ergonomics:

```typescript theme={null}
// enforce — one call
const result = await enforce(schema, run, { name: "score", rules });

// defineContract — two calls
const contract = defineContract({ name: "score", schema, rules });
const result = await contract.accept(run);
```

Both accept identical options (`ContractOptions<T> & { name: string }`). `enforce`'s signature re-arranges the required fields (schema + run + options), while `defineContract` bundles everything into one config object and returns a factory.

## When to switch from `enforce` to `defineContract`

* You start calling `enforce(schema, ..., { name: "x", rules })` in two or more places. DRY it up — create a shared `contract` and reuse.
* You want to wire a single `logger` (especially `createBoundaryLogger`) across many calls without repeating the option.
* Your test suite starts needing to share the contract config with production code.

## TypeScript inference

Both preserve schema-driven `T` inference:

```typescript theme={null}
const schema = z.object({ tier: z.enum(["hot", "warm", "cold"]) });

const r1 = await enforce(schema, run, { name: "x" });
if (r1.ok) r1.data.tier;  // typed as "hot" | "warm" | "cold"

const c = defineContract({ name: "x", schema });
const r2 = await c.accept(run);
if (r2.ok) r2.data.tier;  // same
```

## What about the dashboard?

The SDK reads `contractName` off each event. Whether you used `enforce` or `defineContract`, the dashboard groups events by `name`. Keep names **stable** — renaming them splits your dashboard history.

## See also

<CardGroup cols={2}>
  <Card title="defineContract" icon="cube" href="/api-reference/define-contract">
    Full option reference
  </Card>

  <Card title="enforce" icon="bolt" href="/api-reference/enforce">
    Signature + example
  </Card>
</CardGroup>
