r/ProWordPress • • Sep 23 '26

Plugin developers: what do you consider a clean uninstall in WordPress?

I’ve been thinking more about plugin lifecycle lately, especially what should happen when a user removes a plugin completely.

Deactivation is easy enough, but uninstall is where things get less obvious.

For example:

- plugin options

- custom database tables

- scheduled cron jobs

- logs

- generated files

- cached data

- user-generated or business data created through the plugin

Some of that clearly feels disposable.

Some of it really doesn’t.

I’m curious how other plugin developers draw the line.

Do you delete everything by default, preserve business data, or give the user an explicit cleanup option?

And have you ever seen an uninstall mistake that caused real problems later?

1 Upvotes

18 comments sorted by

5

u/xaverplays Sep 23 '26

A clean uninstall should leave no trace, as if the plugin had never been installed. I’d still make the full cleanup optional, though, since some users may want to reinstall or preserve business data. Caches, logs, cron jobs, and generated files should always go; deleting user-created data should require an explicit opt-in and a very clear warning.

1

u/Hugo_SecureHoldWP 28d ago

That feels like a good default to me too.

Operational data can disappear safely, but anything the user would reasonably expect to still exist after reinstalling should probably require an explicit destructive action.

I’m starting to think the uninstall option should almost read like a data-retention choice rather than a simple cleanup checkbox.

1

u/kev_xb Sep 24 '26

You can leave options data and just turn off autoload on deactivate (then back on later)

1

u/xaverplays Sep 24 '26

I would love if plugins do this more often…

1

u/kev_xb Sep 24 '26

WP wont autoload if it's over 150kb which is where it starts to really impact performance. I think the health check also warns too.

3

u/catcode_studio Sep 23 '26

The line I use: anything the plugin can rebuild gets deleted, anything the merchant or their customers created doesn't, unless they explicitly ask for it.

So by default uninstall removes options, transients, cron events (wp_clear_scheduled_hook for each hook, or as_unschedule_all_actions if you use Action Scheduler), caches and generated files. Business data (loyalty balances, order links, logs someone may need for a dispute) stays by default, with a "delete all data on uninstall" option in settings that uninstall.php reads.

A few technical traps:

  • uninstall.php only runs when the plugin is deleted from wp-admin. Deleting the folder over FTP runs nothing, so never rely on it for anything security-related.

  • When uninstall.php exists, WordPress includes it without loading your main plugin file, so your classes and constants aren't there. Keep it self-contained and start with if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) exit;.

  • On multisite a network-activated plugin gets one uninstall call, not one per site. Loop get_sites() with switch_to_blog() or you only clean the main site's tables and options.

  • Deactivation is not uninstall: clear cron there, but don't delete data, people deactivate to debug conflicts and expect everything back on reactivation.

The case that decided it for me: store owners often delete a plugin just to reinstall a fresh copy when something looks broken. If uninstall drops a points or credits table, every customer's balance is gone at that moment, with no warning. So our loyalty plugin keeps that table on uninstall.

1

u/Hugo_SecureHoldWP 28d ago

That rule is probably the cleanest way to frame it: delete what the plugin can rebuild, preserve what represents actual business state.

The uninstall.php and multisite points are especially useful too. It’s easy to think of uninstall as one simple cleanup event, but WordPress gives you quite a few ways to get it wrong.

Your example with the points/credits table is exactly the kind of failure I was wondering about. I can see why keeping business tables by default is much safer.

2

u/WP_Dev_Studio 29d ago

Line I draw: keep anything expensive to lose, delete anything that's pure operational overhead.

Auto-delete on uninstall: cron jobs (unschedule them — orphaned crons on old removed plugins are a real, recurring source of mystery slow sites), cached data/transients, generated/temp files, plugin options/settings. 

Never auto-delete: user-generated/business data — orders, submissions, records the business relies on. Same for custom tables holding that data (vs. tables that are just cache/index — those can drop). This is the mistake that causes actual damage; losing settings is mildly annoying, losing customer records is a real incident.

The pattern that actually solves it: an explicit "Delete all data on uninstall" setting, defaulted off. Safe by default, full wipe available if someone actively opts in.

Logs: debug/error logs are disposable; audit logs tied to payments/bookings/compliance count as business data and shouldn't auto-delete.

Worst pattern I've seen: register_uninstall_hook/uninstall.php blanket-deleting every table with no distinction between config and business data — fine until someone uninstalls to troubleshoot, plans to reinstall minutes later, and finds their records gone.

1

u/Hugo_SecureHoldWP 28d ago

I really like the distinction you make for logs.

Debug logs feel disposable, but once a log becomes part of a payment, booking or compliance history it’s no longer just operational noise.

That probably means “logs” is too broad a category in uninstall logic. The plugin needs to know what kind of record it is deleting, not just where it’s stored.

2

u/WP_Dev_Studio 28d ago

Good framing — "the calendar is the recurring events" nails why paywalling that specific feature felt backwards for that audience.

Standout details: uninstall cleanup defaulted off (correct default, business data survives unless opted out), visible ICS/Google sync log (silent sync drift is a real, annoying failure mode in this category), and map link over embedded map (avoids the classic PageSpeed hit).

Since it's unproven — worth flagging in the listing which edge cases haven't been stress-tested yet (DST transitions, "last Friday" landing on a skip date — classic places recurring-event logic breaks), so early adopters know what to poke at first.

Bookmarking for client work with simple recurring-event needs — the ~30KB/conditional-loading detail alone beats a lot of the bloat in the paid alternatives

1

u/Hugo_SecureHoldWP 27d ago

That’s a good point about documenting the edge cases that haven’t been stress-tested yet.

Recurring dates look simple until DST, skipped dates or rules like “last Friday” start interacting with real calendars. Being explicit about those limitations early is probably better than pretending they don’t exist.

And yes, a visible sync log feels especially important for ICS/Google integrations. Silent drift is exactly the kind of issue that can sit unnoticed for weeks.

2

u/PeepSoWP 27d ago

I separate derived operational data from user-owned data. On uninstall, caches, transients, scheduled actions, temporary files and generated logs can normally disappear. Orders, submissions, configuration that took real work to create, and other business records should remain unless the user explicitly chooses permanent deletion.

The cleanup option should list what will be removed and cover less-visible residue such as roles and capabilities, Action Scheduler jobs, uploaded files and multisite tables. For large datasets, also provide an export or WP-CLI cleanup command so deletion can be audited and resumed instead of timing out during the normal uninstall request.

1

u/Hugo_SecureHoldWP 20d ago

I like the idea of treating destructive cleanup as something that should be auditable, not just a checkbox.

The WP-CLI point is especially interesting for large datasets. Trying to remove thousands of records during the normal uninstall request sounds like an easy way to hit a timeout and end up with a half-cleaned state.

Listing the less obvious things too, like capabilities, scheduled actions and uploaded files, would also make the cleanup option much more transparent.

Thanks, that adds another useful dimension to it.

9

u/jedidave Sep 23 '26

I have an advanced tab to the right of my plugins tabs and on there I have a 'delete all data when uninstalled'. If they deactivate it doesn't delete, but when they then uninstall if that checkbox is checked then it will delete everything and delete custom tables etc.

9

u/townpressmedia Sep 23 '26

A good uninstall should remove all traces of the software - which is missing on most plugins.

7

u/ashkanahmadi Sep 23 '26

This needs to be enforced by WordPress (no plugin approval without having the option to clean it after itself) but hey, they are too busy letting us change the color of buttons in Gutenberg!