Bodholdt Backup for Google Drive
Automated WordPress backups to Google Drive
Get Bodholdt Backup for Google Drive
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, so you choose what to restore (database, plugins, themes, uploads, core)
- 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:
- Up to 127 sites
- Priority email support while active
See full feature comparison →
| Feature | Free | Solo | Pro | Agency |
|---|---|---|---|---|
| Sites | 1 | 1 | 5 | 127 |
| Full plugin functionality | ✓ | ✓ | ✓ | ✓ |
| Full restore | ✓ | ✓ | ✓ | ✓ |
| Backup retention | Unlimited | Unlimited | Unlimited | Unlimited |
| Selective restore | — | ✓ | ✓ | ✓ |
| Slack & Discord alerts | — | — | ✓ | ✓ |
| Lifetime license option | — | ✓ | ✓ | ✓ |
| Support | Documentation | Email while active | Email while active | Priority email |
Start free with the Lite edition
Full automated backups and full restore, with unlimited retention, free forever. Upgrade to Solo for selective restore, or Pro for multi-site and Slack/Discord alerts.
Get Bodholdt Backup for Google Drive (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 Google Drive
These are real admin screens, not mockups. Click any one to view it full size.
Automated WordPress backups to Google Drive with selective restore, setup wizard, and a cyberpunk-themed admin panel. Built with accessibility in mind (keyboard navigation, reduce-motion gating, ARIA semantics) and security-hardened against XSS.
Streaming restore that stays FK-safe and BLOB-safe
Full and incremental backups, scheduled or on demand
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
Pre-restore safety snapshot
Guided setup wizard with an OAuth walkthrough
Sentinel: The Night Watch arcade, with an Unbroken Watch streak and a live protection shield for every clean backup
- Streaming restore that stays FK-safe and BLOB-safe
- Full and incremental backups, scheduled or on demand
- 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
- Pre-restore safety snapshot
- Guided setup wizard with an OAuth walkthrough
- Sentinel: The Night Watch arcade, with an Unbroken Watch streak and a live protection shield for every clean backup
- Scheduled database backups no longer fail when a security plugin rebuilds its temporary table mid backup. Some security tools create and then drop a temporary scratch table while your site is running. If that happened partway through the database export, the backup stopped with a "table definition has changed" message and only finished on the next scheduled run, so you saw a failure followed by a success. That scratch table holds nothing worth restoring, so it is now left out of the backup entirely, and any brief database hiccup during a backup now retries on its own up to three times instead of ending the run. Nothing for you to change.
- Clearer restore confirmation before anything is overwritten. The restore panel now says plainly that restoring replaces your current files and database, that the automatic safety copy covers your database only and there is no one-click undo, and that your site briefly shows a maintenance page while a restore runs. If a restore is interrupted, the panel reminds you the site reopens on its own within about ten minutes, so you are not left wondering. Starting a restore now asks you to tick "I understand this will overwrite my current site" before the final button turns on, matching the OneDrive backup plugin. No change to how restore itself works.
- Backups now restore on sites running both Bodholdt backup plugins. If you have both the Google Drive and OneDrive backup plugins installed, every archive included the other plugin's files, and restore refused those archives outright because it is designed never to overwrite a backup plugin mid restore. The result was backups that always reported success and could never be restored. Archives taken from this version on restore normally. If you run both plugins, please take a fresh backup after updating so you have an archive that restores.
- Please take a fresh backup after updating if your site runs MySQL 8. Columns whose default is the current timestamp were being left out of the database export. Many plugin tables use these for dates like "created" and "last updated", including WooCommerce order analytics. Backups still reported success, and on restore those dates were refilled with the time of the restore rather than the original values, so the data looked plausible but was wrong. Archives created before this update are affected on MySQL 8. Sites on MariaDB were never affected. One new backup after updating replaces the gap.
- Backups no longer stop when your site contains a symbolic link. One linked theme, plugin folder, or single linked file inside uploads used to end the entire run, and the failure email suggested checking your server logs or internet connection, which had nothing to do with the real cause. Links are now skipped, the rest of your site is backed up normally, and the run tells you exactly which links were left out. This is common on managed hosting, on container based setups, and on any site using a linked child theme or a shared media mount.
- An interrupted restore can no longer leave your site stuck offline. If a restore was killed partway through, by a memory limit, a timeout, or a server restart, WordPress was left in maintenance mode permanently. Both the site and the WordPress admin returned "Briefly unavailable for scheduled maintenance" forever, and the only way out was deleting a file over SSH or FTP. A running restore still closes the site as it should, but an abandoned one now reopens on its own after ten minutes so you can get back into your admin and try again.
- A stalled restore no longer blocks every future backup with no way to clear it. The Force Unlock button only ever appeared for stalled backups, never for stalled restores, so a restore that stopped unexpectedly left backups permanently blocked with the fix hidden from view.
- Clearer, more honest failure messages. Running out of local disk space is no longer reported as your Google Drive being full, and no longer suggests deleting your backups, which would not have helped. The size estimate on the dashboard now tells you what actually went wrong instead of only "Could not calculate estimate." Internal log tags no longer appear in messages meant for you.
- File exclusions now support wildcards. Patterns like `*.log` and `wp-content/cache/*` previously matched nothing at all and gave no warning, so files you thought you had excluded were still being uploaded.
- Backup notification emails are now translatable and no longer arrive in English on a translated site. They also fall back to plain text if the HTML message cannot be delivered, and a delivery failure is recorded instead of passing silently.
- Retention problems are now visible. If old backups could not be removed from your Drive, the run said nothing and storage kept growing. It now tells you.
- Also in this release. A warning when the ZIP extension your host needs is missing, rather than a silent failure. A working admin menu when the plugin is activated on a single site of a multisite network. Large downloads no longer time out on a blank page, and a download that was merely interrupted is no longer reported as a corrupt archive. The setup wizard no longer says setup is complete when the server rejected the save.
- Sharper error text in the activity log. Error status labels and their suggested fixes now meet WCAG 2.1 AA contrast against the dark log rows, so they are easy to read at a glance. No change to how backups or restores work.
- More accessible admin. The dashboard now meets WCAG 2.1 AA: error text and the button focus outline have stronger contrast, and the optional celebration pop-ups are announced to screen readers, can be dismissed, and pause when you hover or focus them. No change to how backups or restores work.
- Your cloud backups no longer contain your credentials on some sites. If your database table prefix has no trailing underscore, such as `wp` instead of `wp_`, the filter that keeps live credentials out of uploaded archives did not match your options table. Your Google client secret, refresh token, license key, and WordPress security salts were written into the backup. Redaction now works with any valid prefix. If your site uses a prefix like this, please delete your existing cloud backups and create a fresh one.
- Binary and case-sensitive collations survive a restore exactly. Backups previously rewrote collations such as `utf8mb4_0900_bin` and `utf8mb4_ja_0900_as_cs` to a case-insensitive one, which silently changed sorting, uniqueness, and case-sensitive lookups on the restored site. They are now preserved exactly, in every place the plugin reads or writes a collation.
- Backups record your real database schema. Collation rewriting at backup time is now off by default, so an archive is a faithful copy of your source schema. If a restore lands on a server that cannot support one of your collations, the plugin adapts at restore time instead, where it can see what the destination actually supports. If you restore a dump by hand with the `mysql` command onto a different database engine, switch the setting back on before taking that backup.
- Backups no longer fail on valid database schemas. A site with a view or table that uses `REPLACE()`, `TRUNCATE()`, or `INSERT()` as a function was blocked from backing up at all. Those are now read as functions rather than as dangerous statements, and genuinely unsafe statements are still rejected.
- Restore no longer writes credentials to your error log. If a statement failed while a restore was rolling back, WordPress logged the whole statement, including options rows holding your live credentials. Failures are now recorded without the statement text, and errors are still detected and reported to you.
- Your license is no longer removed when you upgrade. On single-site installs a one-time migration deleted the stored license key, status, and tier, quietly dropping paid installs to the free feature set. It now does nothing outside multisite, and it can be run repeatedly without harm.
- A stuck backup can no longer leave the plugin unusable. If a backup process was killed, the operation lease stayed held, listing and restore stopped working, and after an hour the Force Unlock control disappeared, leaving no way to recover without server access. The stale-lock warning and its Force Unlock button now follow the real lock state, and the message explains what actually happened.
- The settings screen always opens. A corrupted saved option could stop the page mid-render, so the form and its save button never appeared and there was no way to repair it from the admin.
- A site with no schedule stays unscheduled. Google Drive treated a missing schedule as daily and armed an automatic backup even when the saved setting was "none".
- Exact OAuth continuity after restore. Access-token refresh now preserves an existing option row's raw autoload metadata while changing only its encrypted value, avoiding a false restore failure on legacy WordPress rows.
- Self-contained Fast Backup restore. Removed an obsolete post-restore base marker. Fast Backup restore already downloads, authenticates, hash-links, and privately composes its exact Full base before maintenance mode or customer-data mutation.
- Mobile and keyboard-safe admin. The Sentinel HUD no longer overflows narrow screens; wizard step focus stays inside the visible modal, and Escape persists dismissal while restoring scrolling and focus.
- Correct page behavior. Visiting License no longer runs the Dashboard size estimate or locks page scrolling, and inline restore controls no longer trap keyboard focus.
- Honest copy and setup actions. Clipboard success appears only after a confirmed copy, failures are reported, setup and schedule controls have explicit labels, and OAuth/connection-expiry guidance matches the real same-tab and visible-error behavior.
- No partial SQL after a failed export. Snapshot, storage, truncation, empty-export, and rejected nontransactional-table failures now remove the incomplete local database file before returning an error.
- Safe Dashboard metadata. Legacy object- or array-valued log fields are ignored before the Dashboard renders them, matching the hardened Logs table.
- Signed file permissions and exact rollback. On POSIX backup hosts, new backups authenticate every managed regular-file mode alongside its bytes, including mode-only Fast Backup changes and deletions. Other hosts authenticate that exact mode authority is unavailable and cannot provide an exact-mode Fast Backup base. Restore and automatic rollback re-prove the exact regular-file inode and bytes around permission application, then finalize directory modes.
- Safe compatibility behavior. On a POSIX restore host, older authenticated backups use a clearly labeled, non-deleting overlay that preserves existing modes and gives new files owner-only permissions. On a non-POSIX host, filesystem restore stops before mutation because exact permission rollback cannot be guaranteed; database-only restore remains available. Fast Backup switches to Full when its verified cloud base lacks exact permission authority and records the reason in Logs.
- Unambiguous recovery records. Recovery, rollback, operation-lease, and cloud-folder JSON rejects duplicate decoded object keys, including escaped aliases, before PHP can collapse them into a different authority.
- Action after upgrading: Create a new Full backup before relying on Fast Backup or exact file-permission recovery. Older recovery points remain available through the degraded compatibility path.
- Safe partial legacy log rows. Older metadata that contains only a backup size or file count now renders with a harmless placeholder instead of a PHP warning; nonscalar display fields are ignored safely.
- Clean legacy Logs screens on modern PHP. Older log rows with empty or malformed metadata are displayed safely without PHP 8.5 deprecation notices on the Dashboard or Logs tab.
- Database consistency cannot be weakened by another plugin. Only InnoDB/XtraDB tables and the exact rebuildable Wordfence role-count cache are accepted. Other MEMORY/MyISAM material tables stop safely, and every customer-facing suggestion correctly names the InnoDB remedy even when the table name contains words such as token, quota, zip, or disk.
- Settings save before connection. A fresh site can save its schedule, retention, scope, and exclusions with blank Google OAuth fields. Credentials are validated only when you actually enter or change them, and failed credential changes still roll back safely.
- Stricter restore connection verification. A legitimate Google access-token refresh can update only the encrypted token value and clear a stale auth warning; unexpected autoload or unrelated provider, licensing, destination, schedule, or multisite changes still stop the restore safely.
- Wordfence no longer blocks safe backups. The rebuildable `wp_wfls_role_counts` MEMORY cache is preserved as an empty InnoDB table for Wordfence to repopulate; any other non-transactional material table still stops the backup clearly.
- Multisite recovery boundaries are explicit. Every primary-site table must have one unambiguous owner, and a Network backup records the one network and all of its sites. Ambiguous tables, multi-network databases, and older archives without the new signed recovery authority are refused before anything is changed.
- Action after upgrading: Create a new Full backup in Network mode before relying on multisite restore; older network recovery points do not contain the authority required by this release.
- Full/core restore safety snapshot fixed. Canonical WordPress core directories are now captured and verified correctly before restoration, so a valid full restore no longer stops at the rollback-snapshot gate.
- Safer setup and connections. Settings and setup-wizard changes now commit and verify as one transaction or roll back exactly. Concurrent administrators can connect safely, stale OAuth callbacks are bounded and invalidated, and legacy credentials, tokens, and licenses cannot overwrite a newer valid change during upgrade.
- Restore rollback and storage protection. Pre-restore snapshots bind exact no-follow file copies to content digests, reject filesystem drift and link/type swaps, verify the complete private rollback image before mutation, and preserve a 64 MiB emergency disk reserve.
- Exact rollback and Google connection continuity. If restore health checks fail, rollback now reinstates root-level, empty, and Unicode-named files exactly and removes restore-only files. A verified Google token refresh no longer makes an otherwise healthy restore look failed, while every unrelated continuity check stays fail-closed.
- Restore progress reconnects safely. A temporary maintenance-mode response no longer turns the progress screen red or stops polling. The page reconnects automatically, and final health failures expose only safe diagnostic codes rather than site or provider details.
- Recoverable database exports on MySQL and MariaDB. Dumps preserve the disk reserve, create dependent views in a safe global order, reject incomplete or ambiguous view graphs, and stop clearly when triggers, routines, or events cannot be represented instead of silently omitting them.
- Hardened packages, cleanup, and privacy. Release builds reject unsafe source/ZIP content before atomic publication. Uninstall removes only Google-owned state, operator notices require the backup capability, and diagnostics no longer expose cloud-account identifiers, response bodies, provider URLs, or customer filesystem paths.
- Database recovery points are trustworthy again. Literal percent signs, percent-encoded URLs, CSS, SQL patterns, and serialized data now round-trip byte-for-byte. Every export uses one isolated InnoDB consistent snapshot; non-transactional tables and metadata omissions stop the backup with an actionable error instead of producing a mixed or incomplete recovery point.
- Migrations fail safely and finish accurately. Empty or failed imports cannot show green success, same-host multisite moves preserve every blog's domain and path, serialized objects stay opaque, and the generated `wp-config.php` must pass exact semantic verification. SQL and manifest files live in a protected, key-bound hidden directory from extraction onward.
- Archives and operations are integrity checked. Owner-token leases survive an options-table restore, large ZIP members extract in heartbeat-sized chunks with size/CRC checks, unsafe paths and special entries are rejected, and missing requested files or mandatory recovery members fail before upload.
- Settings and cleanup match the UI. All webhook triggers can be disabled, weekly schedules keep their weekday through DST, multisite defaults reset fully, deliberately blank webhook tests are rejected, and uninstall removes plugin-specific transients plus Google Drive rows from every legacy subsite log table without removing OneDrive rows. Missing subsite arcade tables are skipped without database errors.
- Restores you can trust, right on the screen. A restore that could not write your core WordPress files now says so instead of showing "Restore complete", and restoring an incremental backup over your existing site no longer reports a false "nothing was restored". Any compatibility caution now appears on the restore screen itself, not only in the log.
- Migrations that finish the job. Moving a multisite network keeps every subsite on its correct address, saved nested settings survive a domain change, and backups taken on newer MySQL versions restore cleanly onto older database servers.
- Protected recovery evidence. A successful migration removes its database file and manifest. A failed migration retains them for retry and diagnosis; 6.33.1 moves them into a protected random directory from extraction onward. Removing the plugin from a large multisite network now cleans every site, not just the first hundred.
- Notifications and scheduling polish. The "Send Test" button now tests the webhook address you just typed, "Reset to Defaults" truly resets everything, a weekly schedule no longer triggers a daily false "backups are stale" alert, and your backup keeps its chosen time through daylight saving changes. Plus a long list of smaller reliability, safety, and translation fixes. Nothing about how your everyday backups run changes.
- Migrations you can trust on any host. Moving a site to a new server now keeps every table even when your host uses a custom database prefix (common on managed hosting), so a migration no longer lands on a blank WordPress setup screen. Multisite network settings carry across too, and the one click site mover deletes its database file the moment it finishes instead of leaving it in your web root.
- Backups and restores that tell the truth. A backup now stops and warns you if the database export was cut short, instead of quietly saving an incomplete copy. A restore that had nothing to put back tells you which parts were empty rather than showing a cheerful "all done". And an incremental backup is clearly labeled, so restoring one on its own can never rebuild a broken site by surprise.
- Notifications and licensing polish. Saving your settings before you enter a license no longer clears your Slack or Discord alert choices. Activating a valid key while our server is briefly unreachable now says exactly that, rather than claiming your key is wrong, and a slow licensing server no longer makes your Plugins screen hang.
- Calmer by default, and much more. The Sentinel arcade now has its own settings and confetti is off unless you turn it on, the nightly watchdog reliably warns you if backups go stale, and there is a long list of smaller fixes and tidy ups. Nothing about how your everyday backups run changes.
- Reliability and safety hardening (QA round). Saving Settings now reliably persists every change, restores report their status honestly, the database export stops rather than truncating on an error, and automatic backups re arm correctly after the plugin is switched off and on. Plus a range of smaller migration, retention, and cleanup fixes. No change to how your everyday backups run.
- Restore you can trust. Restores now tell you the truth: if any part of the database or files could not be applied, you get a clear "finished with problems" message that points you to the pre-restore snapshot, instead of a false "Restore complete!". Cross-version moves are smoother too, so a backup taken on a newer MySQL restores cleanly onto an older MySQL or MariaDB.
- More reliable migrations. The standalone migration installer now works correctly on modern PHP (8.1 and newer), keeps your real site configuration and table prefix (custom prefixes and multisite restore properly now), and imports stored procedures and triggers without dropping them.
- Backups that hold up on big sites. Long backups on large sites no longer risk colliding with another run, generated columns and special column types (BIT, spatial) back up and restore correctly, and every row is captured even in unusual tables.
- Clearer and safer everywhere else. Honest schedule status on a fresh install, an accurate description of per-site multisite mode, important warnings now visible to multisite network admins, saved credentials no longer loaded on every page, a cleaner reconnect prompt when Google access expires, tidier file-exclusion matching, and a range of smaller polish and cleanup fixes.
- Backup safety and robustness hardening. Backups are now guarded against overlapping runs, so a scheduled backup and a manual backup or restore can never run at the same moment and leave you with an inconsistent archive. We also tightened how backup file names are shown in your Backups list, made the Settings save shrug off unusual form input instead of erroring, finished the cleanup of network settings when you remove the plugin from a multisite network, and made activation safer around an old backup folder. No change to how your backups run day to day.
- Security and cleanup hardening. Tightened the Slack/Discord webhook so notifications only ever reach real Slack and Discord addresses. Hardened the bundled one-click migration installer so its dormant copy can never run in place. Removing the plugin now fully cleans up after itself: settings, stats, gamification data, and scheduled tasks all clear. Also quieted a repeated staging-folder log line on some hosts, corrected a few translation labels so the whole interface localizes, and refreshed some onboarding wording. No change to how your backups run.
- Arcade engine polish (shared). Protection XP now counts correctly when several events land at the same moment, the on-screen HUD labels are fully translatable, and celebration cards always stay within the screen. No change to how your backups run.
- Sentinel: The Night Watch. Your backups now earn their keep. A calm HUD shows your protection level and an Unbroken Watch streak that grows every night a scheduled backup runs clean, with a live Shield gauge for how protected you are right now. Trophies mark the milestones. It stays quiet by design: sound is off, the mood is steady rather than loud, and one switch turns it off. Nothing about how your backups run changes.
- Connection card leads with status. Once connected, Settings shows your connection status first, with Test Connection and Disconnect beside it; the credentials tuck behind "Change credentials".
- Backup Size Estimate is now a table with a proportion bar per component and a totals row, instead of a plain text list.
- Your Backups rows now lead with the date and size, with the file name below plus a one-click Copy. Restore is styled distinctly from Download so the two are easy to tell apart.
- Activity log filters are one-click chips (All, Success, Errors) with live counts, and long paths get a Copy button.
- Reset to Defaults moved into a clearly marked danger zone. Accessibility and contrast improvements throughout, and emoji removed from admin messages.
- Privacy & transparency. Added an "External services" section to this readme, and self-hosted the admin font so no request is sent to Google Fonts.
- Renewal UX. If a license lapses, the Plugins screen now shows a "renew to install this update" note instead of a failed update.
- Brand-string cleanup (author/URI, translation template). No functional or backup-behaviour change.
- Corrected support and upgrade links. The contextual-help "Email Support" link now points to [email protected], and the in-admin upgrade prompts link to the product page instead of the account portal.
- Accurate tier copy. Upgrade hints and the readme now match what each tier actually includes: retention is unlimited on every tier, including Free, and selective restore is a Solo feature (not Pro). Documentation and labelling only. No functional or backup-behaviour change.
- More emerald polish. "Start Backup Now" cloud icon is now pure white; the "Your Backups" table (column headers, file/Download/Restore icons, row hover) is now emerald instead of cyan/purple; in-app "Upgrade to Pro" notices and the troubleshooting path now use the emerald accent.
- Logs toolbar. The "Apply" filter button is now a gradient button and "Clear History" is clearly marked red as a destructive action.
- Admin polish. Dashboard "Success" and "Connected" status pills now use the emerald theme accent; "Refresh Estimate", "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.
- Fixed "Your Backups" showing empty. The plugin could read a stray duplicate "Bodholdt Backups" folder in Google Drive and report no backups even when backups existed. It now reliably resolves the original folder (oldest match) and will no longer create a duplicate folder if a Drive lookup briefly fails.
- Consistent buttons. The "Start Backup Now" button now uses the same green gradient as the "Save Settings" button, so the primary actions match across the admin.
- Green admin theme. The Google Drive admin now uses an emerald-green accent that matches its product page, so it reads as distinct from the cyan Bodholdt Backup for OneDrive admin at a glance. Same layout and dark canvas; accent colour only.
- 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. This release brings the Google Drive admin up to full parity with the OneDrive edition: entrance animations on cards/modals/toasts, standardized title/progress gradients, a tooltip layering fix, the upsell/locked-feature polish, and an accessible purple text colour. No functional or behavioural change.
- Pro edition with licensed auto-updates. This is the paid, off-directory build of Bodholdt Backup for Google Drive, 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 Google Drive / Google OAuth endpoints, the Bodholdt Labs licensing/update server, and the WordPress.org secret-key (salt) request used during cross-server migration.
- Fixed: Duration column in the Logs view showed a stray trailing "s" (e.g. "2 minutess" instead of "2 minutes"). The duration string from `human_time_diff()` already includes the unit word, but the Logs table cell appended an extra `'s'`, a leftover from when duration was rendered as a numeric-seconds value. The cell now renders the duration string as-is. Cosmetic only. No data, scheduling, or backup behavior changes.
- 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 v6.20.0 footer line (Bodholdt Backup for Google Drive v6.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 GOOGLE DRIVE V6.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 and most established plugins handle this.
- Brand consistency follow-up. The License sub-page heading still showed the short-form "Bodholdt G-Drive" label after the v6.19.0 rebrand. Now reads "Bodholdt Backup for Google Drive: License" to match the rest of the plugin. No behavior change.
- Renamed to "Bodholdt Backup for Google Drive." 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: inline OAuth setup guide. Step 2 of the first-run wizard now spells out exactly how to create the OAuth Client ID and Client Secret in Google Cloud Console, including the redirect URI you need to paste, inline with a copy-to-clipboard helper. Previously this guidance lived only in the Settings tab, and the wizard alone left first-time users without enough information to complete the connection.
- Setup wizard: improved "Testing on a local site?" callout. The advisory now correctly explains Google's policy that redirect URIs must be either `http://localhost` or HTTPS at a public TLD (`.com`, `.org`, etc.), so `.local` development sites (Local by Flywheel, MAMP) are rejected even with HTTPS enabled. Recommends using Local's "Live Link" feature for an HTTPS public URL when testing.
- 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 Google Drive (identified by a per-site ID), so multiple WordPress sites sharing one Google 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) and use the correct product reference, so license keys validate reliably (a mismatch previously prevented validation). 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 + consistency pass. Brings the celebration moments, accessibility, and copy up to full parity with Bodholdt Backup for OneDrive, plus a few new-for-both delights.
- New: cumulative "backups protected" stat. The Backup Health card now shows a running total ("N backups protected · X GB kept safe"), counted from a dedicated counter so it survives clearing your activity log.
- New: first-backup celebration. Your very first successful backup now gets a bigger one-time celebration ("Your first backup is done. Your site is officially protected!").
- New: restore-complete celebration. Finishing a restore now greets you with a gentle celebration and a "Site restored. Welcome back." confirmation.
- Richer backup-complete celebration: multi-burst branded confetti, a green-to-teal glow on the progress bar, and a success notification (matching Bodholdt Backup for OneDrive).
- Accessibility: confetti now respects "reduce motion." All celebration animations are skipped when your system requests reduced motion. Tab navigation also gained proper ARIA tablist semantics for screen readers.
- Friendlier first-run guidance: the connect-success banner is now warmly themed with a "Run First Backup →" button; the Start Backup button is disabled with a "Connect your Google Drive first, then open Settings" prompt until you've connected; and a "Your site stays online during the backup." reassurance now sits under the button.
- Clearer empty states + copy: the activity log empty state is now two reassuring lines; health-card placeholders read "Ready to start" / "Awaiting first backup"; and destructive confirmations now state exactly what happens and what's reversible.
- Help sidebar now links to the plugin homepage and email support. Page heading standardized to "Bodholdt Backup for Google Drive."
- Cross-plugin parity follow-ups.
- "Reset to Defaults" now restores a sensible Daily 3:00 AM backup (both backup plugins now reset to the same default). And a scheduled backup on a site that hasn't connected its cloud account yet now skips quietly, instead of emailing a daily "backup failed" alert.
- Removed the extra pre-backup confirmation dialog: clicking "Start Backup Now" begins immediately, matching Bodholdt Backup for OneDrive's one-click flow. The "Force Full Backup" option is still right there as an inline checkbox above the button.
- 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 OneDrive. Bundles three internal batches:
- *Error handling (R2):* OAuth failures now show a friendly, recoverable banner instead of a blank WordPress error page; an expired or revoked Google Drive authorization now prompts you to reconnect instead of showing "no backups yet"; a dropped network request during a backup or restore no longer freezes the progress bar silently (you get a clear error + retry path); a mistyped notification email is no longer saved silently; cancelling at Google's consent screen is handled gracefully; backup-download errors are now friendly with a back link.
- *Settings (R3):* added Notification-Email and Backup-Type help tooltips, a copy-paste real-cron helper, a friendlier Google Drive destination display, and clearer "Backups to Keep" / "Migration Tools" / file-exclusion copy.
- *Polish (R4/R5):* modern navigation guard during in-progress backups, a green "saved" confirmation, 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-v6.15.0 the plugin defaulted `BODHOLDT_GDRIVE_DIR` to `/var/lib/bodholdt-staging/gdrive/<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…", with no error to the user. v6.15.0 adds a three-tier fallback hierarchy resolved at plugin-load time: (1) `/var/lib/bodholdt-staging/gdrive/<suffix>/` if writable (preserves the post-2026-05-05 security model on operator-prepared hosts), (2) `sys_get_temp_dir() . '/bodholdt-staging-gdrive/<suffix>/'` if `/var/lib/` isn't writable (system temp, ephemeral but still isolated from the web document root), (3) `wp-content/uploads/.bodholdt-staging-gdrive/<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_GDRIVE_DIR', __DIR__ . '/your/custom/path/')` in wp-config.php to bypass the resolution entirely. Plan §4.5.13.
- Fixed: Backup engine was silently omitting WordPress drop-ins (`object-cache.php` / `advanced-cache.php` / `db.php` / `maintenance.php`) at the root of `wp-content/`. v6.14.1 closed the matching gap for `wp-content/mu-plugins/` and v6.14.2 closed the matching gap on the restore side for drop-ins, but the backup engine itself never added drop-ins to the zip. Its scope-dirs helper enumerates directories only, and drop-ins are single files. v6.14.2's restore-side `is_file()` guard masked the gap by degrading quietly (file missing from temp dir → no error → drop-in silently absent from the destination). v6.15.0 adds an unconditional drop-ins block to the backup engine (symmetric to v6.14.1's mu-plugins always-include treatment) with `is_file()` guards so installs without drop-ins degrade quietly. Drop-ins now ride through end-to-end on the backup/restore round-trip. Plan §4.5.19.
- 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). v6.14.1 closed the matching backup-side gap so mu-plugins now ride along inside the zip, but the restore engine still only copied `themes/`, `plugins/`, and `uploads/` from the extracted backup to the destination's `wp-content/`. Result: any disaster-recovery restore from any prior version 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. Plan §4.5.15.
- Fixed: Restore engine no longer unconditionally drops your other Bodholdt backup plugin. Prior versions hard-skipped both `bodholdt-google-drive-backup` AND `bodholdt-onedrive-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.
- Fixed: `wp-content/mu-plugins/` was silently omitted from every backup. The backup engine's scope-dirs helper enumerated four directory scopes: `core` (wp-admin + wp-includes only), `themes`, `plugins`, and `uploads`. It had no entry for mu-plugins. Result: every GDrive backup zip was missing must-use plugins entirely. Restoring from one of these backups would leave the destination site without any of its mu-plugins (custom auth handlers, debug-log filters, multisite-shared utilities, etc.), silently corrupting site behavior. Surfaced 2026-05-17 during the §4.5.5 GDrive parity retest. OneDrive's wholesale ABSPATH `core` scan picked them up as a side effect, masking the bug across plugins until this retest. Fix: mu-plugins are now treated like the database (always included, no user-facing scope toggle, no way to accidentally omit). Plan §4.5.11.
- Note on Bodholdt Backup for OneDrive: Bodholdt Backup for OneDrive v5.14.0 is not affected. Its `core` scope walks `ABSPATH` wholesale with skip-prefixes for plugins/themes/uploads, so mu-plugins were already included via the wider walk. No OneDrive hotfix is required.
- New: Welcome step added to the setup wizard. The wizard now opens on a friendly Welcome screen ("Let's get your site backed up to Google Drive in just a few steps") instead of dropping the user straight into a Client ID / Client Secret form. The 3-step shape becomes 4-step: Welcome → Connect Google Drive → Schedule → Run First Backup. Mirrors the new Bodholdt Backup for OneDrive v5.14.0 wizard structure for cross-plugin consistency. Closes Steve Jobs pass Round 1. P1-B + P1-C.
- New: Wizard JS state machine refactored. Step navigation is now driven by a single `showWizardStep(n)` helper that handles step visibility, dot indicators, and the step-counter text consistently. The previous per-step JS handlers had subtle drift; this refactor unifies them.
- 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 v4. 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 "Google Cloud credentials" / "Google Cloud Console" jargon in favor of consumer-friendly framing ("free Google project", "Google Cloud (free)") that explains *why* each step is needed. 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 consent-screen step is now correctly ordered. The Setup Guide previously listed "Configure Consent Screen (if prompted)" as Step 4, but Google requires consent-screen configuration before they'll let you create credentials at all, so it's *always* prompted on a new project, not optional. The step is now Step 3 (before Create Credentials), the "if prompted" misleading wording is gone, and the External-vs-Internal user-type choice is called out explicitly. P2-C.
- New: OAuth Setup Guide Step 5 is split into Save + Authorize. The old single step packed three actions ("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.
- Fixed: "Could not calculate estimate" error on the dashboard. The pre-flight size estimator's "Core Root Files" section called `new DirectoryIterator($root)` against a never-defined variable, which threw a PHP fatal on every dashboard load and the Refresh Estimate button. Vestigial reference left over from the v6.10 `getSubPathname()` refactor. Defined `$root` from `ABSPATH` the same way the scope-dirs helper does. Single-line fix, no behavior change beyond the estimate now actually rendering. OneDrive was unaffected (its core-scope walk handles root files in the main iterator).
- Fixed: Multisite retention manifest + retention setting were per-blog (architectural). The retention manifest (`bodholdt_gdrive_backup_ids`) and the configured backup-retention count (`bodholdt_gdrive_retention`) were stored as per-blog WordPress options, but the Google Drive 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 could see an empty manifest, skip 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 v6.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 Drive file ID, so no data loss) and promotes the main blog's retention setting to network scope. Single-site installs are unaffected (`site_option` transparently falls back to `option` there). Plan §4.2 v6.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 (manual operator file ops, pre-v6.1.1 backups inherited at upgrade time, etc.) could ever self-heal. Surfaced 2026-05-16 when Kyle's GDrive showed 9 files vs configured retention of 7. Seven pre-v6.1.1 backups were already in the cloud at v6.1.1 install time, the first new backup populated the manifest, the seed never ran again, and the 7 pre-existing orphans were never caught. The function is now a reconciliation pass that runs every enforcer call. Healthy installs pay zero option writes per pass. Plan §0a 2026-05-16 evening.
- Improved: HTTP-status logging in the manifest reconcile pass. Non-2xx Drive API responses are now logged to `error_log()` with the status code + body excerpt, matching the parity hardening already shipped on OneDrive in v5.11.1.
- Improved: Uninstall now cleans up the network-scoped options + transients introduced in v6.8 (license keys) and v6.12 (manifest + retention).
- 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_gdrive_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 v6.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.). `bodholdt_gdrive_get_scope_dirs()` now returns each entry shaped as `[ 'path', 'relpath_prefix' ]` (back-compat-tolerant of older callers passing plain-string paths).
- 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_Gdrive_FollowSymlink_RDI`) that overrides `hasChildren()` to pass `$allowLinks=true`.
- 3. `ZipArchive::close()` silently drops the entire ~500-file staged batch when even one staged file is unreadable. 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: VIEW emission order regression in multisite-network mode. v6.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 Google Drive 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: Unreadable files are skipped cleanly instead of poisoning a 500-file batch. Previously, ZipArchive's deferred-read behavior meant a single 0600/0700 file 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.
- New: Backup manifest (`bodholdt-manifest.json`). Every zip now includes a manifest describing mode, plugin version, WP version, blog topology, tables dumped, and shared-table inclusion. The restore engine reads it first.
- New: Restore-side mode compatibility check. Refuses incompatible mode combos (multisite backup → single-site target) to prevent orphan tables.
- Single-site installs unchanged. No UI changes, no feature flag, no migration required.
- 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.
- Single-site installs are unaffected.
- 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_gdrive_notifications_allowed()` returns true only for Pro/Bundle. Filterable for one-off overrides.
- Failure notifications include the same auto-diagnosis hint as the email notification.
- 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.
- Removed: Three hard-locks that previously blocked the admin UI, manual backups, and scheduled backups on non-valid licenses.
- Setup wizard now also surfaces to Free users for OAuth-flow guidance.
- New: Tier-gated selective restore. Pro and Bundle customers can choose what to restore (database, plugins, themes, uploads, core). 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. 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.
- Critical fix: added backup-filename allowlist to retention enforcement. Previously the enforcer listed every child of the backup folder; user-dropped files in `Bodholdt Backups/<site>` could have been auto-deleted. Now the enforcer uses an ID-manifest written at upload time plus a filename regex check.
- New: One-time seed migration on upgrade for existing backups.
- New: Watchdog last-success timestamp with stale-alert.
- New: Out-of-web-root staging directory.
- New: Multipart-leak fix in email rendering.
- Security hardening: strict base64 decoding, WP_DEBUG-gated error logs, Windows zip-path validation, OAuth state-transient race fix, path-traversal protection in uninstall, API response structure validation, hex-format validation on directory suffix.
- GPL-2.0-or-later license file added. Languages directory added. CHANGELOG.md added.
- Incremental backup mode. Selective backup scope. Setup wizard. Test email. Force unlock for stuck backup processes.
- Selective restore. Sentinel-based SQL statement splitting. Pre-restore safety snapshot.
- Sodium encryption support. HMAC-authenticated AES-256-CBC. Dark cyberpunk admin theme.
- Resumable chunked upload to Google Drive. Backup retention policy with automatic cleanup. Email notifications.
- Migration installer (standalone restore tool). Custom file/folder exclusions. Scheduled backups.
- Initial release with Google Drive integration.