Chrome Extension Extension Packaging — Developer Guide
4 min readExtension Packaging Guide
This guide covers how to package your Chrome extension for distribution—either through the Chrome Web Store or for self-hosting. You’ll learn what to include, what to exclude, and how to automate the packaging process in your CI/CD pipeline.
Overview
Packaging your extension is the final step before distribution. The package format depends on your distribution method:
- Chrome Web Store (CWS): Upload a ZIP file
- Self-hosting: Create a CRX file for manual installation
- Development/Testing: Load unpacked directly from the source directory
This guide covers all three approaches plus automation strategies.
Directory Structure for Packaging
Your extension needs a clean, production-ready directory before packaging. A typical structure looks like:
dist/
├── manifest.json
├── background.js
├── popup.html
├── popup.js
├── content.js
├── styles.css
└── icons/
├── icon-16.png
├── icon-32.png
├── icon-48.png
└── icon-128.png
This dist folder should contain only the files needed at runtime—no source files, build artifacts, or development tools.
What to Include
Every extension package must include:
| File/Directory | Required | Notes |
|---|---|---|
manifest.json |
Yes | Must be valid Manifest V3 |
| Background script | Yes | Service worker file |
| Popup files | If applicable | HTML, JS, CSS for popup |
| Content scripts | If applicable | Injected scripts |
| Icons | Strongly recommended | 16, 32, 48, 128px sizes |
_locales/ |
If internationalized | Translation files |
What to Exclude
Never include the following in your package:
- Source files:
src/,.tsfiles, uncompiled code - node_modules/: Dependencies should be bundled
- Test files:
__tests__/,.test.js,*.spec.ts - Config files:
.gitignore,.eslintrc,tsconfig.json - Documentation:
README.md,LICENSE,CHANGELOG.md - Secrets:
.envfiles, API keys, private keys (except the PEM for CRX) - Source maps:
.mapfiles (unless specifically debugging) - Development tools: Hot reload scripts, mock servers
ZIP for Chrome Web Store
The Chrome Web Store accepts extensions as ZIP files. Create your package with:
cd dist
zip -r ../extension.zip . -x ".*" "*.map"
Important Notes
- Maximum size: 50MB (compressed), though smaller is better
- Faster review: Smaller packages review faster
- Better UX: Users download smaller extensions more willingly
- Exclusions: Skip hidden files, source maps, and any debug artifacts
CRX for Self-Hosting
For self-hosted distribution (without the Store), create a CRX file:
Option 1: Using Chrome UI
- Open
chrome://extensions - Enable Developer mode
- Click Pack extension
- Select your
distdirectory - Chrome generates
dist.crxanddist.pem
Option 2: Programmatic (crx3 package)
npx crx3 pack ./dist -o extension.crx -p private-key.pem
Important: Private Key
- The
.pemfile is your private key—keep it secure - This key signs your CRX and identifies your extension
- Losing the key means you cannot update that extension
- Backup your PEM file in a secure location
Build Script Example
A typical npm-based build and package workflow:
{
"scripts": {
"build": "esbuild src/background.js --bundle --outfile=dist/background.js --minify",
"prepackage": "npm run build",
"package": "cd dist && zip -r ../extension.zip . -x '.*' '*.map'",
"prepublish": "npm run package"
}
}
More advanced setups might use:
- Vite or Webpack for bundling
- TypeScript compilation
- CSS post-processing (PostCSS, Tailwind)
- Asset optimization for icons
Automated Packaging in CI
Automate your packaging pipeline using GitHub Actions:
name: Build and Package
on: [push, pull_request]
jobs:
package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build
- run: npm run package
- uses: actions/upload-artifact@v4
with:
name: extension
path: extension.zip
Publishing to Chrome Web Store
For automated publishing, use the Chrome Web Store API with a service account:
npx chrome-webstore-upload-cli upload \
--extension-id=$EXTENSION_ID \
--client-id=$CLIENT_ID \
--client-secret=$CLIENT_SECRET \
--refresh-token=$REFRESH_TOKEN \
--zip-path=extension.zip
Verification Before Packaging
Always verify your package before distribution:
- Run all tests: Ensure nothing is broken
- Validate manifest: Use Chrome’s manifest validator
- Check file references: Confirm all files in manifest exist in dist
- Verify icons: Ensure 16, 32, 48, and 128px icons are correct
- Load unpacked: Test one final time with “Load unpacked”
Cross-References
- Publishing Guide — Overview of distribution options
- Submission Process — Step-by-step Store submission
- CI/CD Pipeline — Automation best practices
Related Articles
Related Articles
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.