# Configuration

Config lives in `deployoor.config.ts` at your project root. Every field is optional — and so is the
file itself: `deployoor generate` works with no config at all, using the defaults below. Add one (by
hand or with `npx deployoor init`) when you want to change something.

```ts
import { defineConfig } from "deployoor";
import { etherscan } from "@deployoor/etherscan";
import { slack } from "@deployoor/slack";

export default defineConfig({
  include: ["Token", "Vault"], // default: every contract with bytecode
  out: "./deployers", // generated deployers output
  deploymentsPath: "./deployments", // deployment records
  plugins: [etherscan({ apiKey: process.env.ETHERSCAN_KEY }), slack({ webhook: process.env.SLACK_WEBHOOK })],
  onPluginError: "warn", // "warn" (default) or "throw"
  redeploymentStrategy: "on-change", // "on-change" (default) | "never" | "always"
  redeploymentStrategyByChainId: { 1: "never" }, // pin mainnet to reuse-only
});
```

## Options

| Option                          | Default                  | Description                                                                               |
| ------------------------------- | ------------------------ | ----------------------------------------------------------------------------------------- |
| `include`                       | all deployable contracts | Filter by contract name                                                                   |
| `out`                           | `./deployers`            | Where typed deployers are written                                                         |
| `deploymentsPath`               | `./deployments`          | Where JSON records are read/written                                                       |
| `artifactsPath`                 | framework default        | Compiled artifacts dir, if not `./artifacts` (Hardhat) or `./out` (Foundry)               |
| `framework`                     | auto-detected            | `"hardhat"` | `"foundry"` | `"tevm"` — name the toolchain outright                      |
| `sources`                       | `./src`                  | `tevm` only: the `.sol` directory to compile                                              |
| `importExtension`               | `"auto"`                 | `.js` on generated relative imports under node16/nodenext ([details](#import-extensions)) |
| `plugins`                       | `[]`                     | Deploy-lifecycle hooks                                                                    |
| `onPluginError`                 | `"warn"`                 | `"throw"` fails the run on plugin error                                                   |
| `redeploymentStrategy`          | `"on-change"`            | Reuse vs redeploy when a record exists ([details](/concepts/idempotency))                 |
| `redeploymentStrategyByChainId` | `{}`                     | Per-chain override, keyed by chain id                                                     |

`defineConfig` is typed via `import { defineConfig } from "deployoor"`. If a name in `include` matches nothing after compile, `deployoor generate` warns (typo or missing artifact).

### A non-default project layout

Usually nothing to do. deployoor reads `paths.artifacts` from your `hardhat.config.*` and `out` from
the active `foundry.toml` profile, so moving your build output needs no deployoor config.

`artifactsPath` is for the case neither config states: an output directory produced some other way.
It wins over both.

```ts
export default defineConfig({
  artifactsPath: "./build/artifacts",
});
```

The split is worth internalising, because it decides whether you need a config file at all:

| Path                      | Owner          | Who tells deployoor            |
| ------------------------- | -------------- | ------------------------------ |
| sources, artifacts, cache | your framework | nobody — it reads their config |
| `out`, `deploymentsPath`  | deployoor      | you, here                      |

[`examples/custom-paths`](https://github.com/raycashxyz/deployoor/tree/main/examples/custom-paths)
moves all five and sets only the two deployoor owns. The other examples in that repo have **no config
file at all**, because their paths are the defaults.

:::warning
deployoor reads `paths.artifacts` by **importing** `hardhat.config.*`, and a config that registers a
plugin cannot be imported outside a Hardhat run — it throws `HH5: HardhatContext is not created`. That
covers most real Hardhat projects, so if yours registers anything **and** moves its artifacts
directory, set `artifactsPath` here.

The symptom is confusing on its own: `deployoor generate` works, because
[`@deployoor/hardhat`](/guides/hardhat) hands the resolved path over directly — but a deploy or a test
reads the config itself, so it looks in the default `./artifacts` and reports nothing compiled.
:::

Toolchain detection keys on the config file (`foundry.toml`, `hardhat.config.*`). A project with
neither, or one whose sources sit somewhere unusual, can name it outright with `framework`.

## Generated output

`deployoor generate` writes, per contract:

* `getOrDeploy<Name>` — idempotent deployer returning a typed viem contract object
* Typed artifact module under `deployers/types/`
* Project-level `register` and `reset` bound to your config

### Import extensions

The relative imports inside `deployers/` are written to match your TypeScript setup, so the
generated tree typechecks without you touching `tsconfig.json`.

Under `moduleResolution: "node16"` or `"nodenext"` — Hardhat 3's default — TypeScript rejects an
extensionless relative import (`TS2835`), so deployoor emits `./types/Counter.js`. Everywhere else
it emits `./types/Counter`, because that is the idiomatic form and a `.js` specifier is a resolution
failure in setups that do not map it back to `.ts` — webpack without `resolve.extensionAlias`,
ts-jest without a `moduleNameMapper`.

`moduleResolution` does not have to be spelled out. TypeScript infers it from `module`, and
`"node16"`, `"node18"` and `"node20"` all imply `moduleResolution: "node16"` (`"nodenext"` implies
`"nodenext"`), so naming any of those and nothing else is enough to require extensions. `"esnext"`,
`"preserve"` and `"commonjs"` imply modes that accept extensionless imports.

Detection uses [`get-tsconfig`](https://github.com/privatenumber/get-tsconfig) — the same resolver
`tsx` uses — so it reads your project the way your runtime does: the nearest `tsconfig.json` at or
above the project, with `extends` resolved (a `moduleResolution` inherited from a preset such as
`@tsconfig/node22` counts). A project with no readable tsconfig keeps the extensionless form.
Override it when you need to:

```ts
export default defineConfig({
  importExtension: "none", // "auto" (default) | "none" | "js"
});
```

## Per-deploy plugin overrides

Skip or customize plugins for a single deploy:

```ts
await getOrDeployVault({
  ...clients,
  args: [token.address],
  plugins: { etherscan: false },
});
```

In tests, disable verifiers and notifiers:

```ts
await getOrDeployToken({
  ...clients,
  args: [owner],
  plugins: { slack: false, etherscan: false },
});
```

## In-memory store (tests)

Pass a store override to keep deploys off disk:

```ts
import { memoryStore } from "deployoor";

await getOrDeployToken({
  ...clients,
  args: [owner],
  store: memoryStore(),
});
```

`@deployoor/testing`'s `createTestClients()` passes an in-memory store automatically when you spread `clients`.
