Publishing an Angular Component Library to NPM: Step-by-Step Guide 2025

Expert NX Monorepo for Angular Design Systems: Enterprise Guide 2025 Figure 1: The complete pipeline from Angular source code to a published NPM package
Why Publish Your Angular Library?
Sharing reusable UI components across projects is one of the highest-leverage investments a design system team can make. A published NPM library means one source of truth, versioned releases, and no copy-paste drift. In 2025, the Angular ecosystem makes this easier than ever — but there are still pitfalls that trip up even experienced developers.
This guide walks you through every step: generating the library scaffold, configuring the Angular Package Format, setting up semantic versioning, and automating releases with GitHub Actions.
Step 1: Generate the Library Scaffold
Start inside an existing Angular workspace (or create one with ng new my-workspace --no-create-application):
ng generate library ui-components --prefix ds
This creates:
projects/ui-components/src/lib/— your component sourceprojects/ui-components/src/public-api.ts— the barrel file that controls your public APIprojects/ui-components/ng-package.json— ng-packagr configurationprojects/ui-components/package.json— the library's own package.json
The Public API Barrel File
Everything you want consumers to use must be exported from public-api.ts:
// projects/ui-components/src/public-api.ts
export * from './lib/button/button.component';
export * from './lib/button/button.module';
export * from './lib/card/card.component';
export * from './lib/tokens/design-tokens';
If a symbol isn't listed here, it won't be accessible after publishing — even if TypeScript can see it during development.
Step 2: Configure ng-package.json
The ng-package.json file tells ng-packagr how to build your library:
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/public-api.ts"
},
"dest": "../../dist/ui-components"
}
Adding Secondary Entrypoints
Secondary entrypoints let consumers import subsets of your library, improving tree-shaking:
projects/ui-components/
src/
public-api.ts ← primary entrypoint
testing/
src/
public-api.ts ← secondary: @my-org/ui-components/testing
ng-package.json
icons/
src/
public-api.ts ← secondary: @my-org/ui-components/icons
ng-package.json
Each secondary entrypoint has its own minimal ng-package.json:
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "src/public-api.ts"
}
}
Step 3: Configure the Library's package.json
This is where most publish failures originate. The library's package.json (in projects/ui-components/) needs careful attention:
{
"name": "@my-org/ui-components",
"version": "1.0.0",
"peerDependencies": {
"@angular/common": ">=17.0.0 <20.0.0",
"@angular/core": ">=17.0.0 <20.0.0",
"rxjs": ">=7.4.0 <8.0.0"
},
"sideEffects": false,
"exports": {
".": {
"import": "./fesm2022/my-org-ui-components.mjs",
"default": "./fesm2022/my-org-ui-components.mjs"
},
"./testing": {
"import": "./testing/fesm2022/my-org-ui-components-testing.mjs",
"default": "./testing/fesm2022/my-org-ui-components-testing.mjs"
}
}
}
Key points:
peerDependencies— never list Angular as adependency. Dual Angular instances cause runtime errors.sideEffects: false— tells bundlers the library is fully tree-shakeable.exportsfield — required for Node.js 12+ module resolution and Angular's package format.
Step 4: Build for Production
Always build before publishing:
ng build ui-components --configuration production
The output in dist/ui-components/ contains:
fesm2022/— FESM2022 bundle (single flat file, optimal for modern toolchains)esm2022/— individual ESM files (used for better tree-shaking in some bundlers)*.d.ts— TypeScript declarations*.d.ts.map— source maps for declarations
Verify the build output looks correct before proceeding:
ls dist/ui-components/
# Should show: fesm2022/, esm2022/, package.json, README.md, *.d.ts
Step 5: Set Up Semantic Versioning
Follow semver religiously — consumers depend on it for safe upgrades:
# Bug fix: 1.0.0 → 1.0.1
npm version patch --workspace=projects/ui-components
# New backward-compatible feature: 1.0.1 → 1.1.0
npm version minor --workspace=projects/ui-components
# Breaking change: 1.1.0 → 2.0.0
npm version major --workspace=projects/ui-components
Document breaking changes in a CHANGELOG.md using Keep a Changelog format.
Step 6: Create .npmignore
Prevent development files from bloating the published package:
# .npmignore (in projects/ui-components/)
*.spec.ts
*.spec.js
karma.conf.js
tsconfig.spec.json
src/
!dist/
jest.config.ts
Step 7: Publish to NPM
First, do a dry run to catch issues:
cd dist/ui-components
npm publish --dry-run --access public
Review the output — it shows exactly what files will be published and their sizes. Once satisfied:
npm publish --access public
For scoped packages under an organization, --access public is required or it defaults to private (which requires a paid NPM plan).
Step 8: Automate with GitHub Actions
Manual publishing is error-prone. Automate it with a tag-triggered workflow:
# .github/workflows/publish-library.yml
name: Publish Angular Library to NPM
on:
push:
tags:
- 'v*.*.*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Run tests
run: ng test ui-components --watch=false --browsers=ChromeHeadless
- name: Build library
run: ng build ui-components --configuration production
- name: Publish to NPM
run: npm publish --access public
working-directory: dist/ui-components
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
To trigger a release:
npm version minor --workspace=projects/ui-components
git push --follow-tags
The --follow-tags flag pushes both the commit and the tag, triggering the workflow.
Pre-Publish Checklist
| Check | Why It Matters |
|---|---|
All exports in public-api.ts |
Missing exports break consumer imports silently |
| Angular peer dependency range is broad | Prevents forcing consumers to upgrade Angular |
sideEffects: false set |
Enables tree-shaking in consumer apps |
exports field in package.json |
Required for sub-path imports and modern resolution |
| Dry-run passes with expected files | Catches .npmignore misconfigurations |
| Version number bumped | NPM rejects publishing the same version twice |
| CHANGELOG updated | Consumers need to understand what changed |
| Tests pass on CI | Prevents publishing broken code |
Common Pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
| Publishing from workspace root | Wrong files published | cd dist/ui-components && npm publish |
Angular as dependency |
Duplicate Angular instances at runtime | Move to peerDependencies |
Missing public-api.ts exports |
Cannot find module errors in consumers |
Audit and add all public symbols |
Missing exports field |
Sub-path imports fail with ERR_PACKAGE_PATH_NOT_EXPORTED |
Add exports map to package.json |
| No dry-run before publish | Hidden files or missing files published | Always npm publish --dry-run first |
| Not bumping version | npm publish fails with 403 |
npm version patch/minor/major |
| Global styles not in CSS bundle | Consumer styles missing | Add styleUrls and configure ng-packagr to process SCSS |
No --access public on scoped package |
Defaults to private, fails without paid plan | Always pass --access public for OSS |
Consuming Your Published Library
Once published, consumers install and use it like any Angular package:
npm install @my-org/ui-components
In their app.module.ts or standalone component:
import { ButtonComponent } from '@my-org/ui-components';
import { SpinnerComponent } from '@my-org/ui-components/loading';
@Component({
imports: [ButtonComponent, SpinnerComponent],
// ...
})
export class AppComponent {}
What's Next?
Publishing is just the start. To build a world-class component library ecosystem:
- Add Storybook — document every component with interactive examples (see our Storybook guide)
- Set up an NX monorepo — manage multiple publishable packages in one repo (NX monorepo guide)
- Implement design tokens — connect Figma Variables to your Angular styles (design tokens guide)
A well-published library is a multiplier — every component you ship once gets reused dozens of times across products.