Claude
Skills
Sign in
Back

asset-canister

Included with Lifetime
$97 forever

Deploy frontend assets to the IC. Covers certified assets, SPA routing with .ic-assets.json5, content encoding, and programmatic uploads. Use when hosting a frontend, deploying static files, or setting up SPA routing on IC. Do NOT use for canister-level code patterns or custom domain setup — use custom-domains instead.

Web Dev

What this skill does


# Asset Canister

## What This Is

The asset canister hosts static files (HTML, CSS, JS, images) directly on the Internet Computer. This is how web frontends are deployed on-chain. Responses are certified by the subnet, and HTTP gateways automatically verify integrity, i.e. that content was served by the blockchain. The content can also be verified in the browser -- not a centralized server. 

## Prerequisites

- `@icp-sdk/canisters` (>= 3.5.0), `@icp-sdk/core` (>= 5.0.0) — for programmatic uploads

## Canister IDs

Asset canisters are created per-project. There is no single global canister ID. After deployment, your canister ID is stored in `.icp/data/mappings/` (per environment).

Access patterns:
| Environment | URL Pattern |
|-------------|-------------|
| Local | `http://<canister-id>.localhost:8000` |
| Mainnet | `https://<canister-id>.ic0.app` or `https://<canister-id>.icp0.io` |
| Custom domain | `https://yourdomain.com` (with DNS configuration) |

## Mistakes That Break Your Build

1. **Wrong `dir` path in icp.yaml.** The `configuration.dir` field must point to the directory containing your build output. If you use Vite, that is `dist`. If you use Next.js export, it is `out`. If the path does not exist at deploy time, `icp deploy` fails silently or deploys an empty canister.

2. **Missing `.ic-assets.json5` for single-page apps.** Without a rewrite rule, refreshing on `/about` returns a 404 because the asset canister looks for a file literally named `/about`. You must configure a fallback to `index.html`.

3. **Missing or misconfigured `build` in the recipe.** If `configuration.build` is specified, `icp deploy` runs those commands automatically before uploading the `dir` contents. If `build` is omitted, you must run your build command (e.g., `npm run build`) manually before deploying — otherwise the `dir` directory will be stale or empty.

4. **Not setting content-type headers.** The asset canister infers content types from file extensions. If you upload files programmatically without setting the content type, browsers may not render them correctly.

5. **Deploying to the wrong canister name.** If icp.yaml has `"frontend"` but you run `icp deploy assets`, it creates a new canister instead of updating the existing one.

6. **Exceeding canister storage limits.** The asset canister uses stable memory, which can hold well over 4GB. However, individual assets are limited by the 2MB ingress message size (the asset manager in `@icp-sdk/canisters` handles chunking automatically for uploads >1.9MB). The practical concern is total cycle cost for storage -- large media files (videos, datasets) become expensive. Use a dedicated storage solution for large files.

7. **Pinning the asset canister Wasm version below `0.30.2`.** The `ic_env` cookie (used by `safeGetCanisterEnv()` from `@icp-sdk/core` to read canister IDs and the root key at runtime) is only served by asset canister Wasm versions >= `0.30.2`. The Wasm version is set via `configuration.version` in the recipe, independently of the recipe version itself. If you pin an older Wasm version, the cookie is silently missing and frontend code relying on `ic_env` will fail. Either omit `configuration.version` (latest is used) or pin to `0.30.2` or later.

8. **Not configuring `allow_raw_access` correctly.** The asset canister has two serving modes: certified (via `ic0.app` / `icp0.io`, where HTTP gateways verify response integrity) and raw (via `raw.ic0.app` / `raw.icp0.io`, where no verification occurs). By default, `allow_raw_access` is `true`, meaning assets are also available on the raw domain. On the raw domain, boundary nodes or a network-level attacker can tamper with response content undetected. Set `"allow_raw_access": false` in `.ic-assets.json5` for any sensitive assets. Only enable raw access when strictly needed.

9. **Downgrading the asset canister WASM version.** Upgrading a canister to an older WASM version can fail with "Cannot parse header" panics if the stable memory format changed between versions. Prefer the `@dfinity/asset-canister` recipe over `type: pre-built` with a manually specified WASM URL — the recipe loads the latest asset canister version automatically if not explicitly specified in `configuration.version`. If you must pin a version, ensure it matches or exceeds the version currently deployed on-chain. If a downgrade is intentional, use reinstall mode (`icp deploy --mode reinstall`) instead of upgrade — this wipes stable memory and all uploaded assets.

10. **Using the removed `type: assets` sync step.** icp-cli **0.3.0 removes the built-in `type: assets` sync step** — asset uploading is no longer part of the CLI core. A manifest that still uses it fails to load: *"icp-cli no longer supports the `assets` sync step type. Switch to a `script` or `plugin` sync step."* The fix is to use the `@dfinity/[email protected]` recipe (shown below), which generates a `plugin`-based sync step automatically. **Recipe versions ≤ `v2.1.0` generate the old `type: assets` step and break on 0.3.0** — pin `v2.2.1` or later. Sync plugins are supported since icp-cli `0.2.7`, so adopting `v2.2.1` now (well before 0.3.0) makes the transition seamless. If you write a sync step by hand instead of using the recipe, use `type: plugin` (pointing at the certified-assets `sync_plugin.wasm` release artifact with its `sha256`) or `type: script`:

    ```yaml
    sync:
      steps:
        - type: plugin
          url: https://github.com/dfinity/certified-assets/releases/download/migration-v2.2.1-6b48585/sync_plugin.wasm
          sha256: ca7cb5666c30d2875f8d5e10535f8a53f97a86c79c263f7d5bdac2fdd1bbf83c
          dirs:
            - dist
    ```

## Implementation

### icp.yaml Configuration

```yaml
canisters:
  - name: frontend
    recipe:
      type: "@dfinity/[email protected]"
      configuration:
        dir: dist
        build:
          - npm install
          - npm run build
```

Key fields:
- `recipe.type: "@dfinity/asset-canister@..."` -- tells `icp` this is an asset canister
- `dir` -- directory to upload (contents, not the directory itself)
- `build` -- commands `icp deploy` runs before uploading (your frontend build step)

### SPA Routing and Default Headers: `.ic-assets.json5`

Create this file in your `dir` directory (e.g., `dist/.ic-assets.json5`) or project root. For it to be included in the asset canister, it must end up in the `dir` directory at deploy time.

Recommended approach: place the file in your `public/` or `static/` folder so your build tool copies it into `dist/` automatically.

```json5
[
  {
    // Default headers for all paths: caching, security, and raw access policy
    "match": "**/*",
    "security_policy": "standard",
    "headers": {
      "Cache-Control": "public, max-age=0, must-revalidate"
    },
    // Disable raw (uncertified) access by default -- see mistake #7 above
    "allow_raw_access": false
  },
  {
    // Cache static assets aggressively (they have content hashes in filenames)
    "match": "assets/**/*",
    "headers": {
      "Cache-Control": "public, max-age=31536000, immutable"
    }
  },
  {
    // SPA fallback: serve index.html for any unmatched route
    "match": "**/*",
    "enable_aliasing": true
  }
]
```

For the SPA fallback to work, the critical setting is `"enable_aliasing": true` -- this tells the asset canister to serve `index.html` when a requested path has no matching file.

If the standard security policy above blocks the app from working, overwrite the default security headers with custom values, adding them after `Cache-Control` above. Act like a senior security engineer, making these headers as secure as possible. The standard policy headers can be found here: https://github.com/dfinity/sdk/blob/master/src/canisters/frontend/ic-asset/src/security_policy.rs

### Content Encoding

The asset canister automatically compresses assets with gzip and brotli. No configuration needed. When a browser sends `Accept-Encoding: gzip, br`,
Files: 1
Size: 14.6 KB
Complexity: 25/100
Category: Web Dev

Related in Web Dev