# Consent Record Log With Shortened IP Addresses

> Records each cookie consent choice (accepted or rejected, categories, date and privacy policy version) in its own database table, with only a shortened IP address, and lists the latest records under Tools > Consent Log.

- Type: PHP
- Runs: Run everywhere
- Category: [Privacy & Cookies](https://wpide.com/library/category/privacy/)
- Tags: consent, gdpr, logging
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/privacy-consent-log/

## Code

```php
// Days to keep consent records.
$wpide_consent_keep_days = 395;

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

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

    if ( '1' === get_option( 'wpide_consent_log_db' ) ) {
        return;
    }

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( 'CREATE TABLE ' . $wpide_consent_table() . " (
        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        created datetime NOT NULL,
        choice varchar(20) NOT NULL,
        categories varchar(190) NOT NULL DEFAULT '',
        ip varchar(45) NOT NULL DEFAULT '',
        policy varchar(32) NOT NULL DEFAULT '',
        PRIMARY KEY  (id),
        KEY created (created)
    ) " . $wpdb->get_charset_collate() . ';' );
    update_option( 'wpide_consent_log_db', '1' );
} );

add_action( 'rest_api_init', function () use ( $wpide_consent_table ) {
    register_rest_route( 'wpide/v1', '/consent', array(
        'methods'             => 'POST',
        'permission_callback' => '__return_true',
        'args'                => array(
            'choice'     => array( 'type' => 'string', 'enum' => array( 'granted', 'denied', 'partial' ), 'required' => true ),
            'categories' => array( 'type' => 'string', 'default' => '' ),
        ),
        'callback'            => function ( WP_REST_Request $request ) use ( $wpide_consent_table ) {
            global $wpdb;

            $ip   = isset( $_SERVER['REMOTE_ADDR'] ) ? wp_privacy_anonymize_ip( sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) ) : '';
            $rate = 'wpide_consent_rate_' . md5( $ip );
            if ( (int) get_transient( $rate ) >= 10 ) {
                return new WP_REST_Response( array( 'ok' => false ), 429 );
            }
            set_transient( $rate, (int) get_transient( $rate ) + 1, HOUR_IN_SECONDS );

            if ( '1' !== get_option( 'wpide_consent_log_db' ) ) {
                return new WP_REST_Response( array( 'ok' => false ), 503 );
            }

            $policy_id = (int) get_option( 'wp_page_for_privacy_policy' );
            $wpdb->insert( $wpide_consent_table(), array(
                'created'    => current_time( 'mysql', true ),
                'choice'     => $request['choice'],
                'categories' => substr( sanitize_text_field( $request['categories'] ), 0, 190 ),
                'ip'         => $ip,
                'policy'     => $policy_id ? (string) get_post_modified_time( 'Y-m-d', true, $policy_id ) : '',
            ) );

            return array( 'ok' => true );
        },
    ) );
} );

add_action( 'wp_footer', function () {
    ?>
<script>
(function () {
    var url = <?php echo wp_json_encode( esc_url_raw( rest_url( 'wpide/v1/consent' ) ) ); ?>;
    var timer;
    function send(choice, categories) {
        clearTimeout(timer);
        timer = setTimeout(function () {
            var body = new URLSearchParams({ choice: choice, categories: categories || '' });
            if (navigator.sendBeacon) { navigator.sendBeacon(url, body); } else { fetch(url, { method: 'POST', body: body, keepalive: true }); }
        }, 500);
    }
    document.addEventListener('wpide:consent', function (e) { send(e.detail === 'granted' ? 'granted' : 'denied', 'all'); });
    document.addEventListener('wp_listen_for_consent_change', function (e) {
        var detail = e.detail || {};
        var cats = Object.keys(detail);
        var granted = cats.filter(function (c) { return detail[c] === 'allow' || detail[c] === 'granted'; });
        send(granted.length === cats.length ? 'granted' : (granted.length ? 'partial' : 'denied'), granted.join(','));
    });
})();
</script>
    <?php
}, 99 );

add_action( 'init', function () {
    if ( ! wp_next_scheduled( 'wpide_consent_log_cleanup' ) ) {
        wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wpide_consent_log_cleanup' );
    }
} );

add_action( 'wpide_consent_log_cleanup', function () use ( $wpide_consent_table, $wpide_consent_keep_days ) {
    global $wpdb;

    if ( '1' === get_option( 'wpide_consent_log_db' ) ) {
        $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpide_consent_table() . ' WHERE created < %s LIMIT 5000', gmdate( 'Y-m-d H:i:s', time() - $wpide_consent_keep_days * DAY_IN_SECONDS ) ) );
    }
} );

add_action( 'admin_menu', function () use ( $wpide_consent_table ) {
    add_management_page( 'Consent Log', 'Consent Log', 'manage_options', 'wpide-consent-log', function () use ( $wpide_consent_table ) {
        global $wpdb;

        $rows = '1' === get_option( 'wpide_consent_log_db' ) ? $wpdb->get_results( 'SELECT * FROM ' . $wpide_consent_table() . ' ORDER BY id DESC LIMIT 200' ) : array();

        echo '<div class="wrap"><h1>Consent Log</h1><p>The latest 200 consent choices. IP addresses are stored shortened.</p>';
        echo '<table class="widefat striped"><thead><tr><th>Date (UTC)</th><th>Choice</th><th>Categories</th><th>IP</th><th>Policy version</th></tr></thead><tbody>';
        if ( ! $rows ) {
            echo '<tr><td colspan="5">No records yet.</td></tr>';
        }
        foreach ( $rows as $row ) {
            printf( '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>', esc_html( $row->created ), esc_html( $row->choice ), esc_html( $row->categories ), esc_html( $row->ip ), esc_html( $row->policy ) );
        }
        echo '</tbody></table></div>';
    } );
} );
```

## Notes

Picks up choices from the WP Consent API and from the wpide:consent event of the consent banner snippet; records older than $wpide_consent_keep_days are removed daily.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Consent Record Log With Shortened IP Addresses" 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/
