When to Publish to npm
Publish to npm when:
- Multiple projects need the same utility code
- You want to share tools with the community
- You're building a framework or library
Don't publish for app-specific code—use a monorepo instead.
Package Setup
// package.json
{
"name": "@yourscope/my-utils",
"version": "1.0.0",
"description": "Utility functions for TypeScript projects",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"prepublishOnly": "npm run build && npm test"
},
"publishConfig": {
"access": "public"
}
}
Build with tsup
npm install -D tsup typescript
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
clean: true,
sourcemap: true,
minify: false, // let consumers minify
});
Semantic Versioning
MAJOR.MINOR.PATCH
1.0.0 — initial stable release
1.0.1 — patch: bug fix, no API changes
1.1.0 — minor: new feature, backwards compatible
2.0.0 — major: breaking change
Pre-release:
2.0.0-alpha.1 — alpha
2.0.0-beta.1 — beta
2.0.0-rc.1 — release candidate
npm version patch # 1.0.0 → 1.0.1
npm version minor # 1.0.1 → 1.1.0
npm version major # 1.1.0 → 2.0.0
npm version prerelease --preid=beta # → 2.0.0-beta.0
npm version automatically creates a git tag and commits the version bump.
Changesets (Team Workflow)
For packages with multiple contributors:
npm install -D @changesets/cli
npx changeset init
# Developer adds a changeset when making a change
npx changeset
# Select: patch/minor/major
# Write: what changed
# Creates a .changeset/random-name.md file
# Release: consume changesets, bump version, update CHANGELOG
npx changeset version
git commit -am "Version packages"
npm publish
GitHub Actions Auto-Publish
# .github/workflows/release.yml
name: Release
on:
push:
tags: ['v*']
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- run: npm test
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# Trigger release
git tag v1.1.0
git push origin v1.1.0
# GitHub Actions publishes automatically
Scoped Packages
# Scoped to your npm username or organization
@yourname/package-name
@yourorg/package-name
# Public scoped package (free)
npm publish --access public
# Private scoped package (requires npm paid plan or private registry)
npm publish --access restricted
Scoped packages prevent naming conflicts and group related packages.
What to Include/Exclude
# .npmignore or "files" in package.json
INCLUDE:
dist/ ← compiled output
README.md ← documentation
LICENSE ← required for open source
EXCLUDE:
src/ ← source TypeScript (unless providing dual)
test/ ← tests
.github/ ← CI config
node_modules/ ← always excluded automatically
.env ← NEVER publish this
Testing Before Publish
# Dry run — see what would be published
npm publish --dry-run
# Check package contents
npm pack
tar -tzf *.tgz
# Test locally before publishing
npm link
cd /path/to/other-project
npm link @yourscope/my-utils
The files field in package.json is the most important thing to get right—too broad and you ship source and tests; too narrow and consumers can't import your package.
Need to automate your developer tooling? Whoff Agents Ship Fast Skill Pack includes npm package templates, GitHub Actions workflows, and release automation.
Top comments (0)