Activation, deactivation and uninstall sound like three versions of the same WordPress plugin event. They are not. Treating them as interchangeable is how plugins end up deleting settings when an administrator only wanted to troubleshoot, flushing rewrite rules on every request, or leaving years of abandoned options behind after deletion.
This guide explains the practical boundary between the three lifecycle stages and gives you a workflow for deciding what belongs in each one. If you want a starting implementation, use the WordPress Plugin Lifecycle Generator and then adapt the output to your plugin’s actual data model.
The short version
Activation is for setup required when a plugin becomes active. Deactivation is for reversible cleanup when the plugin stops running. Uninstall is for permanent cleanup when the plugin is deleted and the product’s retention policy says its data should go too.
That distinction matters because deactivation is routinely temporary. Administrators deactivate plugins to isolate conflicts, perform maintenance, switch implementations, or test performance. Destroying configuration at that point makes a routine troubleshooting action destructive.
What belongs in an activation hook?
WordPress provides register_activation_hook() for work that should happen when a plugin is activated. Typical examples include creating initial options, preparing a custom database structure, scheduling required background work, or refreshing rewrite rules after registering a custom post type.
Activation code should be repeatable. A plugin can be deactivated and activated many times during its lifetime. If activation blindly replaces an existing option with defaults, a reactivation can silently erase configuration. Prefer checking whether data exists before creating it, and treat schema migrations separately from a simplistic “install everything again” routine.
Rewrite rules are a special case
If a plugin registers a custom post type, taxonomy or other rewrite-producing structure, activation is a sensible place for a one-time flush_rewrite_rules(). But the content type or rewrite rules must be registered before the flush, otherwise WordPress has nothing useful to persist.
Do not move that flush into ordinary page execution. Flushing rewrite rules is not normal request-time work. Register the structure on init, invoke the registration routine during activation when necessary, and flush once.
If you’re building the content model itself, the Custom Post Type Generator can help you prepare the registration code before you wire up the lifecycle behavior.
What belongs in deactivation?
Deactivation should generally leave durable user data alone. Think of it as putting the plugin into a reversible stopped state rather than deleting the product’s history.
Good candidates include transient runtime data, temporary caches, temporary files and rewrite-rule cleanup where the plugin introduced rewrites. If the plugin schedules recurring tasks, consider whether those events should be unscheduled on deactivation and restored on activation. The correct choice depends on whether the job can safely run without the plugin active—which usually it cannot.
Persistent options, customer records, custom post content and business data usually should not disappear merely because someone clicked Deactivate. WordPress’s own Plugin Handbook explicitly distinguishes deactivation cleanup from uninstall cleanup.
Uninstall is the destructive boundary
WordPress supports two common uninstall approaches: register_uninstall_hook() and a root-level uninstall.php. The latter is useful when you want cleanup logic isolated from the main plugin bootstrap. When using uninstall.php, verify that WP_UNINSTALL_PLUGIN is defined before doing anything so the file cannot simply be executed directly.
This is where plugin-owned options, tables or other persistent records may be removed—but “may” is important. Not every plugin should automatically destroy all data on uninstall. Stores, membership systems, analytics tools and integrations may hold information that users expect to preserve for migration, audit or reinstallation.
Make data retention a product decision
A good plugin often offers an explicit “remove data on uninstall” preference. That makes the destructive choice visible before deletion. If you provide such a setting, document exactly what is removed: options, custom tables, post types, metadata, scheduled jobs, uploaded files, logs, or external records.
Never treat a generated DROP TABLE statement as boilerplate to ship without review. Confirm ownership of the table, consider multisite behavior, make sure the prefix is handled correctly, and test against a disposable database copy.
Activation hooks have an important timing constraint
A common implementation mistake is registering the activation hook too late. WordPress documentation notes that register_activation_hook() should not itself be registered from plugins_loaded, init, or another later hook. Register it as the plugin file is loaded and make sure its first argument identifies the main plugin file correctly.
Activation also redirects immediately after the activation routine. That means it is a poor place to assume that ordinary later request hooks will continue running. If you need to display a post-activation notice or perform follow-up work, persist a small state flag and handle it on a subsequent request rather than building a fragile activation chain.
Defaults, migrations and versioned upgrades are different jobs
For a tiny plugin, creating a default option during activation may be enough. Mature plugins need a more deliberate migration strategy. A site can update an already-active plugin without triggering a normal manual activation cycle, so database migrations must not rely solely on activation.
Track a schema or plugin data version and compare it when the plugin loads in an appropriate administrative context. Run only the migrations needed to move from the stored version to the current one, then update the stored version after success. Keep migration routines idempotent where possible.
For configuration itself, the WordPress Settings API Generator provides a safer starting point for registering and sanitizing plugin options.
What about transients and caches?
Transient data is usually disposable by definition, so deactivation can be a reasonable place to clear plugin-specific runtime caches. But don’t assume every transient must be deleted manually. Design cache keys predictably and distinguish caches from persistent settings.
The Transient Cache Code Generator demonstrates regeneration and invalidation patterns. This matters because a persistent object cache can change where transient data actually lives; direct database manipulation is therefore a poor cleanup strategy.
Multisite needs separate planning
A single-site lifecycle snippet is not automatically a network-safe lifecycle implementation. Network activation can affect many sites, and looping across a large network during activation or uninstall can become expensive. Site creation after network activation also raises a separate question: does the plugin need to initialize data for new sites?
Before declaring multisite support, test network activation, per-site activation, network deactivation, individual-site behavior and uninstall. Decide whether data is site-specific or network-wide and use the appropriate APIs instead of assuming every option lives in one site’s options table.
A practical testing workflow
Lifecycle code deserves destructive testing on staging, not just a syntax check. Start with a clean site and record the relevant options, tables, cron events and rewrite behavior. Activate the plugin and verify only the intended defaults or structures appear. Change the settings to non-default values, deactivate, and confirm those persistent values remain.
Reactivate and verify the existing configuration survives. If the plugin has pretty URLs, visit them before and after deactivation/reactivation. If it schedules work, inspect scheduled events. If it uses transients, confirm their absence does not break functionality and that they regenerate normally.
Then clone or back up the environment and test uninstall. Confirm that the documented data-retention behavior is exactly what happens. A successful uninstall test is not “the plugin disappeared”; it is knowing which data disappeared, which data remained, and why.
Common mistakes worth checking in code review
Look for persistent data deletion in deactivation callbacks, activation hooks registered from init, rewrite flushing on normal frontend requests, default options that overwrite existing values, raw database deletion without an ownership check, uninstall files without the WordPress uninstall guard, and cleanup routines that ignore multisite implications.
Also check failure behavior. If activation partially creates data and then fails, can the plugin recover on the next attempt? If an uninstall query fails halfway through, is the remaining state understandable? Lifecycle code is infrastructure code: predictability matters more than cleverness.
Use the generator as scaffolding, not policy
The WordPress Plugin Lifecycle Generator intentionally generates conservative scaffolding. It separates activation, deactivation and uninstall responsibilities and makes destructive uninstall cleanup opt-in. Your plugin still needs a product-specific retention policy, migration strategy and staging tests.
If you’re assembling a new plugin, pair it with the Plugin Header Generator for metadata and the PHP Requirements Checker when documenting runtime requirements.
Final checklist
Before release, verify that activation is safe to repeat, deactivation is reversible, uninstall behavior is intentional and documented, rewrite rules are flushed only when necessary, migrations do not depend solely on manual activation, multisite claims have been tested, and destructive cleanup has been exercised against a disposable copy. That small discipline prevents lifecycle code from becoming the part of your plugin users only notice when something goes wrong.
