Public beta — not for production use. Data may be wiped at any time. Questions? Contact us.
Documentation menu

Import message types from another package

Find a published package with axiom search and axiom info, pull its message types into your package with axiom import, and use them as node inputs and outputs.

View as Markdown

Every node input and output is a message — a Protocol Buffers type. Messages do not have to be defined in your own package: axiom import downloads the .proto definitions from any published package so your nodes can consume or produce its types. This is how packages — even in different languages — share a contract: a flow can wire another package's node output straight into your node's input because both sides reference the same message type. See the type system for how edges type-check.

Prerequisites

  • An Axiom package directory (one containing axiom.yaml) — create one with axiom init, see write your first node.
  • Logged in via axiom login. axiom import requires it; axiom search and axiom info work without authentication.
  • For Go, Python, and TypeScript packages, axiom import finishes by running axiom generate, which uses the same proto tooling as the rest of the local loop (protoc + protoc-gen-go for Go; grpcio-tools or protoc for Python; protoc + protoc-gen-js + protoc-gen-ts for TypeScript). Rust, Java, and C# packages need no extra tooling — their protos compile during the language build.

The fast path

# Run inside your package directory (where axiom.yaml is).
axiom search --type messages TokensResult   # which package defines the type?
axiom info axiom-official/axiom-text-ops    # inspect its nodes and messages
axiom import axiom-official/axiom-text-ops  # download protos, update axiom.yaml, generate
axiom create node Analyze --input TokensResult --output AnalysisReport

After axiom import, the imported message types appear in axiom create node's message list and work as --input/--output exactly like messages defined in your own messages/messages.proto.

Search the marketplace

axiom search queries the marketplace; no login is needed.

axiom search                                # list recently published packages
axiom search text-ops                       # packages matching "text-ops"
axiom search --type nodes "tokenize"        # search nodes instead
axiom search --type messages TokensResult   # search messages instead

--type (shorthand -t) selects what to search: packages (the default), nodes, or messages. Package results list name, version, language, node count, and author. Node and message results include the package and version that publish them — so --type messages answers "which package do I import to get this type?"

Inspect a package before importing

axiom info shows the full details of a published package; no login is needed.

axiom info axiom-official/axiom-text-ops        # most recently published version
axiom info axiom-official/axiom-text-ops@0.1.0  # a specific version

When @version is omitted, the most recently published version is shown. The output includes the description, author, license, and deploy status; the package's live endpoint URL; links to its openapi.json and interactive API docs (see use the interactive API docs); every node with its input → output message types; and every message the package defines — the names you can use after importing.

A package can be proto-only: no nodes, just message types published for other packages to import. See nodes, packages, and flows.

Import the message types

# Run inside your package directory (where axiom.yaml is).
axiom import axiom-official/axiom-text-ops        # latest version
axiom import axiom-official/axiom-text-ops@0.1.0  # pinned version

axiom import requires a prior axiom login and must run inside a package directory. It does four things:

  1. Resolves the version (the most recently published one when @version is omitted) and downloads the package's .proto files. If this package already pins a different version of the same dependency in axiom.yaml, a bare axiom import <package> (no @version) is refused — nothing is downloaded or changed — so an existing pin never silently drifts to a newer "latest". To upgrade on purpose, pass the version explicitly (axiom import <package>@<version>); to keep the current pin, re-run with that exact version.
  2. Extracts them to imports/<package>/<version>/. A scoped name's / becomes - in the directory name, so axiom-official/axiom-text-ops@0.1.0 lands in imports/axiom-official-axiom-text-ops/0.1.0/.
  3. Records the dependency in axiom.yaml under imports: with the package, version, and imported message names.
  4. Runs axiom generate so the language bindings appear under gen/ immediately.

If an imported message has the same name as one in your local messages/, the import fails with a collision error before writing anything — rename one of the two first.

Commit imports/ and the updated axiom.yaml to your repository: axiom push builds your package from your git remote's HEAD commit, not your working tree, so unpushed import files would be missing from the build.

What axiom import writes

After importing, axiom.yaml carries the pinned dependency:

# axiom.yaml (excerpt written by axiom import)
imports:
    - package: axiom-official/axiom-text-ops
      version: 0.1.0
      messages:
        - TextRequest
        - TokensResult

and the raw proto definitions live in your repository. The registry names each downloaded file after the first message it defines (snake_case), not after the upstream file name — here the package's TextRequest and TokensResult share one proto file, downloaded as text_request.proto:

imports/
└── axiom-official-axiom-text-ops/
    └── 0.1.0/
        └── text_request.proto

Each entry pins an exact version. Re-running the same import is safe — it merges any newly published message names into the existing entry. Importing a different version of the same package adds a second entry and a second directory side by side; nothing is upgraded implicitly.

Where the generated bindings go

  • Gogen/imports/<package>/<version>/ (Go protobuf bindings).
  • Python — one module per imported package, named after it: gen/axiom_official_axiom_text_ops_messages_pb2.py for the example above.
  • TypeScriptgen/imports/<package>/<version>/, one <file>_pb.js plus a <file>_pb.d.ts type surface per downloaded proto file: text_request_pb.js and text_request_pb.d.ts for the example above.
  • Rust, Java, C# — nothing at import time; the protos compile during the language build (build.rs/tonic-build for Rust, the protobuf-maven-plugin for Java, Grpc.Tools for C#).

Use an imported message in a node signature

Reference imported messages by their simple name, exactly like local ones. axiom create node lists them in its interactive message picker alongside local messages, accepts them for --input/--output, and prints a note about their origin:

# Run inside your package directory, after axiom import.
axiom create node Analyze --input TokensResult --output AnalysisReport
# Note: TokensResult is from imported package "axiom-official-axiom-text-ops@0.1.0"

The node's entry in axiom.yaml uses the simple name (input: TokensResult); Axiom resolves which package it comes from via the imports: section. For Go, Python, Java, C#, and Rust, the scaffolded node file already contains the correct import statement. For TypeScript, the scaffold always imports from ../gen/messages_pb (the local messages module) — edit that line to point at the imported module path shown below.

A complete Go example

A Go node whose input type comes from the imported package and whose output type is local. Replace axiom-analytics with the module path from your package's go.mod:

// nodes/analyze.go — input imported from axiom-official/axiom-text-ops
package nodes

import (
	"context"
	"fmt"
	"strings"

	"axiom-analytics/axiom"
	gen "axiom-analytics/gen"
	axiomtextops "axiom-analytics/gen/imports/axiom-official-axiom-text-ops/0.1.0"
)

// Analyze accepts tokenized text and produces a summary report with the
// token count and a human-readable listing of the tokens.
func Analyze(ctx context.Context, ax axiom.Context, input *axiomtextops.TokensResult) (*gen.AnalysisReport, error) {
	return &gen.AnalysisReport{
		Summary:   fmt.Sprintf("Processed %d tokens: %s", input.GetCount(), strings.Join(input.GetTokens(), ", ")),
		WordCount: input.GetCount(),
	}, nil
}

Import statements per language

For a message TokensResult imported from axiom-official/axiom-text-ops@0.1.0:

# nodes/analyze.py — Python: per-package module under gen/
from gen.axiom_official_axiom_text_ops_messages_pb2 import TokensResult
// nodes/analyze.ts — TypeScript: module under gen/imports/, named after the proto file
import { TokensResult } from '../gen/imports/axiom-official-axiom-text-ops/0.1.0/text_request_pb';
// nodes/analyze.rs — Rust: imported messages compile into the same module as local ones
use crate::gen::messages::TokensResult;
// nodes/Analyze.java — Java: fully qualified type in the imports.* namespace
import imports.axiom_official.axiom_text_ops.TokensResult;
// nodes/analyze.cs — C#: bring in the imports.* namespace, then use the simple name
using imports.axiom_official.axiom_text_ops;

Java and C# derive the namespace from the package name: / becomes . and - becomes _, so axiom-official/axiom-text-ops becomes imports.axiom_official.axiom_text_ops. Local messages stay where they always are: gen.Messages.<Name> in Java, the Gen namespace in C#.

Embed an imported message as a field of your own message

An imported message doesn't have to be a node's whole input or output — you can reference it as a field inside a message you declare locally, just like a same-file field reference. Two things go in your messages/messages.proto:

  1. an import of the imported package's stable flat proto filename, and
  2. the field written with the imported message's proto-qualified name.

The stable filename

axiom generate exposes every imported package under one deterministic filename, independent of the pinned version's on-disk path:

<handle>_<pkg>_messages.proto

— the package name, lowercased with every / and - turned into _, plus _messages.proto. So axiom-official/axiom-text-ops is importable as axiom_official_axiom_text_ops_messages.proto — the same stem as its Python module (axiom_official_axiom_text_ops_messages_pb2). The name is stable: it never encodes the version, so axiom import <pkg>@<newer> never forces you to edit an embedding site.

The proto-qualified field type

Write the field type as the imported message's fully-qualified proto name — the package …; line from the downloaded file under imports/… followed by the message name. A package whose proto declares package acmepay.v1; with a PaymentInfo message is referenced as acmepay.v1.PaymentInfo.

Worked example

Given axiom import acme/pay@1.2.0, whose proto declares package acmepay.v1; and a PaymentInfo message, embed it as a field of a local CheckoutRequest:

// messages/messages.proto
syntax = "proto3";
package mypkg;

import "acme_pay_messages.proto";        // the stable flat filename

message CheckoutRequest {
  string order_id = 1;
  acmepay.v1.PaymentInfo payment = 2;    // proto-qualified imported type
}

axiom generate resolves the import and emits a real cross-package reference: the generated Go gen/messages.pb.go imports the already-compiled …/gen/imports/acme-pay/1.2.0 binding, so payment is the one canonical type published by acme/pay — never a duplicated local copy. This is the point of embedding rather than re-declaring a structurally-identical local message: both sides of a flow edge stay the same protobuf type.

Language support. The embedded-field pattern is fully wired for Go and Python — the generated code references the canonical imported binding. For TypeScript, Rust, Java, and C#, axiom generate (or the language build) resolves the proto symbol so compilation succeeds, but emitting a correct cross-module reference for an embedded field is not yet wired. For those languages, prefer the whole-input/output import form above, or verify the generated binding before you publish.

Troubleshooting

  • "message X is listed in axiom.yaml imports but its proto definition is not downloaded"axiom.yaml declares the import but the files under imports/ are missing (for example, a fresh checkout where imports/ was never committed). Run the axiom import <package>@<version> command the error message prints.
  • "import collision: message X exists in both your local messages/ and the imported package" — two messages with the same name cannot coexist. Rename your local message (or import a package that doesn't clash), then re-run the import.
  • "axiom.yaml not found — run this command from an Axiom package directory"axiom import only works inside a package directory; cd into the directory created by axiom init first.
  • "Not logged in" — run axiom login. Search and info work logged out; import does not.

Next steps