# Activity Log in Its Own Database Table

> Records logins, failed logins, content changes, plugin, theme and user changes and edits to key settings in a dedicated database table, with a searchable, paginated log under Tools > Activity Log and automatic cleanup.

- Type: PHP
- Runs: Run everywhere
- Category: [Security](https://wpide.com/library/category/security/)
- Tags: audit, logging, activity
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/sec-activity-log-table/

## Code

```php
// Days to keep log entries, and settings whose changes are logged.
$wpide_log_days    = 90;
$wpide_log_options = array( 'siteurl', 'home', 'admin_email', 'users_can_register', 'default_role', 'blog_public', 'permalink_structure' );

$wpide_log_table = function () {
    global $wpdb;
    return $wpdb->prefix . 'wpide_activity';
};

$wpide_log = function ( $action, $object = '', $details = '', $user_id = null ) use ( $wpide_log_table ) {
    global $wpdb;

    if ( '1' !== get_option( 'wpide_activity_db' ) ) {
        return;
    }

    $user_id = null === $user_id ? get_current_user_id() : (int) $user_id;
    $user    = $user_id ? get_userdata( $user_id ) : false;

    $wpdb->insert( $wpide_log_table(), array(
        'created'    => current_time( 'mysql', true ),
        'user_id'    => $user_id,
        'user_login' => $user ? $user->user_login : '',
        'ip'         => isset( $_SERVER['REMOTE_ADDR'] ) ? substr( sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ), 0, 45 ) : '',
        'action'     => substr( $action, 0, 40 ),
        'object'     => substr( (string) $object, 0, 190 ),
        'details'    => substr( (string) $details, 0, 1000 ),
    ) );
};

add_action( 'admin_init', function () use ( $wpide_log_table ) {
    global $wpdb;

    if ( '1' === get_option( 'wpide_activity_db' ) || ! current_user_can( 'manage_options' ) ) {
        return;
    }

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( 'CREATE TABLE ' . $wpide_log_table() . " (
        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        created datetime NOT NULL,
        user_id bigint(20) unsigned NOT NULL DEFAULT 0,
        user_login varchar(60) NOT NULL DEFAULT '',
        ip varchar(45) NOT NULL DEFAULT '',
        action varchar(40) NOT NULL,
        object varchar(190) NOT NULL DEFAULT '',
        details text NOT NULL,
        PRIMARY KEY  (id),
        KEY created (created),
        KEY action (action)
    ) " . $wpdb->get_charset_collate() . ';' );
    update_option( 'wpide_activity_db', '1' );
} );

add_action( 'wp_login', function ( $login, $user ) use ( $wpide_log ) {
    $wpide_log( 'login', $login, '', $user->ID );
}, 10, 2 );
add_action( 'wp_login_failed', function ( $login ) use ( $wpide_log ) {
    $wpide_log( 'login_failed', sanitize_user( $login ), '', 0 );
} );
add_action( 'transition_post_status', function ( $new, $old, $post ) use ( $wpide_log ) {
    if ( $new === $old || wp_is_post_revision( $post ) || in_array( $new, array( 'auto-draft', 'inherit' ), true ) || ! post_type_exists( $post->post_type ) || ! is_post_type_viewable( $post->post_type ) ) {
        return;
    }
    $wpide_log( 'post_' . $new, $post->post_type . ' #' . $post->ID, $post->post_title );
}, 10, 3 );
add_action( 'before_delete_post', function ( $post_id, $post ) use ( $wpide_log ) {
    if ( $post && is_post_type_viewable( $post->post_type ) && 'revision' !== $post->post_type ) {
        $wpide_log( 'post_deleted', $post->post_type . ' #' . $post_id, $post->post_title );
    }
}, 10, 2 );
foreach ( array( 'activated_plugin' => 'plugin_activated', 'deactivated_plugin' => 'plugin_deactivated', 'deleted_plugin' => 'plugin_deleted' ) as $wpide_hook => $wpide_action ) {
    add_action( $wpide_hook, function ( $plugin ) use ( $wpide_log, $wpide_action ) {
        $wpide_log( $wpide_action, $plugin );
    } );
}
add_action( 'switch_theme', function ( $name ) use ( $wpide_log ) {
    $wpide_log( 'theme_switched', $name );
} );
add_action( 'upgrader_process_complete', function ( $upgrader, $extra ) use ( $wpide_log ) {
    if ( ! empty( $extra['type'] ) && ! empty( $extra['action'] ) ) {
        $items = isset( $extra['plugins'] ) ? $extra['plugins'] : ( isset( $extra['themes'] ) ? $extra['themes'] : array() );
        $wpide_log( $extra['type'] . '_' . $extra['action'], implode( ', ', (array) $items ) );
    }
}, 10, 2 );
add_action( 'user_register', function ( $user_id ) use ( $wpide_log ) {
    $user = get_userdata( $user_id );
    $wpide_log( 'user_created', $user ? $user->user_login : '#' . $user_id, $user ? implode( ',', $user->roles ) : '' );
} );
add_action( 'delete_user', function ( $user_id ) use ( $wpide_log ) {
    $user = get_userdata( $user_id );
    $wpide_log( 'user_deleted', $user ? $user->user_login : '#' . $user_id );
} );
add_action( 'set_user_role', function ( $user_id, $role, $old ) use ( $wpide_log ) {
    if ( $old ) {
        $user = get_userdata( $user_id );
        $wpide_log( 'role_changed', $user ? $user->user_login : '#' . $user_id, implode( ',', (array) $old ) . ' -> ' . $role );
    }
}, 10, 3 );
add_action( 'updated_option', function ( $option, $old, $new ) use ( $wpide_log, $wpide_log_options ) {
    if ( in_array( $option, $wpide_log_options, true ) ) {
        $wpide_log( 'setting_changed', $option, wp_json_encode( $old ) . ' -> ' . wp_json_encode( $new ) );
    }
}, 10, 3 );

add_action( 'init', function () {
    if ( ! wp_next_scheduled( 'wpide_activity_cleanup' ) ) {
        wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wpide_activity_cleanup' );
    }
} );
add_action( 'wpide_activity_cleanup', function () use ( $wpide_log_table, $wpide_log_days ) {
    global $wpdb;
    if ( '1' === get_option( 'wpide_activity_db' ) ) {
        $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpide_log_table() . ' WHERE created < %s LIMIT 10000', gmdate( 'Y-m-d H:i:s', time() - $wpide_log_days * DAY_IN_SECONDS ) ) );
    }
} );

add_action( 'admin_menu', function () use ( $wpide_log_table ) {
    add_management_page( 'Activity Log', 'Activity Log', 'manage_options', 'wpide-activity-table', function () use ( $wpide_log_table ) {
        global $wpdb;

        $per    = 50;
        $paged  = max( 1, isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1 );
        $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
        $rows   = array();
        $total  = 0;

        if ( '1' === get_option( 'wpide_activity_db' ) ) {
            $where = '1=1';
            $args  = array();
            if ( '' !== $search ) {
                $like  = '%' . $wpdb->esc_like( $search ) . '%';
                $where = '(user_login LIKE %s OR action LIKE %s OR object LIKE %s OR details LIKE %s OR ip LIKE %s)';
                $args  = array( $like, $like, $like, $like, $like );
            }
            $table = $wpide_log_table();
            $total = (int) $wpdb->get_var( $args ? $wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE $where", $args ) : "SELECT COUNT(*) FROM $table" );
            $rows  = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table WHERE $where ORDER BY id DESC LIMIT %d OFFSET %d", array_merge( $args, array( $per, ( $paged - 1 ) * $per ) ) ) );
        }

        echo '<div class="wrap"><h1>Activity Log</h1><form method="get"><input type="hidden" name="page" value="wpide-activity-table">';
        echo '<p class="search-box"><label class="screen-reader-text" for="wpide-log-search">Search the log</label><input type="search" id="wpide-log-search" name="s" value="' . esc_attr( $search ) . '"> <button class="button">Search</button></p></form>';
        echo '<table class="widefat striped"><thead><tr><th>Time (UTC)</th><th>User</th><th>Action</th><th>Object</th><th>Details</th><th>IP</th></tr></thead><tbody>';
        if ( ! $rows ) {
            echo '<tr><td colspan="6">No entries.</td></tr>';
        }
        foreach ( $rows as $row ) {
            printf( '<tr><td>%s</td><td>%s</td><td><code>%s</code></td><td>%s</td><td>%s</td><td>%s</td></tr>', esc_html( $row->created ), esc_html( $row->user_login ? $row->user_login : '-' ), esc_html( $row->action ), esc_html( $row->object ), esc_html( wp_html_excerpt( $row->details, 120, '...' ) ), esc_html( $row->ip ) );
        }
        echo '</tbody></table>';
        $links = paginate_links( array( 'base' => add_query_arg( 'paged', '%#%' ), 'format' => '', 'current' => $paged, 'total' => max( 1, (int) ceil( $total / $per ) ) ) );
        if ( $links ) {
            echo '<div class="tablenav"><div class="tablenav-pages">' . wp_kses_post( $links ) . '</div></div>';
        }
        echo '</div>';
    } );
} );
```

## Notes

Records are kept for $wpide_log_days days; the table is created the first time an administrator opens the dashboard.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Activity Log in Its Own Database Table" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
