> ## Documentation Index
> Fetch the complete documentation index at: https://docs.talosjs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Publish to npm

> Version, tag, and publish the workspace's packages and modules to the npm registry — driven end to end by the Talos CLI, with already-published versions skipped so every run is safe to repeat.

Where [Deploy an API](/deployment/api) ships a container, publishing to npm ships **libraries**: the packages under `packages/` and the modules under `modules/` that other projects install. Talos drives the whole flow from the CLI — save a token once, then let [`npm:publish`](/cli/commands/npm-publish) pack each target, resolve its workspace dependencies to real version ranges, and push it to the registry. Versions already on npm are skipped, so re-running never double-publishes.

The guiding principle is **version in git, publish from git**. [`release:create`](/cli/commands/release-create) bumps versions, writes changelogs, and tags from your conventional commits; `npm:publish` takes those tagged versions to the registry. The two compose into a single command when you pass `--publish`.

## What gets published

Every directory under `packages/` and `modules/` is a candidate. Each must carry a `package.json` whose `name` and `version` define the published artifact.

| Source   | Location           | Typical use                                                              |
| -------- | ------------------ | ------------------------------------------------------------------------ |
| Packages | `packages/<name>/` | Framework-agnostic libraries (`@talosjs/logger`, `@talosjs/command`, …). |
| Modules  | `modules/<name>/`  | Business-domain modules published for reuse across services.             |

<Note>
  `npm:publish` publishes the package's `dist/` **as-is**. Build your artifacts
  (so `dist/` is current) before publishing — the command packs what is on
  disk; it does not run your build.
</Note>

## Step 1 — Save an npm token

Publishing authenticates with an npm **Granular Access Token**. Save it once with [`npm:credentials:create`](/cli/commands/npm-credentials-create); it is written as block-style YAML to `~/.talos/credentials/npm.yml` under the `default` profile and reused by every publish.

```bash theme={null}
talos npm:credentials:create
```

You are prompted for the token (input hidden), or pass it non-interactively for CI:

```bash theme={null}
talos npm:credentials:create --token=npm_xxxxxxxxxxxxxxxx --silent
```

<Tip>
  Create the token at
  [npmjs.com → Granular Access Tokens](https://www.npmjs.com/settings/~/tokens/granular-access-tokens/new).
  Scope it with **read and write** access to exactly the packages (or scope)
  you publish, and give it the shortest expiry your release cadence allows.
</Tip>

<Warning>
  The token is stored in plaintext under `~/.talos/credentials/`. Keep that
  directory off shared machines and out of any backup that leaves your control.
  In CI, inject the token via `--token` from a secret store rather than
  committing `npm.yml`.
</Warning>

## Step 2 — Publish

With a token saved, publish everything in the workspace:

```bash theme={null}
talos npm:publish
```

Or target specific packages and modules by name (comma-separated):

```bash theme={null}
talos npm:publish --packages=cli,logger
talos npm:publish --modules=billing,catalog
```

For a scoped package that should be private, publish with restricted access:

```bash theme={null}
talos npm:publish --packages=cli --access=restricted
```

### Options

| Option       | Description                                                   | Default                  |
| ------------ | ------------------------------------------------------------- | ------------------------ |
| `--packages` | Comma-separated package names to publish (under `packages/`). | All packages and modules |
| `--modules`  | Comma-separated module names to publish (under `modules/`).   | All packages and modules |
| `--access`   | npm access level: `public` or `restricted`.                   | `public`                 |
| `--silent`   | Suppress log output and the publishing spinner (use in CI).   | `false`                  |

### What the command does per target

For each resolved target, `npm:publish`:

1. **Checks the registry** for the target's `name@version`. If that version already exists, it is reported as *ignored* and skipped — this is what makes re-runs safe.
2. **Packs with `bun pm pack`** into `dist/`, so `workspace:*` dependencies resolve to real published version ranges instead of workspace protocols.
3. **Extracts** the tarball into `dist/publish`, stripping npm's `package/` prefix so the resolved `package.json` lands at the root.
4. **Publishes with `npm publish --access <access>`** from `dist/publish`, authenticating with your saved token. (`npm`, not `bun publish`, is used deliberately — it avoids the interactive web-OTP flow.)
5. **Cleans up** the tarball and `dist/publish` afterward, so nothing leaks into the next packed artifact.

At the end it prints a summary — `N published, M ignored`.

<Note>
  Because publishing packs with `bun` but publishes with `npm`, both must be on
  `PATH`. A version whose `name@version` is already on the registry is never
  re-published, so bumping the version (Step 3) is what makes a new publish
  happen.
</Note>

## Step 3 — Version and publish together

You rarely publish a hand-edited version. Instead let [`release:create`](/cli/commands/release-create) derive the next version from your conventional commits — a breaking change bumps major, a `feat` bumps minor, anything else patches — then write the changelog, commit, and tag. Passing `--publish` hands the freshly released targets straight to `npm:publish`:

```bash theme={null}
talos release:create --publish
```

This single command:

1. Verifies the working tree is clean (aborts otherwise).
2. For each package/module with unreleased commits, bumps its version, updates `CHANGELOG.md`, commits, and creates an annotated git tag.
3. Prompts to push commits and tags to the remote (also refreshes and commits `bun.lock`).
4. Publishes **only the targets it released this run** to npm.

Scope a release to specific targets the same way, and still publish:

```bash theme={null}
talos release:create --packages=cli,logger --publish
```

<Tip>
  Keep `release:create` as the front door for versioning and reach for bare
  `npm:publish` only to **retry** a publish that failed after the version was
  already tagged — the registry check means the already-published siblings are
  simply skipped.
</Tip>

## Publish from CI

The same two commands run unattended. Provide the token from a secret and add
`--silent` to keep logs clean:

<Tabs>
  <Tab title="GitHub Actions">
    ```yaml .github/workflows/publish.yml theme={null}
    name: publish
    on:
      push:
        tags: ["*@*"]        # matches release:create tags, e.g. @talosjs/cli@1.2.0
    jobs:
      publish:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: oven-sh/setup-bun@v2
          - run: bun install
          - run: bun run build
          - run: bunx talos npm:credentials:create --token="$NPM_TOKEN" --silent
            env:
              NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          - run: bunx talos npm:publish --silent
    ```
  </Tab>

  <Tab title="Generic CI">
    ```bash theme={null}
    bun install
    bun run build

    talos npm:credentials:create --token="$NPM_TOKEN" --silent
    talos npm:publish --silent
    ```

    `$NPM_TOKEN` comes from your CI secret store; never commit `~/.talos/credentials/npm.yml`.
  </Tab>
</Tabs>

<Warning>
  Give the CI token write access only to the packages you publish, and prefer a
  short expiry. Because the registry check skips already-published versions, a
  re-triggered pipeline is idempotent — but a leaked long-lived token is not.
</Warning>

## Checklist

* Save the npm token once with `npm:credentials:create` (or inject it via `--token` in CI).
* **Build** so each target's `dist/` is current before publishing.
* Let `release:create` derive versions from conventional commits — don't hand-edit them.
* Use `release:create --publish` for the normal flow; reserve bare `npm:publish` for retries.
* Pass `--access=restricted` for private scoped packages.
* In CI, add `--silent` and pull the token from a secret store — never commit `npm.yml`.

## Related

* [npm:publish](/cli/commands/npm-publish) — the publish command and all its options.
* [npm:credentials:create](/cli/commands/npm-credentials-create) — save the Granular Access Token.
* [release:create](/cli/commands/release-create) — version, changelog, and tag from conventional commits.
* [Deploy an API](/deployment/api) — ship the app module as a container instead of a library.
* [Monorepo](/getting-started/monorepo) — how packages and modules are laid out.
