nodejs/node · error · Error

You must specify a tag using --tag when publishing a prerele

Error message

You must specify a tag using --tag when publishing a prerelease version.

What it means

Thrown by `npm publish` when publishing a version with a SemVer prerelease tag (e.g. `1.0.0-beta.1`) while the `tag` config is still at its default (`latest`) and `--force` is not set. npm refuses to silently point `latest` at a prerelease, since that would push unstable code to users running `npm install <pkg>`.

Source

Thrown at deps/npm/lib/commands/publish.js:131

      foregroundScripts: this.npm.config.isDefault('foreground-scripts')
        ? true
        : this.npm.config.get('foreground-scripts'),
      dryRun: true,
      prefix: this.npm.localPrefix,
      workspaces: this.workspacePaths,
    })
    const pkgContents = await getContents(manifest, tarballData)
    const logPkg = () => logTar(pkgContents, { unicode, json, key: workspace })

    // The purpose of re-reading the manifest is in case it changed, so that we send the latest and greatest thing to the registry note that publishConfig might have changed as well!
    manifest = await this.#getManifest(spec, opts, true)
    const force = this.npm.config.get('force')
    const isDefaultTag = this.npm.config.isDefault('tag') && !manifest.publishConfig?.tag

    if (!force) {
      const isPreRelease = Boolean(semver.parse(manifest.version).prerelease.length)
      if (isPreRelease && isDefaultTag) {
        throw new Error('You must specify a tag using --tag when publishing a prerelease version.')
      }
    }

    // If we are not in JSON mode then we show the user the contents of the tarball before it is published so they can see it while their otp is pending
    if (!json) {
      logPkg()
    }

    const resolved = npa.resolve(manifest.name, manifest.version)

    // make sure tag is valid, this will throw if invalid
    npa(`${manifest.name}@${defaultTag}`)

    const registry = npmFetch.pickRegistry(resolved, opts)

    await oidc({ packageName: manifest.name, registry, opts, config: this.npm.config })

    const creds = this.npm.config.getCredentialsByURI(registry)

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Pass an explicit dist-tag, e.g. `npm publish --tag beta` (or `next`, `rc`) so the prerelease does not claim the `latest` tag.
  2. Set `"publishConfig": { "tag": "beta" }` in package.json so the default tag for this package is non-latest.
  3. Override with `--force` only if you intentionally want the prerelease to become `latest`.
  4. Strip the prerelease suffix from the version (publish `1.0.0` instead of `1.0.0-beta.1`).

Example fix

// before
npm publish   // version is 1.0.0-beta.1
// after
npm publish --tag beta
Defensive patterns

Strategy: validation

Validate before calling

const semver = require('semver')
function distTagFor(version, publishConfigTag, cliTag) {
  const isPre = (semver.parse(version)?.prerelease.length ?? 0) > 0
  const tag = cliTag || publishConfigTag || 'latest'
  if (isPre && tag === 'latest') {
    throw new Error(`Publishing prerelease ${version} requires --tag (or publishConfig.tag). Suggested: --tag beta`)
  }
  return tag
}
// before publish:
distTagFor(pkg.version, pkg.publishConfig?.tag, process.env.NPM_CONFIG_TAG)

Prevention

When it happens

Trigger: Running `npm publish` (or `npm stage`) where `semver.parse(manifest.version).prerelease.length > 0`, `this.npm.config.isDefault('tag')` is true, `manifest.publishConfig?.tag` is unset, and `--force` is not passed.

Common situations: Releasing a beta/rc/alpha from the same branch used for stable releases; CI pipelines that version packages with prerelease suffixes but do not pass `--tag`; forgetting that `publishConfig.tag` in package.json also satisfies the requirement.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/e9caaebda5c06df8. Report an issue: GitHub.