Publishing to XIOM

Everything a package needs to reach this registry: a five-minute quickstart, the trusted-publisher GitHub Action (recommended, no secrets), the manual token path, tags, and the troubleshooting table. No compiler internals required.

Live copy from GitHub, refreshed <time datetime="2026-11-07T20:21:32.258Z" title="2026-11-07 20:21 UTC">just now</time>. View the source on GitHub · community-publish.yml

This guide takes a package from "it exists in my repo" to "anyone can xiom pkg install it" with no prior registry knowledge. It is written for hobbyists and first-time release engineers: every command is copy-pasteable, and the troubleshooting table at the end maps the exact errors you may see.

The same content is rendered at https://registry.xiom-lang.org/publish (nav: Publish), which is also where the workflow template download lives.

  • New here? Go straight to the five-minute quickstart.
  • Already publish from CI with a token? See the manual token lane.
  • Something failed? Jump to troubleshooting.

0. What you need

You needWhy
The XIOM toolchain (xiom on your PATH)Builds, signs, and publishes packages
A GitHub accountSign-in and (recommended) the publish workflow
A package directory with package.xiThe manifest the registry reads
A README.md and a LICENSEShown on the package page; license is required by convention

Install the toolchain once:

# macOS / Linux
curl -fsSL https://xiom-lang.org/install.sh | sh

# Windows (PowerShell)
irm https://xiom-lang.org/install.ps1 | iex

Check it:

xiom pkg --help

A minimal package

Your repository should look like this (the registry cares about the files in the root, not any particular build system):

my-lib/
  package.xi          # the manifest (required)
  README.md           # what the package does (strongly recommended)
  LICENSE             # MIT OR Apache-2.0, for example
  src/...             # your code

A complete, minimal package.xi:

name: "my-lib"
version: "0.1.0"
description: "A small library that does one thing well"
license: "MIT OR Apache-2.0"
repository: "https://github.com/you/my-lib"
categories: ["tooling"]
keywords: ["example"]

[dependencies]

Rules the registry enforces:

  • name is lowercase letters, digits, ., -, _; . separates namespaces (my-ns.utils). Names under xiom.*, std.*, core.* are reserved.
  • version must be new for every publish. The registry answers 409 if it already exists; bump the version, never reuse one.
  • description is one sentence and shows up in search results.
  • repository should be the public URL of this repository.

Do not commit build output. The publish guard (next client patch) refuses build and artifact entries by default: directories target/, build/, dist/, out/, .git/, .toolchain/, .xiom_ai_cache/, node_modules/, and files *.exe, *.o, *.obj, *.a, *.lib, *.dll, *.so, *.dylib, *.wasm, *.pdb, .DS_Store, Thumbs.db. Extend the list with .xiomignore (gitignore syntax); entries explicitly declared in package.xi are kept. --force overrides for intentional cases. Module collection uses the same filter, and publishing from a clean checkout stays the habit.


This is the path most people should take. There is no token and no secret in your repository: GitHub proves the repo/workflow/ref to the registry using OpenID Connect (OIDC), and a maintainer approves that exact combination once.

Step 1 -- add the workflow

  1. In your repo, create .github/workflows/publish-registry.yml.
  2. Copy the template from <https://registry.xiom-lang.org/ui/templates/community-publish.yml> (or click Download the workflow template on the publishing page).
  3. Commit it.

Step 2 -- request access on the registry

  1. Open <https://registry.xiom-lang.org/login> and sign in with GitHub.
  2. Go to Requests (/account/requests).
  3. Choose Trusted publisher and fill in:
    • Repository: you/my-lib (exactly as on GitHub);
    • Workflow file: publish-registry.yml (the file name you committed);
    • Refs: refs/tags/v* if you release by tag (recommended), or

refs/heads/main if you publish from a branch;

  • Package names: the name from package.xi (e.g. my-lib).
  1. Submit. A maintainer reviews it, usually within a day; you get an in-app notice (and an email if you set one in Settings).

While the request is pending you can already tag releases; publishing simply starts working once the request is approved.

Step 3 -- tag a release

# make sure package.xi says version: "0.1.0"
git add package.xi
git commit -m "release: v0.1.0"
git tag v0.1.0
git push origin main
git push origin v0.1.0

Step 4 -- let the workflow publish

Uncomment the push: tags: ["v*"] trigger at the top of the workflow file (one time), or run Actions -> Publish to XIOM registry -> Run workflow manually. The workflow:

  1. checks the tag matches version: in package.xi;
  2. mints a short-lived OIDC token for this repo/workflow/ref;
  3. downloads the pinned toolchain, signs the package, and publishes;
  4. fails loudly (with a readable error) if anything is off.

Step 5 -- check your package page

Within a few seconds:

  • <https://registry.xiom-lang.org/packages/my-lib>
  • xiom pkg info my-lib
  • xiom pkg install my-lib

The page shows the version, size, SHA-256, signature, and the trust badges (see section 5).

Step 6 (optional) -- a stable signing key

Without a key secret, each CI run generates a fresh ephemeral key. The package is still signed, but the fingerprint changes every release. For a stable "verified" fingerprint:

xiom pkg keygen --out my-lib.key     # keep this file secret!

Add the 64-hex key as a repository secret named XIOM_SIGNING_KEY (Settings -> Secrets and variables -> Actions -> New repository secret). The template picks it up automatically. See section 6 for pinning the key in xiom pkg trust.


2. Versions, tags, and the workflow guard

  • Semantic versions: MAJOR.MINOR.PATCH, optionally -prerelease (e.g. 1.0.0-rc.1). The registry accepts any valid X.Y.Z-style version string; consumers use it for resolution.
  • Tags: v + the exact manifest version: v1.2.3, v1.0.0-rc.1. The template's guard compares github.ref_name (without the leading v) with version: in package.xi and stops the job on a mismatch, so a wrong tag can never publish the wrong version.
  • One version, one publish. Re-publishing an existing version returns 409 version_exists. Delete the tag, bump version:, commit, and tag again.
  • Pre-releases are published like any other version. Users opt in with xiom pkg install my-lib@1.0.0-rc.1.
  • Yanking retires a version without deleting it (section 7).

If you prefer branch-triggered releases, use refs/heads/main as the approved ref and keep the version bump + tag in the same push. The OIDC claims include the ref, so a workflow run from an unapproved ref is refused with publisher_not_mapped.

Changing or revoking a trusted publisher. Open Requests (/account/requests) and find Your grants. Each approved trusted publisher has Request a change (repository, workflow, refs, scopes) and Request revocation; each fulfilled token has Request rotation. Nothing changes until a maintainer approves the request from the queue, and the request history records who asked and who executed it. Revocation takes effect on approval: the entry stops matching and the next publish from that repo/workflow/ref is refused with publisher_not_mapped.


3. The manual token lane

Use a publish token when you publish from your own machine, from a CI system that is not GitHub Actions, or from a script. A token is scoped to specific package names and can be revoked by the maintainers at any time.

Compared to a trusted publisher, a token is a long-lived secret: it can publish only the package names it is scoped to, but you must store it safely. For GitHub Actions, prefer section 1.

Step 1 -- request a token

  1. Sign in at <https://registry.xiom-lang.org/login>.
  2. Open Requests (/account/requests), choose Publish token, and list the package names or namespaces you need (e.g. my-lib, my-ns; a namespace like my-ns covers my-ns.*).
  3. A maintainer approves it and the operator mints the token on the registry host; it reaches you privately (never through the registry web UI, issues, or chat). You get an in-app notice when it is fulfilled.

Step 2 -- store it

Name the secret whatever your CI supports (GitHub: XIOM_REGISTRY_TOKEN).

# local shell: export it for the session only
export XIOM_REGISTRY_TOKEN="<the token you received>"
export XIOM_REGISTRY="https://registry.xiom-lang.org"   # optional; this is the default

Windows PowerShell:

$env:XIOM_REGISTRY_TOKEN = "<the token you received>"

Step 3 -- sign and publish

xiom pkg keygen              # once per machine; writes ~/.xiom/keys/default.key
xiom pkg publish             # packs, signs, uploads the current directory

xiom pkg publish reads package.xi, packs the directory, signs with your key (or the ephemeral key), and sends the tarball with your token. On success it prints Successfully published <name>@<version>.

Useful variations:

xiom pkg publish --tarball dist/my-lib-0.1.0.tar.gz   # upload exact bytes
xiom pkg publish --compiler v0.64.0                   # record the toolchain tag

Build provenance

If your workflow generates GitHub build provenance with actions/attest-build-provenance, you do not need to send anything extra: after a trusted-publisher publish the registry asks GitHub for an attestation on the exact tarball digest and links it on the package page as build attestation when one exists. API publishers may also send an optional attestation form field with a canonical https://github.com/<owner>/<repo>/attestations/<id> URL; any other value is rejected. The link is display metadata: it never changes the artifact, its digest, or its signature.

Validate before publishing

POST /validate runs the exact publish checks without writing anything -- no artifact, no index entry, and your upload is discarded. It takes the same token and multipart fields as /publish and answers with the same status codes, so a dry run tells you precisely what a real publish would do:

curl -sS -X POST "$REGISTRY/validate" \
  -H "Authorization: Bearer $XIOM_REGISTRY_TOKEN" \
  -F name=my-lib -F version=0.1.1 -F package=@dist/my-lib-0.1.1.tar.gz

A clean payload answers 200 with the sha256, size, and any publish warnings; conflicts (409, the version exists), signature rules (422), scope denials (403), and malformed input (400) are reported exactly as /publish would. It is rate-limited like a publish and changes nothing.

Names and scopes

Every publish scope is checked before your request is queued, and again when a maintainer approves it:

  • Names are lowercase dot-separated segments (letters, digits, hyphens; dots separate namespaces).
  • The xiom, xiom.*, and xiom-* namespaces are reserved for official packages and cannot be requested from the form.
  • A name that differs from a published package only by dots vs hyphens (my-lib vs my.lib) is refused: readers and search treat them as the same, so it would be impossible to tell apart.
  • If a name is already published, only its listed maintainers can request publish rights over it: claim maintainership first (the package page has I maintain this package, a reviewer verifies it), then request. Everyone else is refused at the door.
  • Lookalike names (a typo away) and names mentioning "xiom" are allowed but flagged: the request carries the warning, the admin queue shows it, and the approver must explicitly acknowledge it before it can be approved.
  • Two grants may never hold the same name or namespace. An overlap with a live grant is shown at the door and blocks approval.

Token hygiene

  • One token per purpose; ask for the smallest scope that works.
  • Never commit it, never paste it into the registry UI, never put it in a workflow file.
  • If it leaks, contact <support@xiom-lang.org> immediately and the maintainers revoke it; ask for a replacement scoped the same way.

4. What the registry checks, and what the badges mean

Every publish is validated before anything is stored:

  1. Manifest -- name, version, description, and the tarball layout.
  2. Identity -- a valid token scoped to this name, or an OIDC token whose repository + workflow + ref match an approved trusted publisher.
  3. Signature -- a valid ed25519 signature over the artifact digest.
  4. Immutability -- a version that exists is never overwritten.

Badges on the package page and in listings:

BadgeMeaning
verifiedThe artifact is signed and verifies against the included public key
signedA signature is present (older/unsigned-key metadata)
trustedPublished through an approved GitHub OIDC trusted publisher
reviewedA registry reviewer looked at it and found nothing to flag
officialA first-party package maintained by the XIOM project
flaggedA reviewer marked a concern; read the review history
yankedA specific version is retired; pinned installs still resolve
mutedHidden from listings/search by the maintainers; the page, files, and /index.json entry still work

You do not need a reviewer to install or use a package: badges are extra signals. To request a review, use the Report form on the package page -- reports go to the reviewer queue and are never anonymous to maintainers.


5. Verify your release

After publishing, verify from a clean machine (a container or a CI job):

xiom pkg info my-lib                 # metadata + versions
xiom pkg install my-lib@0.1.0        # downloads and verifies the signature
xiom pkg verify artifact.tar.gz artifact.tar.gz.sig --key <public-key-hex>

If a package page shows the registry signing key, pin it so installs refuse anything signed by a different key:

xiom pkg trust --registry https://registry.xiom-lang.org --key <ed25519-public-hex>
xiom pkg trusted                     # list pinned keys

The raw protocol is always available if you want to integrate:

  • GET /index.json -- the whole registry index;
  • GET /packages/my-lib -- one package (JSON);
  • GET /packages/my-lib/0.1.0/package.tar.gz -- the artifact;
  • GET /packages/my-lib/0.1.0/package.tar.gz.sha256 -- the digest sidecar.

6. Yanking a version

Yanking marks a version retired: fresh installs skip it, but existing lockfiles and already-resolved dependencies keep working (the artifact is never deleted). Use it when you shipped a broken release; then publish a fixed version.

There is no xiom pkg yank subcommand yet -- use the API with a token scoped to the package:

curl -fsS -X POST \
  -H "Authorization: Bearer $XIOM_REGISTRY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason":"broken dependency in 0.1.0"}' \
  https://registry.xiom-lang.org/packages/my-lib/0.1.0/yank

With a trusted publisher you can also run the workflow template with mode=yank. Ask a maintainer if you do not have publish access anymore.


7. What maintainers do (so you know what to expect)

  • Approve requests: a maintainer reviews /admin/requests. A trusted publisher is activated the moment it is approved; a token request is queued for the operator to mint and deliver privately.
  • Review packages: anyone can report a package; reviewers resolve or dismiss reports with a note, and the history stays public on the page.
  • Moderate: flagging adds a public warning; muting hides a package from discovery without touching its files or the index protocol.
  • Everything is audited: approvals, decisions, role changes, and moderation are recorded with actor and timestamp.

Response time is usually within a day. If nothing happens for a few days, ping the community channels below.


8. Troubleshooting

What you seeWhat it meansFix
401 invalid_tokenThe token is unknown or was revokedRe-check the value; request a new token if it was rotated
401 no bearer tokenXIOM_REGISTRY_TOKEN is not set in this shell/jobExport it, or add the repo secret and reference it
401 oidc_token_expiredThe workflow minted one OIDC token and reused it past its ~6-minute lifetime (long loops over many packages)Re-mint per publish (or on a 401); the template publishes one package per job, so copy that pattern
403 scope_deniedThe token is not scoped to this package nameRequest a token that includes the name; namespaces cover ns.*
403 publisher_not_mappedThe OIDC claims do not match an approved entryCheck repo, workflow file name, and ref in your request
403 oidc_not_configuredThe registry has no OIDC publishers configuredUse the token lane and contact the maintainers
409 version_existsThis name@version is already publishedBump version: in package.xi; versions are immutable
409 publisher_existsThe same repo/workflow is already approvedCheck the existing entry; revoke it first if you are moving repos
413 payload_too_largeThe tarball exceeds the registry limitRemove build output and large fixtures from the repo
422 signature_requiredThe registry requires signed artifactsRun xiom pkg keygen before publishing, or set the signing secret
422 incomplete_signatureA signature and public key must be sent togetherPublish with the xiom client instead of hand-rolled requests
422 invalid_name / reserved_namespaceThe package name is invalid or reservedPick a lowercase name that is not under xiom.*, std.*, core.*
429 rate_limitedToo many requests in a short windowWait a minute and retry; do not loop the publish
Workflow fails at Check tag matchesTag and manifest version differBump version: in package.xi, commit, re-tag
Workflow fails at Mint registry tokenid-token: write is missingAdd permissions: id-token: write to the job
unknown field in package.xiTypo in the manifestCompare with the minimal example in section 0
Install says the package is yankedThe version was retiredInstall the newest version, or pin an older, working one

If the error is not here, open an issue at <https://github.com/xiom-lang/registry/issues> with the command you ran, the exact error text, and the package name.


9. Glossary

  • Artifact -- the .tar.gz the registry stores and serves for a version.
  • Digest / SHA-256 -- the hash of that artifact; used to verify downloads.
  • Signature -- an ed25519 signature over the digest, made with your key.
  • Index -- /index.json, the complete, immutable-to-consumers list of packages and versions. Clients read it; the web UI never changes its shape.
  • Trusted publisher -- an approved repo + workflow + ref combination that may publish via GitHub OIDC, with no stored secret.
  • Token -- a scoped bearer credential for the manual lane.
  • Yank -- retire a version without deleting it.
  • Mute -- maintainer action that hides a package from discovery only.

10. Getting help and contributing

  • Community chat: <https://discord.gg/fsxQfDUg9>
  • Registry issues and guide fixes: <https://github.com/xiom-lang/registry/issues>
  • Security reports: <support@xiom-lang.org> (please do not open a public issue)
  • Registry operations and the deploy runbook live in DEPLOY.md (maintainers).

The registry's own request flow is the only front door for tokens and trusted publishers. The old GitHub issue template for token requests is deprecated and removed: signed-in requests are audited, notifiable, and revocable, and they cannot leak a token into a public issue thread.

Something unclear or out of date? Open an issue or a pull request — contributions to this guide are welcome.