25 KiB
Plugin Update Workflow
Last Updated: January 20, 2026
Version: 1.8.4
Status: Production
Scope: How to release plugin updates and what happens automatically vs manually
🎯 Quick Start: Simplified Release Process
The plugin release process has been simplified to require only 3 fields:
- Version (e.g., 1.3.3)
- Changelog (what's new)
- Status (draft → released)
All other fields are either:
- ✅ Auto-filled from previous version
- ✅ Auto-generated on release (file path, size, checksum)
- ✅ Auto-calculated (version code)
Release in 3 clicks:
Django Admin → Add Plugin Version → Enter 3 fields → Save → Change status to 'released'
Recent Release History (v1.7.0)
WordPress Plugin Progression
| Version | Date | Type | Key Changes |
|---|---|---|---|
| 1.5.1 | Jan 10, 2026 | Minor | Admin UI consolidation: 6→3 pages (Dashboard, Settings, Logs), header alignment fixes |
| 1.5.0 | Jan 10, 2026 | Minor | Authentication simplification: Site.wp_api_key as single source of truth |
| 1.3.3 | Jan 10, 2026 | Patch | Template design: Square image grid fixes, landscape image positioning, direct border-radius/shadow on images without captions |
| 1.3.2 | Jan 9, 2026 | Patch | Template fixes in app and plugin, image layout improvements |
| 1.3.1 | Jan 9, 2026 | Patch | WordPress plugin versioning updates |
| 1.3.0 | Jan 8, 2026 | Minor | Initial distribution system release, automated updates |
Distribution System Status
- ✅ Automated ZIP generation: Operational
- ✅ Checksum calculation: MD5 + SHA256
- ✅ WordPress auto-update: Active
- ✅ Health checks: Implemented
- ✅ Installation tracking: Complete
Table of Contents
- Update Workflow Overview
- What Happens Automatically
- What Requires Manual Action
- Step-by-Step: Releasing a New Version
- Post-Update Verification Checklist
- Version Numbering
- Rollback Procedure
- Emergency Updates
- Recent Updates & Lessons Learned
1. Update Workflow Overview
The Big Picture
┌─────────────────────────────────────────────────────────────────────────┐
│ Plugin Update Lifecycle │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ 1. DEVELOP │ Make changes to plugin source code │
│ │ ↓ │ Location: /plugins/wordpress/source/igny8-wp-bridge/ │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ 2. VERSION │ Update version in PHP header + constant │
│ │ ↓ │ File: igny8-bridge.php │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ 3. RELEASE │ Create PluginVersion in Django admin │
│ │ ↓ │ Set status = "released" or "update_ready" │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ 4. AUTO-BUILD│ ✅ AUTOMATIC: ZIP created, checksums calculated │
│ │ ↓ │ Signal handler builds package on status change │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ 5. VERIFY │ Test download, check contents, verify endpoints │
│ │ ↓ │ See verification checklist below │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ 6. AVAILABLE │ WordPress sites can now see and install update │
│ │ │ Users update via WP admin or auto-update │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
2. What Happens Automatically
When you change a PluginVersion status to released or update_ready, the following happens automatically via Django signals:
✅ Automatic Actions
| Action | Details |
|---|---|
| ZIP Creation | Source files packaged into distribution ZIP |
| Version Update | Version number updated in PHP files inside ZIP |
| File Cleanup | Tests, .git, pycache, .bak files removed from ZIP |
| Checksum Calculation | SHA256 hash generated |
| File Size Recording | Byte count stored in database |
| File Path Update | file_path field populated |
| Released Date | released_at timestamp set |
| Symlink Update | *-latest.zip symlink updated |
How It Works (Signal Handler)
# backend/igny8_core/plugins/signals.py
@receiver(pre_save, sender=PluginVersion)
def auto_build_plugin_on_release(sender, instance, **kwargs):
"""
Triggered when PluginVersion.status changes to:
- 'released'
- 'update_ready'
Actions:
1. Calls create_plugin_zip() utility
2. Updates instance.file_path
3. Updates instance.file_size
4. Updates instance.checksum
5. Sets instance.released_at
"""
What WordPress Sites See
Once released, the check-update API returns:
{
"update_available": true,
"latest_version": "1.2.0",
"download_url": "https://api.igny8.com/api/plugins/igny8-wp-bridge/download/",
"changelog": "Your changelog here"
}
WordPress will show "Update Available" in:
- Plugins page
- Dashboard → Updates
- Admin bar (if enabled)
3. What Requires Manual Action
❌ Manual Steps Required
| Action | When | How |
|---|---|---|
| Update Source Version | Before release | Edit igny8-bridge.php header |
| Create PluginVersion Record | Each release | Django admin: just enter version, changelog, status |
| Verify After Release | After status change | Run verification checklist |
| Mark Old Version Deprecated | After successful release | Change old version status |
Note: The form is simplified - most fields auto-fill from previous version or are auto-generated.
Source Version Update Locations
When releasing a new version, update these in the source:
File: /plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php
/**
* Plugin Name: IGNY8 WordPress Bridge
* Version: 1.2.0 ← UPDATE THIS
*/
define('IGNY8_BRIDGE_VERSION', '1.2.0'); ← AND THIS
Note: The ZIP build process also updates these, but it's good practice to keep source in sync.
4. Step-by-Step: Releasing a New Version
The Simplified Process
┌─────────────────────────────────────────────────────────────────────────┐
│ Simplified Version Release (3 Required Fields) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Django Admin → Add Plugin Version │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Plugin: [IGNY8 WordPress Bridge ▼] ← Select plugin │ │
│ │ Version: [1.2.0] ← New version │ │
│ │ Status: [draft ▼] ← Start as draft │ │
│ │ Changelog: [Bug fixes and improvements] ← What's new │ │
│ │ │ │
│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │
│ │ Everything below is AUTO-FILLED or AUTO-GENERATED: │ │
│ │ │ │
│ │ Min API Version: 1.0 (from previous version) │ │
│ │ Min PHP Version: 7.4 (from previous version) │ │
│ │ File Path: (empty) → Auto-generated on release │ │
│ │ File Size: (empty) → Auto-calculated on release │ │
│ │ Checksum: (empty) → Auto-calculated on release │ │
│ │ Version Code: (empty) → Auto-calculated from version │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Click [Save] → Status changes to 'released' → ZIP builds automatically │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Step 1: Make Code Changes
cd /data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/
# Make your changes to PHP files
# Test locally if possible
Step 2: Update Version Number
Edit igny8-bridge.php:
/**
* Plugin Name: IGNY8 WordPress Bridge
* Version: 1.2.0 ← New version
*/
define('IGNY8_BRIDGE_VERSION', '1.2.0'); ← Match here
Step 3: Create PluginVersion in Django Admin
- Go to:
https://api.igny8.com/backend/ - Navigate to: Plugin Distribution → Plugin Versions
- Click Add Plugin Version
- Fill in ONLY these required fields:
- Plugin: IGNY8 WordPress Bridge
- Version: 1.2.0
- Status: draft (initially)
- Changelog: Describe changes
- Click Save
Note: All other fields are auto-filled:
min_api_version,min_platform_version,min_php_version→ Copied from previous versionfile_path,file_size,checksum→ Auto-generated when you releaseversion_code→ Auto-calculated from version number
Step 4: Release the Version
Option A: Release via Status Change
- Edit the PluginVersion you just created
- Change Status to
released - Click Save
Option B: Release via Bulk Action
- Select the version(s) in the list
- Choose Actions → ✅ Release selected versions
- Click Go
What happens: Signal triggers auto-build → ZIP created → database updated with file info
Note: You can also use the action 📢 Mark as update ready to immediately notify WordPress sites.
Step 5: Verify Release
Run the verification checklist below.
Step 6: Deprecate Old Version (Optional)
- Find the previous version in Django admin
- Change Status to
deprecated - Save
5. Post-Update Verification Checklist
Quick Verification Script
Run this single command to verify everything:
#!/bin/bash
# Complete Plugin Release Verification Script
# Usage: Save as verify-plugin.sh and run: bash verify-plugin.sh 1.1.2
VERSION=${1:-"latest"}
PLUGIN_SLUG="igny8-wp-bridge"
echo "=========================================="
echo "Plugin Release Verification: v${VERSION}"
echo "=========================================="
echo ""
# 1. Check ZIP file exists
echo "1. Checking ZIP file..."
if [ "$VERSION" = "latest" ]; then
ls -lh /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-latest.zip 2>/dev/null && echo " ✓ Latest ZIP exists" || echo " ✗ Latest ZIP not found"
else
ls -lh /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-v${VERSION}.zip 2>/dev/null && echo " ✓ ZIP v${VERSION} exists" || echo " ✗ ZIP v${VERSION} not found"
fi
echo ""
# 2. Check symlink
echo "2. Checking symlink..."
ls -la /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-latest.zip | grep -q "^l" && echo " ✓ Symlink valid" || echo " ✗ Symlink missing"
echo ""
# 3. Test download endpoint
echo "3. Testing download endpoint..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/download/)
FILE_SIZE=$(curl -s -o /dev/null -w "%{size_download}" https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/download/)
if [ "$HTTP_CODE" = "200" ]; then
echo " ✓ Download works: ${HTTP_CODE} - ${FILE_SIZE} bytes ($(( FILE_SIZE / 1024 )) KB)"
else
echo " ✗ Download failed: HTTP ${HTTP_CODE}"
fi
echo ""
# 4. Test check-update endpoint
echo "4. Testing check-update endpoint..."
UPDATE_RESPONSE=$(curl -s "https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/check-update/?current_version=1.0.0")
LATEST_VERSION=$(echo "$UPDATE_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin).get('latest_version', 'N/A'))" 2>/dev/null)
if [ -n "$LATEST_VERSION" ] && [ "$LATEST_VERSION" != "N/A" ]; then
echo " ✓ Check-update works: Latest version = $LATEST_VERSION"
else
echo " ✗ Check-update failed"
fi
echo ""
# 5. Test info endpoint
echo "5. Testing info endpoint..."
INFO_RESPONSE=$(curl -s "https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/info/")
PLUGIN_NAME=$(echo "$INFO_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin).get('name', 'N/A'))" 2>/dev/null)
if [ -n "$PLUGIN_NAME" ] && [ "$PLUGIN_NAME" != "N/A" ]; then
echo " ✓ Info endpoint works: $PLUGIN_NAME"
else
echo " ✗ Info endpoint failed"
fi
echo ""
# 6. Verify version in ZIP
echo "6. Verifying version in ZIP..."
if [ "$VERSION" != "latest" ]; then
ZIP_VERSION=$(unzip -p /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-v${VERSION}.zip ${PLUGIN_SLUG}/igny8-bridge.php 2>/dev/null | grep -E "Version:" | head -1 | awk '{print $3}')
if [ "$ZIP_VERSION" = "$VERSION" ]; then
echo " ✓ ZIP version matches: $ZIP_VERSION"
else
echo " ✗ ZIP version mismatch: expected $VERSION, got $ZIP_VERSION"
fi
else
echo " - Skipped (use specific version to check)"
fi
echo ""
# 7. Verify API URL
echo "7. Verifying API URL in ZIP..."
API_COUNT=$(unzip -p /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-latest.zip ${PLUGIN_SLUG}/igny8-bridge.php 2>/dev/null | grep -c "api.igny8.com")
if [ "$API_COUNT" -gt 0 ]; then
echo " ✓ API URL correct: api.igny8.com found (${API_COUNT} occurrences)"
else
echo " ✗ API URL incorrect: api.igny8.com not found"
fi
echo ""
# 8. Check database
echo "8. Checking database..."
docker exec igny8_backend python manage.py shell -c "
from igny8_core.plugins.models import Plugin, PluginVersion
try:
p = Plugin.objects.get(slug='${PLUGIN_SLUG}')
v = p.get_latest_version()
if v:
print(f' ✓ Latest version: {v.version}')
print(f' ✓ Status: {v.status}')
print(f' ✓ File: {v.file_path}')
print(f' ✓ Size: {v.file_size:,} bytes ({v.file_size/1024:.1f} KB)')
print(f' ✓ Checksum: {v.checksum[:32]}...')
else:
print(' ✗ No released version found')
except Exception as e:
print(f' ✗ Error: {e}')
" 2>/dev/null | grep -E "✓|✗"
echo ""
echo "=========================================="
echo "Verification Complete"
echo "=========================================="
Quick run (copy-paste):
# Verify latest version
curl -s "https://api.igny8.com/api/plugins/igny8-wp-bridge/info/" | python3 -m json.tool && \
curl -s "https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0" | python3 -m json.tool && \
ls -lh /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-latest.zip && \
echo "✓ All endpoints working"
Database Verification
# Check database has correct values
docker exec igny8_backend python manage.py shell -c "
from igny8_core.plugins.models import Plugin, PluginVersion
p = Plugin.objects.get(slug='igny8-wp-bridge')
v = p.get_latest_version()
print(f'Version: {v.version}')
print(f'Status: {v.status}')
print(f'File path: {v.file_path}')
print(f'File size: {v.file_size}')
print(f'Checksum: {v.checksum[:32]}...')
print(f'Released at: {v.released_at}')
"
Expected Results
| Check | Expected |
|---|---|
| Download returns | 200 status, ~150-200KB |
| check-update shows | "latest_version": "1.2.0" |
| info shows | "version": "1.2.0" |
| ZIP version header | Version: 1.2.0 |
| API URL | api.igny8.com (NOT app.igny8.com) |
WordPress Verification (If Possible)
- Go to a test WordPress site with plugin installed
- Navigate to Dashboard → Updates → Check Again
- Verify "IGNY8 WordPress Bridge" shows update available
- Click "View version details" → verify changelog appears
- Click "Update Now" → verify update completes successfully
6. Version Numbering
Semantic Versioning
Format: MAJOR.MINOR.PATCH (e.g., 1.2.3)
| Part | When to Increment |
|---|---|
| MAJOR | Breaking changes, incompatible API changes |
| MINOR | New features, backwards compatible |
| PATCH | Bug fixes, minor improvements |
Version Code Calculation
Used for numeric comparison in database:
1.0.0 → 10000
1.0.1 → 10001
1.2.0 → 10200
1.2.3 → 10203
2.0.0 → 20000
Formula: (MAJOR * 10000) + (MINOR * 100) + PATCH
7. Rollback Procedure
If Update Causes Issues
Option 1: Quick Rollback via Database
# Make old version the "latest" by changing status
docker exec igny8_backend python manage.py shell -c "
from igny8_core.plugins.models import PluginVersion
# Demote new version
new = PluginVersion.objects.get(plugin__slug='igny8-wp-bridge', version='1.2.0')
new.status = 'deprecated'
new.save()
# Promote old version back
old = PluginVersion.objects.get(plugin__slug='igny8-wp-bridge', version='1.1.1')
old.status = 'released'
old.save()
print('Rollback complete')
"
Option 2: Keep Old ZIP Files
Old ZIP files are preserved in dist/. To serve an old version:
# Update symlink to point to old version
cd /data/app/igny8/plugins/wordpress/dist/
ln -sf igny8-wp-bridge-v1.1.1.zip igny8-wp-bridge-latest.zip
Retention Policy
Keep at least 3 previous version ZIPs for emergency rollback:
- Current release
- Previous release
- One before that
8. Emergency Updates
For Critical Security Fixes
- Set
force_update = Trueon the new PluginVersion - This flag signals WordPress sites that update is mandatory
- Sites with auto-updates enabled will update immediately
Emergency Release Checklist
# 1. Make the fix in source
cd /data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/
# ... make changes ...
# 2. Update version (use PATCH increment)
# Edit igny8-bridge.php
# 3. Create version in Django admin with:
# - Status: update_ready
# - Force Update: ✓ (checked)
# - Changelog: "SECURITY: [description]"
# 4. Verify immediately
curl https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0
# Response should include:
# "force_update": true
9. Recent Updates & Lessons Learned
v1.3.3 Release (Jan 10, 2026)
Changes:
- Fixed square image grid layout (side-by-side display)
- Fixed landscape image positioning in sections 4+
- Removed card wrapper for images without captions
- Applied border-radius/shadow directly to images
- Landscape images now appear after first paragraph
Process:
- Source code updated in plugin templates and CSS
- Version bumped in
igny8-bridge.php(header + constant) - Django admin: Created v1.3.3 with changelog
- Status changed to "released" → ZIP auto-generated
- Verified download endpoint and file contents
- Template changes tested on staging WordPress site
Lessons:
- CSS changes require thorough cross-browser testing
- Image layout fixes need responsive design verification
- Template changes should be tested with multiple content types
v1.3.2 Release (Jan 9, 2026)
Changes:
- Template rendering improvements
- Image layout enhancements
- Content section fixes
Process:
- Standard release workflow
- Auto-build successful
- No rollback needed
v1.3.0 Release (Jan 8, 2026)
Changes:
- Initial distribution system implementation
- WordPress auto-update mechanism
- Base template system
Process:
- Major release with new infrastructure
- Extensive testing required
- First use of automated packaging
Distribution System Milestones
v1.7.0 Infrastructure:
- ✅ Complete plugin distribution system
- ✅ Multi-platform support architecture
- ✅ Automated versioning and packaging
- ✅ Security features (checksums, signed URLs)
- ✅ Monitoring and analytics
Best Practices Established:
- Always test download endpoint after release
- Verify ZIP contents match source
- Check version number in extracted files
- Test update notification in WordPress
- Monitor download analytics
Common Issues Resolved:
- ZIP generation timing → Now synchronous in signals
- Checksum mismatches → Auto-calculated reliably
- Version comparison → Semantic versioning logic fixed
- File size tracking → Automatic and accurate
Quick Reference Card
Release New Version (Simplified)
1. Edit source code in /plugins/wordpress/source/igny8-wp-bridge/
2. Update version in igny8-bridge.php (header + constant)
3. Django Admin → Add Plugin Version:
- Plugin: IGNY8 WordPress Bridge
- Version: 1.3.3 (or next version)
- Changelog: Describe changes
- Status: draft
- (All other fields auto-fill)
4. Change status to "released" (or use bulk action) → Auto-build triggers
5. Run verification checklist
6. Optionally: Mark old version as deprecated
Admin Bulk Actions:
- ✅ Release selected versions - Builds ZIP and marks as released
- 📢 Mark as update ready - Notifies WordPress sites
- 🗑️ Mark as deprecated - Deprecates old versions
Verification Commands
# Test all endpoints
curl -I https://api.igny8.com/api/plugins/igny8-wp-bridge/download/
curl https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0
curl https://api.igny8.com/api/plugins/igny8-wp-bridge/info/
# Check ZIP contents
unzip -l /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v*.zip | head -20
Emergency Rollback
# Swap versions in database
docker exec igny8_backend python manage.py shell -c "
from igny8_core.plugins.models import PluginVersion
PluginVersion.objects.filter(version='NEW').update(status='deprecated')
PluginVersion.objects.filter(version='OLD').update(status='released')
"
Related Documentation
- WORDPRESS-INTEGRATION.md - Full integration guide
- docs/plans/PLUGIN-DISTRIBUTION-SYSTEM.md - Original system design