---
title: "axiom.yaml manifest reference"
description: "The complete schema of the axiom.yaml package manifest: every field, its type, default, validation rule, and which CLI commands read or write it."
category: reference
surfaces: [cli]
related: [getting-started/first-node, concepts/nodes-packages-flows, concepts/type-system, guides/import-package-types, guides/manage-secrets, reference/cli/axiom-init, reference/cli/axiom-validate, reference/glossary]
last_reviewed: 2026-06-06
---

# axiom.yaml manifest reference

`axiom.yaml` is the package manifest. It sits at the root of every Axiom
package and declares the package's identity, its nodes, the message types
it imports from other packages, and optional build overrides. The Axiom CLI
locates the package root by walking up from the current directory until it
finds `axiom.yaml`.

Three fields are required: `name`, `version`, and `language`. Everything
else is optional. `axiom validate` checks the manifest before publishing
(see [Validation rules](#validation-rules)).

## Complete example

Every field the parser accepts, in one manifest:

```yaml
# axiom.yaml — at the package root
name: christian/text-tools
version: 1.2.0
language: python
description: Text processing nodes
author: Christian Lucas
license: MIT
tags:
  - text
  - nlp
nodes:
  - name: SummarizeText
    description: Summarize input text
    input: TextRequest
    output: SummaryResult
    required_secrets:
      - OPENAI_API_KEY
  - name: StreamTokens
    input: TextRequest
    output: TokenFrame
    type: pipeline
imports:
  - package: axiom-official/axiom-conv-ai
    version: 1.0.0
    messages:
      - ChatTurn
env:
  - name: LOG_LEVEL
    description: Verbosity of node logging
    required: false
    secret: false
    default: info
build:
  dockerfile: custom/Dockerfile
  system_deps:
    - ffmpeg
```

A minimal valid manifest is just the three identity fields — `axiom init`
writes exactly that (plus your `--description` if given, and a commented
example `nodes:` block you can delete).

## Top-level fields

| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
| `name` | string | yes | — | Scoped package name, `handle/package-name` |
| `version` | string | yes | — | Semantic version of the package |
| `language` | string | yes | — | One of `go`, `python`, `rust`, `java`, `typescript`, `csharp` |
| `type` | string | no | `""` | `proto-only` to skip build and deployment; otherwise omit |
| `description` | string | no | `""` | Shown in the registry on publish |
| `author` | string | no | `""` | Stored with the package record on publish |
| `license` | string | no | `""` | Stored with the package record on publish |
| `tags` | list of string | no | `[]` | Discovery tags, stored in the registry's tag index on publish |
| `nodes` | list of node | no | `[]` | The package's nodes (see [nodes](#nodes)) |
| `imports` | list of import | no | `[]` | Message types from other packages (see [imports](#imports)) |
| `env` | list of env var | no | `[]` | Runtime environment variables (see [env](#env)) |
| `build` | object | no | unset | Build overrides (see [build](#build)) |

`description`, `author`, and `license` are written to the registry when you
publish with `axiom push`. `tags` are written to the registry's tag index
on publish. Marketplace search currently matches on package name,
description, and author — not on these tags.

## Identity fields: name, version, language

**`name`** must be scoped: `your-handle/package-name`. Both parts may
contain only lowercase letters, digits, and hyphens, and each part must
start with a letter or digit (pattern:
`^[a-z0-9][a-z0-9-]*/[a-z0-9][a-z0-9-]*$`). An unscoped or missing name
fails validation. `axiom init christian/text-tools` creates a local
directory named after the part after the last `/` (`text-tools/`).

**`version`** must be semantic versioning: `MAJOR.MINOR.PATCH`, with an
optional leading `v` and an optional pre-release suffix of letters, digits,
and dots after a hyphen (pattern:
`^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$`). Examples: `1.0.0`,
`v2.1.3`, `0.4.0-beta.1`. `axiom init` writes `0.1.0` unless you pass
`--version`.

**`language`** must be exactly one of `go`, `python`, `rust`, `java`,
`typescript`, `csharp`. It selects the node templates, the generated
service code, and the Dockerfile the build uses. `axiom init` defaults to
`go` unless you pass `--language` (`-l`).

## nodes

Each entry in `nodes` declares one node in the package. `axiom create node`
appends entries here for you; you can also edit the list by hand.

| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
| `name` | string | yes | — | PascalCase node name, unique within the package |
| `description` | string | no | `""` | Shown in the registry |
| `input` | string | yes | — | Input message name |
| `output` | string | yes | — | Output message name |
| `type` | string | no | unary | Omit or `unary` for one-in/one-out; `pipeline` for a streaming generator |
| `required_secrets` | list of string | no | `[]` | Secret names the node reads at runtime |
| `mutation_capable` | bool | no | `false` | Declares the node emits state mutations on its response |

**`name`** — `axiom create node` enforces PascalCase: the first character
must be uppercase, and only letters and digits are allowed. Duplicate node
names in one manifest are rejected. The node's source file is the
snake_case form of the name (`SummarizeText` → `nodes/summarize_text.py`).

**`input` / `output`** — each must name a [message](/docs/reference/glossary#message)
defined in `messages/messages.proto` or available from an imported package.
`axiom validate` fails when a referenced message cannot be found.

**`type`** — a node is unary unless `type: pipeline` is set. `axiom create
node --type` accepts only `unary` or `pipeline` and writes `type: pipeline`
to the manifest only for pipeline nodes (the key is omitted for unary).
See [execution model](/docs/concepts/execution-model).

**`required_secrets`** — names of tenant [secrets](/docs/reference/glossary#secret)
the node reads at runtime. The declaration is **informational**: the platform
does not enforce it at invocation time. A missing secret does not block a run —
`secrets.Get("NAME")` simply yields a not-found result (`found = false`) at read
time, which the node handles. The marketplace displays the list so users know
which secrets to register. `axiom validate` emits a non-blocking warning when
node source calls `secrets.Get("NAME")` with a name missing from this list. See
[manage secrets](/docs/guides/manage-secrets).

**`mutation_capable`** — declares that the node emits state mutations on
its response. The flow compiler reads this across a flow's nodes to decide
whether to enable the mutation path; the default `false` keeps a node out
of that path entirely.

## imports

Each entry in `imports` declares a dependency on another package's message
types. Do not write these entries by hand — run
`axiom import <package>[@version]`, which downloads the package's `.proto`
files into `imports/<flattened-name>/<version>/` (scope `/` becomes `-`,
e.g. `christian/text-ops` → `imports/christian-text-ops/1.0.0/`), adds or
updates the manifest entry, and runs `axiom generate`.

| Field | Type | Required | Purpose |
|---|---|---|---|
| `package` | string | yes | Scoped name of the imported package |
| `version` | string | yes | Exact version of the imported package |
| `messages` | list of string | yes | Message names imported from that package |

Validation rules for imports:

- An import entry with an **empty `messages` list is a hard error** and
  blocks publishing. Fix it by re-running
  `axiom import <package>@<version>`, which populates the list.
- A message name listed here but **not found in the downloaded proto
  files** is a warning — it usually means a stale entry after the imported
  package changed.
- The downloaded proto files must exist locally under
  `imports/<flattened-name>/<version>/`; if they are missing, re-run
  `axiom import <package>@<version>`.

See [import package types](/docs/guides/import-package-types) for the full
workflow.

## env

Each entry in `env` describes a runtime environment variable for the
package. All fields except `name` are optional.

| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
| `name` | string | yes | — | Variable name |
| `description` | string | no | `""` | What the variable controls |
| `required` | bool | no | `false` | Whether the variable must be set |
| `secret` | bool | no | `false` | Whether the value is sensitive |
| `default` | string | no | `""` | Default value |

The `env` section is declarative metadata about what the package expects;
`axiom validate` does not check it. For credentials read by node code at
runtime, use [secrets](/docs/guides/manage-secrets) and
`required_secrets` instead — secrets are tenant-scoped and reach node code
through `AxiomContext`.

## build

The optional `build` section overrides how the package's container image is
built. Most packages never need it — `axiom build` and `axiom push`
generate a Dockerfile automatically from the package language.

| Field | Type | Required | Purpose |
|---|---|---|---|
| `dockerfile` | string | no | Path (relative to the package root) to a custom Dockerfile used instead of the generated one |
| `system_deps` | list of string | no | Extra OS-level packages installed into the generated image |

**`system_deps`** entries are installed by the generated Dockerfile as
OS-level packages. For Python packages, the special value `axiom` installs
the Axiom CLI binary into the image instead of an OS package.

For Python packages, `axiom validate` scans node source for `subprocess`
calls and emits a non-blocking warning when a node invokes a common tool
that is absent from the slim base image and not listed in
`build.system_deps` — without the entry, the tool would be missing from the
runtime image. The scan covers a fixed list of tools: `git`, `curl`,
`wget`, `ssh`, `rsync`, `zip`, `unzip`, `make`, `gcc`, `g++`, `svn`, `jq`.
A tool outside that list (such as `ffmpeg`) still needs a `system_deps`
entry to be present in the image — it just is not flagged by validation.

## Proto-only packages

A proto-only package publishes message types for other packages to import,
with no nodes and no running service. A package is treated as proto-only
when either:

- `type: proto-only` is set explicitly, or
- the `nodes` list is empty (proto-only is inferred).

For a proto-only package, the publish pipeline skips the Docker build and
the service deployment, and stores only the proto definitions and package
metadata. Other packages then pull its messages with `axiom import`. See
[the type system](/docs/concepts/type-system).

## Which commands read and write axiom.yaml

- **`axiom init <name>`** creates the manifest with `name`, `version`
  (default `0.1.0`), `language` (default `go`), and your `--description`.
  Unless `--no-example-comment` is passed, it appends a commented example
  `nodes:` block showing the `required_secrets` field.
- **`axiom create node <Name>`** appends a node entry (`name`,
  `description`, `input`, `output`, and `type: pipeline` for pipeline
  nodes) after scaffolding the source files.
- **`axiom import <package>[@version]`** adds or updates an `imports`
  entry, merging newly downloaded message names into the `messages` list.
- **`axiom validate`**, **`axiom build`**, **`axiom push`**, **`axiom
  dev`**, and **`axiom test`** read the manifest; they must be run inside a
  package directory (any directory below the one containing `axiom.yaml`).

When a CLI command rewrites the manifest, it re-serializes the whole file —
**inline comments you wrote in axiom.yaml are not preserved** by these
operations. Keep notes elsewhere if you need them to survive.

## Validation rules

`axiom validate` (also run as a gate by `axiom push`) checks the manifest
as its first layer. Failures block publishing unless marked as warnings:

| Check | Severity |
|---|---|
| `name` present and scoped (`handle/package-name`, lowercase/digits/hyphens) | error |
| `version` present and valid semver | error |
| `language` one of the six supported values | error |
| Every node's `input` and `output` resolves to a known message | error |
| Every import entry has a non-empty `messages` list | error |
| Imported message names exist in the downloaded proto files | warning |
| `secrets.Get("NAME")` calls covered by `required_secrets` | warning |
| Python `subprocess` tools covered by `build.system_deps` | warning |

Later validation layers check the proto files, node function signatures,
and node tests — see the
[axiom validate reference](/docs/reference/cli/axiom-validate).
