Skip to main content
Login Join
Snippet · PHP

Fallback Table Creation Using plugins_loaded When Activation Hook Fails

Shared by Gauri Kaushik · June 10, 2026 · @plugins_loaded

4 views
Back to Snippets

WordPress activation hook does not always fire reliably in certain environments like LocalWP, multisite setups, or some managed hosting configurations. If the activation hook is missed, your custom database table never gets created and the plugin breaks silently.

This snippet adds a safety net. On every page load, maybe_install() checks if the table exists. If it does not, it calls the installer. Because dbDelta() is idempotent, running it multiple times is safe — it only creates what is missing, never drops existing data.

Setup:

  1. Hook maybe_install() to plugins_loaded in your main Plugin class
  2. table_exists() uses SHOW TABLES LIKE to check presence by name
  3. If missing, create_tables() runs dbDelta() to create it
// In Plugin.php — hook on plugins_loaded
add_action( 'plugins_loaded', [ $this, 'maybe_install' ] );

public function maybe_install(): void {
    if ( ! Installer::table_exists() ) {
        Installer::run();
    }
}

// In Installer.php
public static function table_exists(): bool {
    global $wpdb;
    $table_name = $wpdb->prefix . 'decay_snapshots';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
    return $wpdb->get_var(
        $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name )
    ) === $table_name;
}

private static function create_tables(): void {
    global $wpdb;

    $table_name      = $wpdb->prefix . 'decay_snapshots'; //replace with your table name
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE {$table_name} (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        post_id BIGINT(20) UNSIGNED NOT NULL,
        snapshot_date DATE NOT NULL,
        traffic_score INT(11) DEFAULT NULL,
        decay_score TINYINT(3) UNSIGNED DEFAULT NULL,
        suggestions LONGTEXT DEFAULT NULL,
        is_reviewed TINYINT(1) NOT NULL DEFAULT 0,
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY post_id (post_id),
        KEY snapshot_date (snapshot_date)
    ) {$charset_collate};";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( $sql );
}