Arbitrary version numbers make it impossible to understand the impact of updates:
- Unclear changes - Is 2.5 safe to update from 2.4?
- Hidden breaking changes - Changed API but only bumped minor version
- Risky updates - Security fix or breaking change?
- No migration path - When will deprecated features be removed?
- Dependency hell - Can't specify compatible versions
/**
* Plugin Name: My Plugin
* Version: 2.5
*
* Changelog:
* 2.5 - Added new feature and broke backward compatibility (oops!)
* 2.4 - Fixed critical security bug
* 2.3 - Minor tweaks
* 2.2.1.5 - ???
*/Problems:
- Version 2.5: Breaking change but no indication (should be 3.0.0)
- Version 2.4: Security fix (should be 2.3.1, not 2.4)
- Version 2.2.1.5: Invalid semver format
- No clear pattern for users to understand risk
/**
* Plugin Name: My Plugin
* Version: 2.5.1
*
* Semantic Version Format: MAJOR.MINOR.PATCH
* - MAJOR: Breaking changes (incompatible API changes)
* - MINOR: New features (backward compatible)
* - PATCH: Bug fixes (backward compatible)
*
* Changelog:
* 2.5.1 - 2024-11-20 - PATCH: Fixed cache invalidation bug
* 2.5.0 - 2024-11-15 - MINOR: Added caching support
* 2.4.1 - 2024-11-10 - PATCH: Fixed SQL injection vulnerability
* 2.0.0 - 2024-10-01 - MAJOR: Changed return type (breaking change)
*/Benefits:
- Clear communication of change impact
- Users know update safety at a glance
- Automated tools can manage dependencies
- Professional appearance
Given version number MAJOR.MINOR.PATCH:
MAJOR - Increment when you make incompatible API changes
- Changing function signatures
- Removing methods or parameters
- Changing return types
- Breaking backward compatibility
MINOR - Increment when you add functionality in a backward compatible manner
- New features
- New methods
- New optional parameters (with defaults)
- Deprecating (not removing) features
PATCH - Increment when you make backward compatible bug fixes
- Bug fixes
- Security fixes
- Performance improvements
- Documentation updates
// PATCH: Security fix (2.4.0 → 2.4.1)
/**
* @since 2.4.0 Initial implementation
* @since 2.4.1 SECURITY: Fixed SQL injection
*/
function get_post_by_slug( string $slug ): ?object {
global $wpdb;
return $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_name = %s",
$slug
)
);
}
// MINOR: New feature (2.4.0 → 2.5.0)
/**
* @since 2.5.0 Added caching (backward compatible)
*/
function get_popular_posts(): array {
$cached = wp_cache_get( 'popular_posts' );
if ( $cached ) {
return $cached;
}
// Implementation...
}
// MAJOR: Breaking change (1.5.2 → 2.0.0)
/**
* @since 1.0.0 Returned array
* @since 2.0.0 BREAKING: Now returns Collection
*/
function get_user_posts( int $user_id ): Collection {
return new Collection( get_posts( [ 'author' => $user_id ] ) );
}For alpha, beta, and release candidates:
const VERSION_ALPHA_1 = '3.0.0-alpha.1'; // First alpha
const VERSION_ALPHA_2 = '3.0.0-alpha.2'; // Second alpha
const VERSION_BETA_1 = '3.0.0-beta.1'; // First beta
const VERSION_RC_1 = '3.0.0-rc.1'; // Release candidate
const VERSION_STABLE = '3.0.0'; // Stable releaseFormat: MAJOR.MINOR.PATCH-prerelease.number
Include build information without affecting version precedence:
const VERSION_WITH_BUILD = '2.5.1+20241122.abc123';Format: MAJOR.MINOR.PATCH+build.metadata
Deprecate in MINOR, remove in MAJOR:
/**
* Process payment (legacy).
*
* @since 1.0.0
* @deprecated 2.5.0 Use process_payment_v2() instead.
* Will be removed in 3.0.0.
*/
public function old_process(): void {
_deprecated_function( __METHOD__, '2.5.0', 'process_payment_v2' );
$this->process_payment_v2();
}Timeline:
- v2.5.0 (MINOR): Deprecate, show warning, still works
- v2.6.0 - v2.x.x: Grace period for migration
- v3.0.0 (MAJOR): Remove deprecated method
Follow Keep a Changelog format:
## [2.5.1] - 2024-11-20
### Fixed
- Cache invalidation bug in get_popular_posts()
- Memory leak in image processing
### Security
- Fixed XSS vulnerability in user input
## [2.5.0] - 2024-11-15
### Added
- Caching support for popular posts
- Currency parameter to calculate_price()
### Deprecated
- PaymentGateway::old_process() in favor of process_payment_v2()
## [2.0.0] - 2024-10-01
### Changed
- **BREAKING**: get_user_posts() now returns Collection instead of array
### Migration Guide
Before (v1.x):
$posts = get_user_posts($user_id);
foreach ($posts as $post) { }
After (v2.x):
$posts = get_user_posts($user_id);
foreach ($posts->all() as $post) { }Understanding semver in dependencies:
{
"require": {
"my-plugin/core": "^2.5"
}
}^2.5 means: >= 2.5.0 and < 3.0.0
- ✅ Allows: 2.5.0, 2.5.1, 2.6.0, 2.99.0
- ❌ Blocks: 3.0.0 (breaking changes)
~2.5 means: >= 2.5.0 and < 2.6.0
- ✅ Allows: 2.5.0, 2.5.1, 2.5.99
- ❌ Blocks: 2.6.0 (new features)
- ✅ Bug fixes only
- ✅ No API changes
- ✅ No new features
- ✅ Update changelog
- ✅ Tag: v2.5.1
- ✅ New features (backward compatible)
- ✅ Optional new parameters with defaults
- ✅ New methods/classes
- ✅ Deprecation notices (but not removal)
- ✅ Update changelog
- ✅ Tag: v2.6.0
- ✅ Breaking changes documented
- ✅ Remove deprecated features
- ✅ Change method signatures
- ✅ Update migration guide
- ✅ Update changelog
- ✅ Tag: v3.0.0
- ✅ Announce breaking changes prominently
When you follow semver, you promise:
PATCH releases are safe to update automatically
- Fix bugs only
- No API changes
- No new features
MINOR releases are safe to update with confidence
- Add new features
- Maintain backward compatibility
- May deprecate (but not remove)
MAJOR releases require review before updating
- May break backward compatibility
- May remove deprecated features
- Should have migration guide
WordPress itself doesn't strictly follow semver (6.4 → 6.5 can have breaking changes), but YOUR plugin/theme should because:
✅ Users need to know update safety
✅ Automated tools rely on it
✅ Professional appearance
✅ Easier dependency management
✅ Use MAJOR.MINOR.PATCH format
✅ MAJOR for breaking changes
✅ MINOR for new features
✅ PATCH for bug fixes
✅ Deprecate in MINOR, remove in MAJOR
✅ Document changes clearly
✅ Provide migration guides for breaking changes
❌ Don't use arbitrary version numbers
❌ Don't hide breaking changes in MINOR versions
❌ Don't bump MAJOR for small changes
❌ Don't remove features without deprecation period
❌ Don't use invalid semver formats (2.2.1.5)
- Semver Spec: https://semver.org/
- Keep a Changelog: https://keepachangelog.com/
- Composer Versions: https://getcomposer.org/doc/articles/versions.md
- WordPress Plugin Handbook: https://developer.wordpress.org/plugins/
Semantic versioning is a communication tool. It tells users at a glance whether an update is safe, exciting, or requires careful review. Follow it consistently, and your users will trust your updates.