Bodholdt Backup for OneDrive
WordPress backups to Microsoft OneDrive
Get Bodholdt Backup for OneDrive
Lifetime licenses available · 14-day money-back · Cancel anytime
Free
Free forever:
- 1 site
- Full plugin functionality
- Full restore + unlimited backup retention
- Community support
Solo
Everything in Free, plus:
- A paid license for 1 site
- Selective restore — choose what to restore (database, plugins, themes, uploads, core)
- Streaming restore (FK-safe, BLOB-safe)
- Lifetime license available
- Email support while active
Pro
Everything in Solo, plus:
- 5 sites on a single license
- Slack & Discord notifications on backup success, failure, restore, and stale-alert events
Agency
Everything in Pro, plus:
- Unlimited sites
- Priority email support while active
See full feature comparison →
| Feature | Free | Solo | Pro | Agency |
|---|---|---|---|---|
| Sites | 1 | 1 | 5 | ∞ |
| Full plugin functionality | ✓ | ✓ | ✓ | ✓ |
| Full restore | ✓ | ✓ | ✓ | ✓ |
| Backup retention | Unlimited | Unlimited | Unlimited | Unlimited |
| Selective restore | — | ✓ | ✓ | ✓ |
| Slack & Discord alerts | — | — | ✓ | ✓ |
| Lifetime license option | — | ✓ | ✓ | ✓ |
| Support | WordPress.org forum | Email while active | Email while active | Priority email |
Start free with the Lite edition
Full automated backups and full restore, free forever. Upgrade to Pro for selective restore, multi-site, and Slack/Discord alerts.
Get Bodholdt Backup for OneDrive (free)
The free version is yours. No card, no license key. Enter your email and we will send you a download link.
Enter the 6-digit code sent to your email:
Didn't receive it? Check your spam folder, or click Resend Code.
Real screens from Bodholdt Backup for OneDrive
These are real admin screens, not mockups. Click any one to view it full size.
Same bulletproof backup system, powered by Microsoft OneDrive. Same beautiful UI, same security hardening, same selective restore. Different cloud.
Streaming restore that stays FK-safe and BLOB-safe
Full and incremental backups, scheduled or on demand
Microsoft Graph API and Azure app integration
Complete multisite-network backup
Selective restore of the database, plugins, themes, uploads, and core (Solo and up)
Cross-server migration installer
Resumable chunked upload with auto-retry
Guided setup wizard with an OAuth walkthrough
- Streaming restore that stays FK-safe and BLOB-safe
- Full and incremental backups, scheduled or on demand
- Microsoft Graph API and Azure app integration
- Complete multisite-network backup
- Selective restore of the database, plugins, themes, uploads, and core (Solo and up)
- Cross-server migration installer
- Resumable chunked upload with auto-retry
- Guided setup wizard with an OAuth walkthrough
- More cyan polish. "Start Backup Now" cloud icon is now pure white; the "Your Backups" table (column headers, file/Download/Restore icons, row hover) now uses the OneDrive blue instead of grey/purple; in-app "Upgrade to Pro" notices and the troubleshooting path now use the blue accent.
- Logs toolbar. The "Apply" filter button is now a gradient button (Clear History was already red).
- Admin polish. Dashboard "Success" and "Connected" status pills now use the OneDrive blue (matching the theme); "Calculate Size", "Test Connection", "Send Test Email", and "Send Test Notification" are now gradient buttons; and "Reset to Defaults" is now clearly marked red as a destructive action.
- Distinct cyan admin + consistent buttons. The OneDrive admin is now explicitly themed in its signature cyan (matching its product page, so it reads as distinct from the green Google Drive admin), and the "Start Backup Now" button uses the same gradient as "Save Settings."
- Admin styling now sourced from the shared Bodholdt Labs design system. The admin UI consumes the canonical `bod-admin.css` (the same dark-admin stylesheet shipped across Bodholdt Labs plugins) instead of a plugin-local copy, so the look stays consistent and future polish lands everywhere at once. Visually unchanged save for one accessibility fix (purple text colour bumped to a WCAG-AA contrast). No functional or behavioural change.
- Pro edition with licensed auto-updates. This is the paid, off-directory build of Bodholdt Backup for OneDrive, distributed by Bodholdt Labs. It enables in-dashboard automatic updates delivered from the Bodholdt Labs licensing server when a valid license key is active (license key + site URL + product identifier only; never site content or backup data).
- Selective restore available on every paid tier, including Solo — pick exactly which components (database, system files, themes, plugins, uploads) to restore. Retention is uncapped on all tiers (bounded only by a per-install sanity limit).
- Slack/Discord webhook notifications for backup success/failure, restore-initiated, and stale-backup watchdog events (Pro and Bundle tiers).
- Hardening (parity with the directory edition): first-party download streaming now uses the WordPress HTTP API; admin-notice scripts moved to `wp_add_inline_script`; staging/uninstall paths resolved via `wp_get_upload_dir()`; installer CSRF/lockfiles relocated to the system temp directory with a table-prefix validation guard; all request inputs run through `wp_unslash()` before sanitization.
- Added an "External services" section to this readme documenting the Microsoft Graph / Microsoft OAuth endpoints, the Bodholdt Labs licensing/update server, and the WordPress.org secret-key (salt) request used during cross-server migration.
- Fixed: footer line missing on installs that run third-party plugins which globally blank the WP admin footer. Some plugins (e.g. Taxonomy CSV Import/Export) register `__return_empty_string` against `admin_footer_text` at priority 11 across every admin page — which silently wiped our v5.20.0 footer line (Bodholdt Backup for OneDrive v5.X.X · Documentation · Support). Our filter now runs at priority 99, so it executes last and the footer renders as designed regardless of any other plugin's behavior.
- UI: version moved out of the page title. The big "BODHOLDT BACKUP FOR ONEDRIVE V5.X.X" plugin-page heading is now just the product name + the tier badge ("FREE" / "Pro" / etc.). The version still appears, but now in the WordPress admin footer alongside Documentation and Support links — discreet and out of the way. Aligned with how WordPress core, WooCommerce, Yoast, and most established plugins handle this.
- Brand consistency follow-up. Two leftover short-form "Bodholdt OneDrive" labels weren't updated during the v5.19.0 rebrand: the WP admin sidebar menu entry, and the License sub-page heading. Both now read "Bodholdt Backup for OneDrive" to match the rest of the plugin. No behavior change.
- Fixed: Setup Wizard never appeared on fresh installs. A function-scope bug in the dashboard renderer left the wizard's display gate (`$show_wizard`) undefined, which PHP 8+ surfaces as a Warning and silently coerces to null — so on a fresh install the wizard quietly didn't render, leaving new users with no guided onboarding (just an empty dashboard asking them to enter OAuth credentials with no setup hand-holding). The wizard now renders correctly as designed.
- Renamed to "Bodholdt Backup for OneDrive." Adopts the trademark-safe "for [destination]" naming pattern across all customer-facing surfaces (plugin header, admin menus, dashboard, email subjects, logs). Functionally identical — no settings or backups affected.
- Setup wizard — improved "Testing on a local site?" callout. The advisory now recommends Local's "Live Link" feature (HTTPS public URL) as a fallback if Microsoft still rejects a `.local` redirect URI after enabling HTTPS, so testers have a clear path forward.
- Tested up to: WordPress 7.0. Verified compatibility with the current stable release.
- Honest claims. Softened a few storefront descriptions to be evergreen rather than time-specific ("accessibility-minded" rather than a literal compliance grade; "security-audited each release" rather than a specific score) so they stay accurate without periodic updates.
- wp.org Contributors handle updated to `bodholdtlabs` in preparation for wp.org plugin directory submission.
- Per-site backup folders. Each site now backs up to its own uniquely-named folder in your OneDrive (identified by a per-site ID), so multiple WordPress sites sharing one Microsoft account no longer write into the same folder. If a shared folder from an earlier version is detected, the plugin moves this site to its own folder and shows a one-time notice — nothing is deleted and your existing backups stay untouched.
- Licensing fix + brand consolidation. License activation, validation, and auto-updates now point at the correct server (bodholdtlabs.com), so license keys validate reliably (the previous host redirected and dropped the request). Author byline, support email, and account links updated to Bodholdt Labs / bodholdtlabs.com.
- Retention tier fix. Solo and Agency licenses now get their correct backup-retention limits (Solo 50, Agency unlimited up to the system maximum), and Agency now includes selective restore and Slack/Discord notifications. Previously these tiers fell back to the Free 10-backup limit because the entry tier is issued as "default" and the top tier as "agency" — both are now recognized. Pro and Bundle are unchanged.
- Fun + accessibility pass. Small delight and polish touches, matched to the Bodholdt Backup for Google Drive sibling.
- New: A running "backups protected" tally. The Backup Health card now shows how many backups you've made and the total size kept safe (e.g. "🛡️ 47 backups protected · 182 GB kept safe"). This counts off dedicated counters, so clearing your on-screen log history doesn't reset it.
- New: First-backup moment. Your very first successful backup now gets a bigger one-time celebration and a "Your first backup is done — your site is officially protected!" message.
- New: Restore celebration. Finishing a restore now greets you with a gentle celebration and a "Site restored — welcome back." message.
- Accessibility: Celebrations respect "reduced motion." All confetti animations now honor your operating system's "reduce motion" setting (the JS canvas confetti previously ignored it).
- Clearer destructive confirms. The "Disconnect OneDrive" and "Delete all backup log history" confirmations now spell out exactly what happens and what is NOT affected (your cloud backups are never touched).
- Warmer copy on the empty "Your Backups" state and the backup-complete message.
- Cross-plugin parity follow-ups.
- "Reset to Defaults" now restores a sensible Daily 3:00 AM backup (was previously "Manual only"; both backup plugins now reset to the same default). And a scheduled backup on a site that hasn't connected OneDrive yet now skips quietly, instead of emailing a daily "backup failed" alert.
- Internal consistency: the default backup schedule is now Daily for fresh installs (matching Bodholdt Backup for Google Drive). The one-click backup flow is unchanged.
- Steve Jobs / Grandma Test Pass — Rounds 2-5 (error states, settings parity, micro-interactions, visual polish). Brings the plugin to full feature/UX parity with Bodholdt Backup for Google Drive. Bundles three internal batches:
- *Error handling (R2):* clearer test-connection and restore-failure messages; the failed-backup notice now links straight to Settings and Logs; "Disconnect" now tells you how to fully revoke this site's access in your Microsoft account; a mistyped notification email is no longer saved silently; cancelling at Microsoft's consent screen is handled gracefully; backup-download errors are now friendly with a back link.
- *Settings (R3):* added an Hourly-schedule performance warning, hid the time picker when scheduling is set to Manual, added a server-side "Settings saved" confirmation, and clearer "Backups to Keep" / "Migration Tools" / file-exclusion copy.
- *Polish (R4/R5):* reliable copy-to-clipboard (with fallback), a proper "Refreshing…" working state on the size estimate, and all admin brand colors moved onto the shared design-token system (so the warning color is now consistent across both backup plugins). Build pipeline excludes editor/OS cruft from the distributed zip.
- Fixed: Staging directory now has a graceful fallback when `/var/lib/bodholdt-staging/` isn't writable. Pre-v5.15.0 the plugin defaulted `BODHOLDT_OD_DIR` to `/var/lib/bodholdt-staging/onedrive/<random-suffix>/` with no fallback — a path that only exists on operator-prepared hosts. On any host without root access (most managed WordPress hosts, every local development environment, every WP.org reviewer install), the `mkdir()` call returned false, the next `fopen()` failed silently, and the backup or restore died mid-flight with the AJAX progress indicator simply disappearing at "Fetching Download link…" — no error to the user. v5.15.0 adds a three-tier fallback hierarchy resolved at plugin-load time: (1) `/var/lib/bodholdt-staging/onedrive/<suffix>/` if writable (preserves the post-2026-05-05 security model on operator-prepared hosts), (2) `sys_get_temp_dir() . '/bodholdt-staging-onedrive/<suffix>/'` if `/var/lib/` isn't writable (system temp, ephemeral but still isolated from the web document root), (3) `wp-content/uploads/.bodholdt-staging-onedrive/<suffix>/` as a last resort with three hardening layers (an `.htaccess` deny-all rule for Apache, an `index.php` returning HTTP 403 as defense-in-depth, and a docs-recommended nginx `location` deny rule in the FAQ). Each fallback transition is logged via `error_log()` so operators on hardened hosts can see why the plugin moved off the primary. Override via `define('BODHOLDT_OD_DIR', __DIR__ . '/your/custom/path/')` in wp-config.php to bypass the resolution entirely. Symmetric fix to the Bodholdt Backup for Google Drive plugin's v6.15.0 change (also shipped today). Plan §4.5.13.
- Fixed: Restore engine was silently dropping `wp-content/mu-plugins/`, `wp-content/languages/`, and WordPress drop-ins (object-cache.php / advanced-cache.php / db.php / maintenance.php). OneDrive's backup engine already picked up mu-plugins via the wider `core` scope walk (no v5.14.1 backup-side hotfix needed), but the restore engine only copied `themes/`, `plugins/`, and `uploads/` from the extracted backup to the destination's `wp-content/`. Result: any disaster-recovery restore landed a site without its must-use plugins (custom auth handlers, debug-log filters, multisite-shared utilities, etc.) and without its drop-ins (Redis object cache, full-page cache, custom DB layer, maintenance-mode override). The restore engine now treats `mu-plugins/` and `languages/` like the database — always copied, no user-facing scope toggle — and copies the four standard wp-content drop-ins when they're present in the backup. Surfaced 2026-05-17 night during the §4.5.10 GDrive end-to-end retest, applied symmetrically to OneDrive. Plan §4.5.15. (v5.14.1 was never shipped — version number reserved as the GDrive companion to this fix's mirror; v5.14.2 is the first shipped restore-side fix on the OneDrive side.)
- Fixed: Restore engine no longer unconditionally drops your other Bodholdt backup plugin. Prior versions hard-skipped both `bodholdt-onedrive-backup` AND `bodholdt-google-drive-backup` directories during restore. The self-skip is necessary (a plugin can't safely overwrite itself mid-execution), but the sibling-skip meant customers running both plugins as belt-and-suspenders backup would lose one on every restore. The restore engine now skips the sibling only when it's currently network-active (i.e. running and could collide); installed-but-inactive siblings are restored normally. Plan §4.5.18.
- New: Unified 4-step setup wizard. The setup wizard has been rebuilt to a 4-step shape that mirrors the Bodholdt Backup for Google Drive wizard exactly: Welcome → Connect OneDrive (credentials inline, with a collapsible "How do I get these?" guide) → Schedule → Run Your First Backup with size estimate. The previous 3-step wizard ended on a "go elsewhere" link to the Settings tab — new users routinely got lost there. The new wizard keeps everything in one flow, with the full 5-step OAuth setup guide reachable from the credentials step via a `<details>` panel. State persists across the OAuth round-trip (a new `bodholdt_od_wizard_step` option), so refreshing the page or returning from Microsoft mid-flow picks up where the user left off. Closes Steve Jobs pass Round 1. P1-C.
- New: Wizard returns to the dashboard after OAuth. Previously OneDrive's OAuth success redirected to the Settings tab, which meant the user had to find their way back to the dashboard before the wizard could continue. Now, when a user is mid-wizard, the OAuth callback redirects directly to the dashboard tab so the wizard resumes at the Schedule step automatically.
- New: New AJAX endpoint `bodholdt_od_wizard_save_creds`. Saves Client ID + Client Secret from the wizard's Step 2 and returns the Microsoft authorization URL with a CSRF state nonce. Byte-for-byte compatible with the existing "Authorize OneDrive Access" button on the Settings tab.
- New: "Set up" link on the Plugins page. After activating, a "Set up" / "Backups" quick-link now appears next to Activate/Deactivate in the WordPress Plugins list, giving you one-click access to the dashboard. Standard WP pattern; we should have shipped this in v3. Steve Jobs pass Round 1, P1-A.
- New: Friendlier setup-guide language. Wizard welcome copy and the OAuth Setup Guide on Settings have been rewritten to drop "Microsoft Azure app" / "app registration" jargon in favor of consumer-friendly framing ("free Microsoft developer account", "Microsoft Developer Portal"). Technical terms ("Client ID", "Client Secret", "Redirect URI") stay where they're necessary, but the welcome copy no longer reads like enterprise IT. P1-D.
- New: OAuth Setup Guide Step 5 is split into Save + Authorize. The old single Step 5 packed three actions ("copy, paste+save, authorize") into one line, and new users routinely missed the second click. Now Step 5 saves the credentials and Step 6 authorizes the connection. P2-D.
- New: Backup destination renders as a breadcrumb. The Settings tab's "Backup Destination" line now shows `Your OneDrive → Apps → Bodholdt Backup → <site>` instead of the raw `/Apps/Bodholdt Backup/<site>/` path. Easier for non-developers to map to their OneDrive web UI. P2-F.
- New: Backup Size Estimate auto-loads on dashboard. Mirror of GDrive's behavior — the estimate card now populates on dashboard visit instead of waiting for a manual "Calculate Size" click. P2-E.
- Fixed: Multisite retention manifest + retention setting were per-blog (architectural). The retention manifest (`bodholdt_od_backup_ids`) and the configured backup-retention count (`bodholdt_od_retention`) were stored as per-blog WordPress options, but the OneDrive folder they track is per-account — shared across every blog in the network. On multisite installs, a customer who activated the plugin from a non-main blog would hit a manifest scoped to that blog only; subsequent backup runs under a different blog context (e.g., from network admin, or scheduled cron under blog 1) saw an empty manifest, skipped the cap enforcement, and let the cloud grow silently past the configured retention. Both values are now stored as network-wide `site_option`s, mirroring the v5.8.0 license-storage migration. A one-time on-upgrade helper merges every existing blog's manifest into a single network manifest (de-duped by Graph item ID, so no data loss) and promotes the main blog's retention setting to network scope. Customer impact: multisite users will see retention enforce correctly on the next scheduled backup; single-site users are unaffected (`site_option` transparently falls back to `option` there). Plan §4.2 v5.12.
- Fixed: Manifest seed could only run once per install (Bug C). The function that reconciles the manifest against the actual cloud folder was gated by `if ( ! empty( manifest_get() ) ) return;` — meaning seed only ran ONCE in the install's lifetime, the first time the manifest was empty. After that, no source of cloud↔manifest desync (lookup_item_id silent skip after upload, manual operator file ops, pre-v5.1.1 backups inherited at upgrade time, etc.) could ever self-heal. The function is now a reconciliation pass that runs every enforcer call — it lists the folder once, diffs against the known manifest, and appends only previously-unknown canonical-named cloud files. Healthy installs pay zero option writes per pass; leaky installs self-recover on the next backup. Plan §0a 2026-05-16 evening.
- Improved: Uninstall now cleans up the network-scoped options + transients introduced in v5.8 (license keys) and v5.12 (manifest + retention). Previously these were left behind on plugin uninstall on multisite, harmless but untidy.
- Fixed: Retention seed-migration silently broken since v5.1.1. The `bodholdt_od_manifest_seed_from_folder()` function used `$orderby=createdDateTime` in its Microsoft Graph query, which Graph rejects on the `:/children` endpoint with HTTP 400 "Operation not supported." The function silently fell through and returned with an empty manifest, so installs with desynced manifests (caused by occasional `lookup_item_id` failures after upload) never self-recovered, and cloud backup counts could grow unbounded past the user-configured retention. Removed the unsupported `$orderby` parameter; the enforcer already sorts the manifest in PHP. Customer impact: customers whose backup count had drifted above their configured retention will see the cap re-enforce on the next scheduled backup run.
- New: HTTP-status logging in the manifest seed. Non-2xx Graph responses (token expired, throttled, unsupported parameter, etc.) are now logged to `error_log()` so future Graph-side regressions surface in WP_DEBUG instead of vanishing silently.
- New: Cross-engine collation portability. Backups taken on MariaDB 10.10+ (which defaults to the `utf8mb4_uca1400_*` collation family) now restore cleanly on MySQL 5.7 / 8.x and older MariaDB. The dump rewrites every `*_uca1400_*` collation to the closest portable equivalent (`utf8mb4_unicode_520_ci`, `utf8mb3_unicode_ci`, etc.) at emit time across CREATE TABLE / VIEW / TRIGGER / PROCEDURE / FUNCTION / EVENT statements. Set option `bodholdt_od_translate_collations` to 0 if you need byte-for-byte fidelity (e.g., restoring back to the same MariaDB version).
- New: Progress bar resumes on page reload. Refresh the WP admin tab while a backup is mid-run and the progress UI now picks up where it was — bar, status text, and poll loop all re-attach automatically from the server-side job-status transient. No more "did my backup die?" anxiety.
- New: Default-exclusion patterns for dev artifacts. `apply-bodholdt-*-v*.py`, `__pycache__/`, `*.pyc`, and `*.source.php` are now excluded by default. Pairs with the v5.10.0 readability probe — the probe defends against batch-poisoning regardless, but pre-excluding keeps the manifest tidy and the zip leaner.
- Fixed: Backup-engine drops Bodholdt-prefixed plugins/theme on symlinked-deploy patterns. Common deploy pattern (a separate clone of in-house plugins symlinked into `wp-content/plugins/`) used to silently drop those plugins from backup zips. Three independent root causes contributed; all three close in this release. Plan §4.5.6.
- 1. The relpath calc used `getRealPath()` + `substr($path, strlen(ABSPATH))`, which on symlinked subtrees returned paths outside ABSPATH and produced corrupted relpath entries (e.g., `oldt/bodholdt-wordpress/plugins/...`). Replaced with `RecursiveIteratorIterator::getSubPathname()` (iteration-depth-tracked, symlink-safe) plus a per-scope `relpath_prefix` (`wp-content/plugins/` etc.).
- 2. PHP's `RecursiveDirectoryIterator::hasChildren()` defaults to `$allowLinks = false`, so the iterator emits symlinked DIR entries but never descends into them — the entire symlinked subtree gets skipped silently. Fixed via a small RDI subclass (`Bodholdt_OD_FollowSymlink_RDI`) that overrides `hasChildren()` to pass `$allowLinks=true`. File-level symlinks (e.g. mu-plugins) were always picked up correctly because `isDir()` returns false for them.
- 3. `ZipArchive::close()` silently drops the entire ~500-file staged batch when even one staged file is unreadable (e.g., owner-only 0600 perms; `close()` returns false with a `Permission denied` warning, but PHP discards every legitimate sibling file in the batch alongside the bad one). Pre-validate readability with `@fopen($path, 'rb')` + `fclose()` before `addFile()`; unreadable files are skipped cleanly with optional `WP_DEBUG` logging. `addFile()` and `close()` return values are also checked + logged.
- Fixed: File-collector path corruption for files reached through symlinks. Same root cause as the symlink-relpath bug above. Files reached via a symlinked subtree (e.g., `wp-content/mu-plugins/bodholdt-*.php` on the same deploy pattern) landed in the zip at `oldt/bodholdt-wordpress/mu-plugins/` instead of the canonical `wp-content/mu-plugins/`. The `getSubPathname()` approach produces correct canonical paths regardless of symlink chains. Plan §4.5.7.
- Fixed: VIEW emission order regression in multisite-network mode. v5.9.0 emitted each blog's views at the end of that blog's iteration, BEFORE the network-shared group ran. Views that JOIN `wp_users` (network-shared) failed at CREATE time on restore because wp_users hadn't been created yet. All non-table schema objects (views, triggers) are now deferred to the absolute tail of the dump, after every CREATE TABLE across every blog + the network-shared group. Plan §4.5.8.
- New: Proper self-exclusion. The backup zip no longer includes the Bodholdt Backup for OneDrive plugin's own directory (Russian-doll prevention). Sibling Bodholdt plugins and the bodholdt-labs theme are deliberately INCLUDED so customers with multiple Bodholdt plugins get a complete backup. Self-exclusion uses canonical-relpath equality, not name-prefix substring matching.
- New: mu-plugins and other non-scoped wp-content children are now backed up. Previously the "core" scan-and-skip rule excluded everything under wp-content. Relaxed so only the three scope-mapped subtrees (`wp-content/plugins/`, `wp-content/themes/`, `wp-content/uploads/`) are skipped during the core scan; `wp-content/mu-plugins/`, `wp-content/languages/`, etc. now land in the backup at their canonical paths and restore to where they belong.
- New: Unreadable files are skipped cleanly instead of poisoning a 500-file batch. Previously, ZipArchive's deferred-read behavior meant a single 0600/0700 file (e.g., a forgotten dev artifact) could silently drop dozens of unrelated legitimate backups. The pre-`addFile` readability probe defends against this regardless of what's on disk.
- New: Multisite-network backup mode (Free-tier-included). The backup engine now auto-detects single-site vs multisite and defaults to a complete-network backup on multisite installs. Network mode walks every blog via `switch_to_blog()`, dumps each blog's content tables, then dumps the network-shared tables (`wp_users`, `wp_usermeta`, `wp_blogs`, `wp_blogmeta`, `wp_site`, `wp_sitemeta`, `wp_registration_log`, `wp_signups`) exactly once. Restoring such a backup recreates the full multisite layout.
- New: Per-blog mode (opt-in). Multisite admins who want per-customer subsite isolation can switch to per-blog mode in the network admin's Settings tab. Per-blog backups contain only that blog's content tables — no shared tables, no other blogs.
- New: Backup manifest (`bodholdt-manifest.json`). Every zip now includes a manifest at the zip root describing mode, plugin version, WP version, blog topology, tables dumped, and shared-table inclusion. The restore engine reads it first to verify compatibility.
- New: Restore-side mode compatibility check. The engine refuses to restore a multisite backup onto a single-site install (the wp_N_* tables don't belong) and warns when a single-site backup is being restored onto a multisite (data lands on current blog only).
- Single-site installs continue to behave exactly as before — no UI changes, no feature flag, no migration required.
- Database export entry function now accepts an optional `$mode` parameter for callers that want to force a specific mode regardless of saved settings.
- New: Multisite-network compatible. License state (key, tier, status, last-seen-valid timestamp) is now stored as a network-wide site option. A customer who activates a Solo license on blog 1 of a multisite network has the license recognized automatically on every other blog. One-time migration runs on upgrade — existing per-blog license data is copied to network scope.
- Single-site installs are unaffected (site_option falls back to option there).
- New: Slack and Discord webhook notifications (Pro+). One URL field, auto-detected provider. Per-event toggles for backup success, backup failure, restore initiated, and watchdog stale-alert. Send-Test-Notification button in Settings.
- New: Helper `bodholdt_od_notifications_allowed()` returns true only for Pro/Bundle. Filterable via `apply_filters( 'bodholdt_od_notifications_allowed', $allowed, $tier )` for one-off overrides.
- Failure notifications include the same auto-diagnosis hint as the email notification (token / quota / disk / network).
- New: Free tier is now the default experience. No license required for full backup + restore at the Free cap (10 retained).
- New: Tier badge inline in the plugin header (Free / Solo / Pro / Bundle).
- New: Lapse banner — when a previously-active license becomes invalid, a dismissible banner reminds you to renew while backups continue at Free-tier limits.
- New: Soft-degrade on lapse — backups keep running even with an invalid license. Retention auto-clamps to 10; selective restore disables. Manual + scheduled backups continue.
- Removed: Three hard-locks that previously blocked the admin UI, manual backups, and scheduled backups on non-valid licenses.
- New: Tier-gated selective restore. Pro and Bundle customers can choose what to restore (database, plugins, themes, uploads). Free + Solo restore everything.
- New: Inline "Upgrade to Pro" hint shown next to disabled scope chooser on Free + Solo.
- Server-side defense: restore handler coerces submitted scope to full on tiers without selective access.
- New: Tier-aware retention cap helper (`bodholdt_od_retention_cap`). Free=10 / Solo=50 / Pro=100 / Bundle=100. Save handler clamps; settings input cap is dynamic.
- New: Over-cap inline notice when a previously-saved retention value exceeds the current tier cap (e.g., after a downgrade).
- New: Pre-flight `force_refresh()` before retention enforcement so tier downgrades take effect within one backup, not 12 hours later.
- Licensing client now captures `tier_key` from server responses.
- Fixed: Rename-bypass in the retention enforcer. Previous folder-listing + regex enforcer let a renamed backup file slip past the cap entirely. Replaced with an ID-manifest written at upload time — renames can no longer bypass retention.
- New: One-time seed migration on upgrade.
- Last-success-timestamp watchdog with stale-alert.
- Out-of-web-root staging directory (fixes a 2026-05 SQL-dump exposure risk).
- Retention pagination + ordering fix — folders with more than 200 backups now prune correctly.
- Settings save reliability fix.
- Email-delivery failure detection. Plain-text email fallback. Migration key now included in success emails.
- Setup wizard, selective restore, pre-restore snapshot, ZIP integrity verification, atomic locking, contextual help tabs.
- Initial Bodholdt-Engine rewrite. OAuth 2.0. Resumable Graph API uploads. AES-256-CBC + Sodium encryption. Migration installer.