Publish to npm, PyPI, and crates.io with OIDC
Set up trusted package publishing for TypeScript, Python, and Rust. Check package contents, restrict release authority, and verify the published result.
Publish a reviewed package from a narrowly trusted release workflow instead of storing a reusable upload token in CI. The setup differs for npm, PyPI, and crates.io. This guide covers each registry, the package checks before upload, and the evidence to keep afterward. It applies to any package the team releases; a generated artifact still needs the same review and consumer test.
What should you check before enabling publishing?
Keep a release record with the package name, version, source commit, expected files, registry, workflow filename, and approving maintainer. Confirm that you control the package name. Check license metadata, README examples, exports, and supported runtimes. Exclude credentials, test customer data, and local configuration from the package archive.
Build and test with no publishing authority first. Then give only the protected release job id-token: write. An OIDC trust relationship lets a matching workflow obtain publishing authority. It does not establish that the code or its dependencies are safe. Protect that workflow and the release environment from unauthorized edits.
How do you publish a TypeScript package to npm?
Run the project's tests and build. Use npm pack --dry-run to inspect the file list, then npm pack to create the archive. Install that archive in a clean consumer project. Check its JavaScript entry points and TypeScript declarations. npm pack documents the packaging commands.
For a new package, npm requires an initial publication before configuring trust. Complete an approved interactive release using the owner's npm account and required 2FA. For a public scoped package, use npm publish --access public. Confirm the registry entry before proceeding.
For an existing package, add a trusted publisher in its npm settings. Match the GitHub owner, repository, workflow filename, and optional environment. The filename is release.yml, not the full directory path. Set repository.url in package.json to the correct repository. A newly created npm trusted-publisher configuration expires unless its first successful publish occurs within two days. Schedule the approved release in that window. If the window expires, recreate the configuration; do not add a permanent token merely to bypass the expiry.
Use a supported cloud runner, Node.js 22.14.0 or later, and npm 11.5.1 or later. Give the publish job id-token: write. Current npm settings distinguish staged publishing from direct publishing. Allow npm publish explicitly if that is the intended operation; otherwise use the staged flow and maintainer approval.
Run npm publish for an approved direct release. A public scoped package needs the appropriate public access setting. Do not use npm whoami as an OIDC permission test. Remove obsolete publishing tokens after a successful migration. With GitHub Actions, automatic provenance requires a public repository and public package. Private dependencies can still need separate read-only installation credentials. npm's trusted-publisher documentation lists current requirements and troubleshooting.
How do you publish a Python package to PyPI?
Create the distribution files from reviewed source:
python -m pip install build twine
python -m build
python -m twine check dist/*
Use locked, reviewed tool versions in CI. Install the resulting wheel in a clean virtual environment, then run an import and a small caller test. Check the source distribution too. The distribution name and Python import name can differ; inspect pyproject.toml instead of assuming they match. See the Python packaging guide.
Configure an existing PyPI project's trusted publisher, or a pending publisher for a new project. Match the repository owner, repository, workflow filename, and environment. Set id-token: write on the release job. Retrieve the already-tested distributions into dist/, then use this publish step without a password:
- name: Upload approved distributions
uses: pypa/gh-action-pypi-publish@release/v1
For a rehearsal, configure a separate TestPyPI publisher and use its upload URL. Production and TestPyPI are separate targets. A successful TestPyPI upload does not prove the production trust configuration. The PyPI publishing guide supplies the registry-specific setup. Review and pin the action to a verified commit in the production workflow.
How do you publish a Rust crate to crates.io?
Run cargo test, inspect cargo package --list, and run cargo publish --dry-run. The dry run performs publishing checks without uploading. Confirm the selected crate and registry, especially in a workspace. Do not bypass the build check with --no-verify merely to make a release succeed. See Cargo's publish command.
The crate must already exist on crates.io; its initial publication still requires an API token. An owner can then add a trusted publisher in the crate settings. Match the repository owner, repository, workflow filename, and optional environment. Give the GitHub release job id-token: write, check out the reviewed commit, and install the tested Rust toolchain before these steps:
- uses: rust-lang/crates-io-auth-action@v1
id: registry-auth
- run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ steps.registry-auth.outputs.token }}
The official authentication action obtains a temporary token and revokes it after the job. Pin reviewed action revisions for production. The crates.io team's GitHub Actions example shows the publishing flow. Check the current crate settings for the exact publisher fields before the approved release. After migration, consider its trusted-publishing-only setting. The registry blocks pull_request_target and workflow_run triggers for this path, as described in the Rust development update.
What proves that the release worked?
An upload command returning success is one piece of evidence. Check the registry page for the exact name and version. Install the published version in a clean consumer and run the same smoke test used for the local archive. Retain the workflow run, source commit, artifact digest, approval, and any available provenance.
| Failure | First check | Do not do this |
|---|---|---|
| OIDC authentication denied | Exact repository, workflow, environment, job permission | Add a broad permanent token as an unexplained fallback |
| Package missing files | Archive contents and build output | Publish repeatedly without checking the consumer install |
| Version already exists | Registry and prior release run | Change a version just to hide an uncertain release |
| Wrong source revision | Tag, checkout, artifact source, approval | Assume a signed identity means the artifact is correct |
Do not run a production upload to test whether a configuration is syntactically valid. Use a deliberate release with the package owner's approval. Short-lived credentials reduce exposure duration; a compromised release job can still misuse its current authority. Keep package publishing separate from untrusted pull-request execution and review changes to the trust configuration.
Know which dependency risks need a fix
Check affected versions, the build and deployment evidence. Use the update checklist, or discuss the dependency risks in your application review.
Sources
- npm trusted publishersAccessed
- npm packAccessed
- Python packaging guideAccessed
- PyPI trusted publishingAccessed
- Cargo publishAccessed
- crates.io trusted publishing announcement and GitHub Actions exampleAccessed
- crates.io authentication actionAccessed
- Rust registry development updateAccessed
- npm trust prerequisitesAccessed