﻿# Project structure

> For the complete documentation index, see [llms.txt](/llms.txt)

After running `icp new`, you have a complete project ready to build and deploy. This page explains every file and directory that `icp new` creates, how they fit together, and the key concepts behind the project model.

## Prerequisites

You should have already created a project by following the [Quickstart](quickstart.md). The examples below use the hello-world template with a Rust backend, but the structure is similar for Motoko.

## Project layout

A typical project generated by `icp new my-project` looks like this after the first build and deploy:

```
my-project/
├── icp.yaml              # Project configuration (the project root)
├── .icp/                 # Generated files (canister IDs, build artifacts) ← created by icp deploy
│   ├── cache/            # Ephemeral: safe to delete, rebuilt automatically
│   └── data/             # Persistent: mainnet canister ID mappings
├── backend/
│   ├── canister.yaml     # Canister-specific configuration
│   ├── Cargo.toml        # Rust package manifest
│   ├── backend.did       # Candid interface definition
│   └── src/
│       └── lib.rs        # Canister source code
├── frontend/
│   ├── canister.yaml     # Asset canister configuration
│   ├── package.json      # Node dependencies (binding generation)
│   └── app/              # Frontend application (React + Vite)
│       ├── src/
│       ├── dist/         # Built assets (uploaded to the asset canister) ← created by icp deploy
│       └── package.json
└── .gitignore            # Ignores .icp/cache/ (but tracks .icp/data/)
```

## icp.yaml

The `icp.yaml` file is the project root. `icp` commands look for this file in the current directory and parent directories to locate the project.

In the hello-world template, `icp.yaml` is minimal:

```yaml
canisters:
  - backend
  - frontend
```

Each entry under `canisters` is a directory name. `icp` looks for a `canister.yaml` file inside that directory. You can also define canisters inline or use glob patterns:

```yaml
canisters:
  - canisters/*          # Discover all canister.yaml files under canisters/
  - name: inline-canister
    build:
      steps:
        - type: script
          commands:
            - cargo build --target wasm32-unknown-unknown --release
            - cp target/wasm32-unknown-unknown/release/my_canister.wasm "$ICP_WASM_OUTPUT_PATH"
```

Beyond canisters, `icp.yaml` can also define **networks** (where to deploy) and **environments** (named deployment configurations). Two of each are provided implicitly:

| Implicit environment | Network | Purpose |
|---------------------|---------|---------|
| `local` | `local` (managed, localhost:8000) | Local development |
| `ic` | `ic` (connected, https://icp-api.io) | Mainnet production |

You only need to add custom networks or environments when you have staging environments, testnets, or other deployment targets. See the [icp-cli configuration reference](https://cli.internetcomputer.org/0.2/reference/configuration#networks) for the full schema.

## Canister configuration (canister.yaml)

Each canister has its own `canister.yaml` that defines how to build and deploy it.

### Backend canister

#### Motoko

The Motoko backend uses the `@dfinity/motoko` recipe:

```yaml
name: backend
recipe:
  type: "@dfinity/motoko@v4.1.0"
  configuration:
    main: src/main.mo
    candid: backend.did
```
#### Rust

The Rust backend uses the `@dfinity/rust` recipe:

```yaml
name: backend
recipe:
  type: "@dfinity/rust@v3.1.0"
  configuration:
    package: backend
    shrink: true
    candid: backend.did
```

### Frontend canister

The frontend uses the `@dfinity/asset-canister` recipe, which builds the frontend app and uploads the output to an asset canister:

```yaml
name: frontend
recipe:
  type: "@dfinity/asset-canister@v2.1.0"
  configuration:
    build:
      - npm install
      - npm run generate --prefix app
      - npm run build
    dir: app/dist
```

The `build` commands run in order: install dependencies, generate TypeScript bindings from the backend's Candid file, then build the Vite app. The `dir` field tells the recipe which directory to upload to the asset canister.

## Recipes

Recipes are reusable build templates that expand into full canister build and sync steps. Instead of writing shell commands from scratch, you reference a recipe with a version pin and pass configuration parameters.

The four official recipes cover the most common patterns:

| Recipe | Purpose |
|--------|---------|
| `@dfinity/rust@<version>` | Rust canisters with Cargo |
| `@dfinity/motoko@<version>` | Motoko canisters |
| `@dfinity/asset-canister@<version>` | Asset canisters for static files |
| `@dfinity/prebuilt@<version>` | Pre-compiled WASM files |

To see what a recipe expands to after template rendering:

```bash
icp project show
```

This outputs the effective configuration, including all expanded recipe steps and implicit defaults.

Recipes are Handlebars templates hosted at [dfinity/icp-cli-recipes](https://github.com/dfinity/icp-cli-recipes). You can also create local or remote recipes for custom build patterns. See the [icp-cli recipes documentation](https://cli.internetcomputer.org/0.2/guides/creating-recipes) for details.

## The .icp/ directory

When you build or deploy, `icp` creates a `.icp/` directory in your project root:

```
.icp/
├── cache/                    # Ephemeral data (safe to delete)
│   ├── artifacts/            # Built WASM files
│   ├── mappings/             # Canister IDs for local networks
│   └── networks/             # Local network state
└── data/
    └── mappings/             # Canister IDs for connected networks (mainnet)
```

### What to commit

| Directory | Commit? | Why |
|-----------|---------|-----|
| `.icp/cache/` | No | Rebuilt automatically. Add to `.gitignore`. |
| `.icp/data/` | Yes | Contains mainnet canister ID mappings. Deleting means `icp` won't know which canisters you've deployed (though the canisters still exist on the network). |

The hello-world template's `.gitignore` already excludes `.icp/cache/` and tracks `.icp/data/`.

## Canister discovery

Canister IDs are assigned at deployment time and differ between environments. Hardcoding them creates problems when switching between local development and mainnet. `icp` solves this with automatic canister ID injection, triggered by `icp deploy`.

During deployment:

1. All canisters are created (or looked up) to get their IDs
2. Each canister receives environment variables for every other canister: `PUBLIC_CANISTER_ID:<canister-name>`
3. WASM code is installed

### Frontend reads backend IDs

The asset canister exposes injected canister IDs through a cookie called `ic_env`. Your frontend JavaScript reads this cookie to discover backend canister IDs at runtime, with no code changes needed between environments:

```typescript
import { getCanisterEnv } from "@icp-sdk/core/agent/canister-env";

interface CanisterEnv {
  "PUBLIC_CANISTER_ID:backend": string;
  IC_ROOT_KEY: Uint8Array;
}

const env = getCanisterEnv<CanisterEnv>();
```

### Backend reads other backend IDs

Backend canisters read the injected variables directly:

#### Motoko

```motoko
import Runtime "mo:core/Runtime";
import Principal "mo:core/Principal";

switch (Runtime.envVar("PUBLIC_CANISTER_ID:other_canister")) {
  case (?id) { Principal.fromText(id) };
  case null { /* handle missing */ };
};
```
#### Rust

```rust
use candid::Principal;

let backend_id = Principal::from_text(
    &ic_cdk::api::env_var_value("PUBLIC_CANISTER_ID:other_canister")
).unwrap();
```

## Binding generation

Bindings are generated TypeScript (or Rust) code that provides type-safe access to canister methods. They are created from Candid interface files (`.did`), which define a canister's public API.

`icp` itself does not generate bindings. Instead, use dedicated tools:

| Language | Tool | Documentation |
|----------|------|---------------|
| TypeScript/JavaScript | `@icp-sdk/bindgen` | [js.icp.build](https://js.icp.build) |
| Rust | `candid` crate | [docs.rs/candid](https://docs.rs/candid/latest/candid/) |
| Other languages | `didc` CLI | [github.com/dfinity/candid](https://github.com/dfinity/candid) |

In the hello-world template, the frontend's build step runs `npm run generate --prefix app`, which uses `@icp-sdk/bindgen` to generate TypeScript bindings from the backend's `backend.did` file.

For a deep dive on binding generation, see [Binding generation](../guides/canister-calls/candid.md#binding-generation).

## Next steps

- [What next?](choose-your-path.md): pick a development path based on what you want to build
- [Binding generation](../guides/canister-calls/candid.md#binding-generation): deep dive on generating type-safe client code
- [Asset canister](../guides/frontends/asset-canister.md): how the frontend recipe and asset upload work
- [Canister lifecycle](../guides/canister-management/lifecycle.md): build, deploy, upgrade, and manage canisters
- [icp-cli reference](https://cli.internetcomputer.org/0.2/reference/cli): full CLI and configuration documentation
