📦 Packaging Themes for Distribution
Prepare your WordPress theme for release
Learn file preparation, licensing, and creating distribution packages
Learning Objectives
- Prepare theme files for distribution
- Choose and implement licensing
- Create theme screenshots and assets
- Build distribution packages
- Version your theme properly
- Include necessary documentation
- Optimize file sizes
- Automate packaging process
Preparing for Distribution
Proper packaging ensures your theme is easy to install, professional, and meets distribution requirements for various platforms.
Distribution Preparation Steps
- Clean Code: Remove development files and debug code
- Optimize Assets: Compress images and minify CSS/JS
- Complete Documentation: Include all necessary docs
- License Properly: Clear licensing terms
- Test Thoroughly: Ensure everything works
- Version Correctly: Follow semantic versioning
Theme File Structure
theme-name/ # Distribution package ├── assets/ │ ├── css/ │ │ ├── style.min.css # Minified production CSS │ │ └── admin.min.css │ ├── js/ │ │ ├── theme.min.js # Minified production JS │ │ └── customizer.min.js │ ├── images/ │ │ └── *.{jpg,png,svg} # Optimized images │ └── fonts/ │ └── *.{woff,woff2} ├── inc/ │ ├── customizer.php │ ├── template-functions.php │ └── template-tags.php ├── template-parts/ │ ├── content/ │ ├── header/ │ └── footer/ ├── languages/ │ ├── theme-name.pot # Translation template │ └── readme.txt ├── page-templates/ │ └── *.php ├── style.css # Theme header information ├── functions.php ├── index.php ├── screenshot.png # 1200x900px ├── README.md ├── readme.txt # WordPress.org format ├── LICENSE ├── CHANGELOG.md └── *.php # Template files # Files to EXCLUDE from distribution: node_modules/ src/ # Source files .git/ .gitignore package.json package-lock.json composer.json composer.lock webpack.config.js gulpfile.js .eslintrc .stylelintrc phpcs.xml *.map # Source maps *.log .DS_Store Thumbs.db
Packaging Process
1 Clean Files
- Remove debug code
- Delete test files
- Clean comments
- Remove console.log
2 Optimize Assets
- Minify CSS/JS
- Compress images
- Optimize fonts
- Remove unused assets
3 Update Version
- Update style.css
- Update README
- Update changelog
- Tag in Git
4 Add Documentation
- README files
- License file
- Changelog
- Installation guide
5 Create Screenshot
- 1200x900px size
- PNG format
- Show key features
- Professional quality
6 Build Package
- Create ZIP file
- Verify contents
- Test installation
- Check file size
Theme Header Information
style.css Header
/**
* Theme Name: My Awesome Theme
* Theme URI: https://example.com/themes/my-awesome-theme
* Author: John Doe
* Author URI: https://example.com
* Description: A modern, responsive WordPress theme with extensive customization options,
* perfect for blogs, portfolios, and business websites. Features include
* custom headers, multiple layout options, and full Gutenberg support.
* Version: 2.1.0
* Requires at least: 5.9
* Tested up to: 6.4
* Requires PHP: 7.4
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: my-awesome-theme
* Domain Path: /languages
* Tags: two-columns, three-columns, left-sidebar, right-sidebar,
* flexible-header, custom-background, custom-colors, custom-header,
* custom-menu, custom-logo, editor-style, featured-images,
* footer-widgets, full-width-template, rtl-language-support,
* sticky-post, theme-options, threaded-comments, translation-ready,
* block-styles, wide-blocks, accessibility-ready
*
* This theme, like WordPress, is licensed under the GPL.
* Use it to make something cool, have fun, and share what you've learned with others.
*/
Theme Licensing
| License | Use Case | Requirements | WordPress.org Compatible |
|---|---|---|---|
| GPL v2+ | WordPress.org, free themes | Must remain GPL, include license | ✅ Yes |
| GPL v3 | Stronger copyleft | More restrictions on usage | ✅ Yes |
| MIT | Permissive, commercial | Include copyright notice | ❌ No |
| Split License | PHP=GPL, CSS/JS=proprietary | Complex licensing terms | ❌ No |
| Commercial | Premium marketplaces | Custom terms, usually GPL | ❌ No |
LICENSE File
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 2024 Your Name
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
[Full GPL v2 text...]
Automated Build Process
NPM Build Script
{
"scripts": {
"build": "npm run clean && npm run compile && npm run optimize",
"clean": "rimraf dist/",
"compile": "npm run compile:css && npm run compile:js",
"compile:css": "sass src/scss:dist/css --style=compressed",
"compile:js": "webpack --mode production",
"optimize": "npm run optimize:images && npm run optimize:fonts",
"optimize:images": "imagemin src/images/* --out-dir=dist/images",
"optimize:fonts": "subset-font src/fonts/*.ttf --output dist/fonts/",
"package": "npm run build && npm run create-package",
"create-package": "node scripts/package.js",
"version": "npm run update-version && git add -A",
"update-version": "node scripts/update-version.js"
}
}
Package Creation Script (scripts/package.js)
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
// Configuration
const themeName = 'my-awesome-theme';
const version = require('../package.json').version;
const outputDir = 'packages';
const outputFile = `${themeName}-${version}.zip`;
// Files to include
const includePatterns = [
'**/*.php',
'**/*.css',
'**/*.js',
'**/*.png',
'**/*.jpg',
'**/*.svg',
'**/*.woff',
'**/*.woff2',
'**/*.pot',
'**/*.mo',
'**/*.po',
'LICENSE',
'README.md',
'readme.txt',
'screenshot.png',
'CHANGELOG.md'
];
// Files to exclude
const excludePatterns = [
'node_modules/**',
'src/**',
'scripts/**',
'tests/**',
'.git/**',
'.github/**',
'*.map',
'*.log',
'.DS_Store',
'Thumbs.db',
'package.json',
'package-lock.json',
'composer.json',
'composer.lock',
'webpack.config.js',
'gulpfile.js',
'.gitignore',
'.eslintrc',
'.stylelintrc',
'phpcs.xml',
'.env',
'.editorconfig'
];
// Create output directory
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
// Create archive
const output = fs.createWriteStream(path.join(outputDir, outputFile));
const archive = archiver('zip', {
zlib: { level: 9 } // Maximum compression
});
// Event handlers
output.on('close', () => {
const size = (archive.pointer() / 1024 / 1024).toFixed(2);
console.log(`✅ Package created: ${outputFile} (${size} MB)`);
console.log(`📦 Total files: ${archive.pointer()} bytes`);
});
archive.on('warning', (err) => {
if (err.code === 'ENOENT') {
console.warn('⚠️ Warning:', err);
} else {
throw err;
}
});
archive.on('error', (err) => {
throw err;
});
// Pipe archive to output
archive.pipe(output);
// Add files to archive
archive.glob('**/*', {
cwd: '.',
ignore: excludePatterns
});
// Add theme folder structure
archive.directory('./', themeName);
// Finalize archive
archive.finalize();
Version Update Script
const fs = require('fs');
const path = require('path');
// Get new version from package.json
const packageJson = require('../package.json');
const newVersion = packageJson.version;
// Update style.css
const stylePath = path.join(__dirname, '..', 'style.css');
let styleContent = fs.readFileSync(stylePath, 'utf8');
styleContent = styleContent.replace(
/Version:\s*[\d.]+/,
`Version: ${newVersion}`
);
fs.writeFileSync(stylePath, styleContent);
// Update README.md
const readmePath = path.join(__dirname, '..', 'README.md');
let readmeContent = fs.readFileSync(readmePath, 'utf8');
readmeContent = readmeContent.replace(
/Version:\s*[\d.]+/g,
`Version: ${newVersion}`
);
fs.writeFileSync(readmePath, readmeContent);
// Update functions.php (if version constant exists)
const functionsPath = path.join(__dirname, '..', 'functions.php');
if (fs.existsSync(functionsPath)) {
let functionsContent = fs.readFileSync(functionsPath, 'utf8');
functionsContent = functionsContent.replace(
/define\(\s*'THEME_VERSION',\s*'[\d.]+'\s*\)/,
`define( 'THEME_VERSION', '${newVersion}' )`
);
fs.writeFileSync(functionsPath, functionsContent);
}
console.log(`✅ Version updated to ${newVersion}`);
GitHub Release Automation
.github/workflows/release.yml
name: Create Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build theme
run: npm run build
- name: Create theme package
run: |
mkdir -p release
rsync -av --exclude-from='.distignore' . release/theme-name/
cd release
zip -r theme-name.zip theme-name/
- name: Create changelog
id: changelog
run: |
echo "CHANGELOG<> $GITHUB_ENV
git log --pretty=format:"- %s" $(git describe --tags --abbrev=0 HEAD^)..HEAD >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: release/theme-name.zip
body: |
## Changes in this Release
${{ env.CHANGELOG }}
## Installation
1. Download theme-name.zip
2. Go to WordPress Admin → Appearance → Themes
3. Click "Add New" → "Upload Theme"
4. Choose the downloaded file and install
## Documentation
See [README.md](README.md) for detailed documentation.
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Pre-Distribution Checklist
- All features tested and working
- Code validated (Theme Check plugin)
- No PHP errors or warnings
- No JavaScript console errors
- CSS/JS minified for production
- Images optimized
- Debug code removed
- Version number updated
- Changelog updated
- Screenshot.png included (1200x900)
- README files complete
- License file included
- Translation files (.pot) included
- Demo content prepared
- Documentation complete
- Support information added
- Development files excluded
- Package size optimized
- Installation tested
- Backup created
File Size Optimization
Image Optimization
# Using ImageOptim (Mac)
imageoptim --directory ./images
# Using jpegoptim (Linux)
jpegoptim --strip-all --all-progressive *.jpg
# Using pngquant
pngquant --quality=65-80 *.png
# Using svgo for SVG
svgo -f ./images/svg --multipass
# Batch optimization with imagemin
npx imagemin images/* --out-dir=dist/images \
--plugin=mozjpeg \
--plugin=pngquant \
--plugin=svgo
CSS/JS Optimization
# CSS minification with cssnano
npx postcss style.css --use cssnano --output style.min.css
# JavaScript minification with terser
npx terser script.js --compress --mangle --output script.min.js
# Combined optimization
npm run build:css && npm run build:js
Best Practices
Distribution Best Practices
- Test installation: Always test the package on a clean WordPress install
- Version consistently: Use semantic versioning (MAJOR.MINOR.PATCH)
- Include documentation: Never skip documentation
- Optimize file size: Smaller packages download faster
- Clean code: Remove all development artifacts
- Proper licensing: Be clear about usage rights
- Professional presentation: Quality screenshot and description
- Support information: Make it easy to get help
Never include sensitive information like API keys, database credentials, or personal data in your distribution package.
Create a .distignore file (similar to .gitignore) to list all files that should be excluded from the distribution package.
Practice Exercise
Package Your Theme