---
title: Your first plan, approval, and receipt
description: Run the whole lifecycle against an in-memory provider, then read the plan, the receipt, and the readiness it produced.
sidebar:
  label: Quickstart
seo:
  title: Build and apply your first DNS plan
---

You will connect a fake provider, build a plan for two DNS requirements, approve its digest, apply
it, and observe the result. No provider account, network request, database, or browser is involved.

## Prerequisites

- Node.js 24.10 or newer, or Bun 1.4.4 or newer.
- A TypeScript project using ESM.

## Install

```package-install
domainkit effect
```

`effect` is a peer dependency. The package root is Effect-native, and every step in this tutorial
is an Effect.

## Write the program

Create `quickstart.ts`:

<Snippet file="packages/domainkit/examples/effect/quickstart.ts" title="quickstart.ts" />

## Run it

```sh
bun quickstart.ts
```

It prints `firstPlan`, the `receipt`, the readiness result, and `secondPlan`. The two plans list
their operations and whether `Plan.isApplicable` is true:

```json
{
  "firstPlan": { "operations": ["Create CNAME", "Noop TXT"], "isApplicable": true },
  "receipt": { "status": "complete" },
  "secondPlan": { "operations": ["Noop CNAME", "Noop TXT"], "isApplicable": false }
}
```

The receipt carries more fields than shown here, and its ids differ on every run.

## What you just read

`Provision.plan` listed the zone and reconciled it against the two requirements. The fake provider
already holds the TXT record, so the plan carries one `Create` for the CNAME and one `Noop` for the
TXT, plus a digest over both.

`Provision.approve` recorded consent for that digest. `Provision.apply` re-planned the zone, saw the
digest had not moved, wrote the CNAME, and returned a receipt whose `outcomes` name one `Applied`
and one `Skipped` with reason `noop`.

`Verify.observe` read the provider through the connection and public DNS through the resolver, and
stored per-requirement readiness with a `nextCheckAt` for the next look.

## The moving parts

| Piece                   | What it is here                                                       |
| ----------------------- | --------------------------------------------------------------------- |
| `Testing.provider`      | A provider definition over in-memory zones, with a token method       |
| `DomainKit.layerMemory` | Every lifecycle service, memory storage, and a throwaway custody key  |
| `Principal.Service`     | Who is acting. Every stored row is scoped by it; there is no default  |
| `Connect.start`         | Connects the provider and attaches the domain in one call for a token |

## Plan again

The program plans a second time after the apply, in the same run:

<Snippet file="packages/domainkit/examples/effect/quickstart.ts" region="replan" />

The fake provider kept the CNAME the apply wrote, so `secondPlan` holds `Noop CNAME` and
`Noop TXT`, `Plan.isApplicable` is `false`, and there is nothing to write. Planning is a read, so it
is safe to run whenever a screen needs to show what would change.

The fake provider lives in memory and is rebuilt on each invocation, so every `bun quickstart.ts`
run starts from the same zone and prints the same `Create` and `Noop` first plan. A real provider
keeps the CNAME between runs.

## Next

**[Plans](/docs/core/plans)**

Conflicts, partial approval, partial receipts, and stale plans.

**[Host integration](/docs/guides/host-integration)**

Mount the routes and swap the fakes for a real provider and database.

The [entry points](/docs/reference/entry-points) reference lists what each import carries, and
[testing](/docs/guides/testing) covers the fakes this tutorial used.
