Docs Core Features Approval Gates

Approval Gates

Require explicit approval before critical WordPress changes go live. Gate 9 dangerous capabilities with configurable expiration.

tutorial 12 min read

Overview

Approval Gates add a human-in-the-loop checkpoint before the most destructive actions in WordPress can execute. Instead of outright blocking capabilities like policies do, approvals let users request permission and wait for an administrator to approve or deny the action.

Approval Gates are a Cloud-only feature. The module checks for a valid adminlocks_cloud_key in the options table before initializing. If no Cloud key exists, the approval system is entirely inactive and imposes zero overhead.

Gated Capabilities

AdminLocks gates 9 WordPress capabilities that represent the most risky administrative actions. These capabilities cover the three areas where a single wrong click can take a site down: plugins, themes, and user management.

Category Capability What It Controls
Plugins install_plugins Adding new plugins from the repository or uploads
activate_plugins Turning on an installed plugin
delete_plugins Permanently removing a plugin from the server
Themes switch_themes Changing the active theme
install_themes Adding new themes from the repository or uploads
delete_themes Permanently removing a theme from the server
Users promote_users Changing a user's role (e.g., editor to administrator)
create_users Adding new WordPress user accounts
delete_users Removing user accounts and their content

How It Works

The approval system intercepts capabilities at the WordPress user_has_cap filter (priority 20, which runs after the policy filter at priority 10). When a non-bypass user attempts a gated action, AdminLocks checks whether an approved record exists for that user and capability.

The interception flow

  1. WordPress core calls current_user_can('activate_plugins') (or any gated capability).
  2. The user_has_cap filter fires. AdminLocks checks if the user is a super admin or has adminlocks_bypass — if so, the capability passes through unchanged.
  3. For non-bypass users, AdminLocks queries the adminlocks_approvals table for a record matching the user ID and capability with status = 'approved' and reviewed_at within the last hour.
  4. If an approved record exists, the capability is allowed. If not, the capability is set to false, effectively preventing the action.
// The core interception logic (simplified)
foreach ($caps as $cap) {
    if (in_array($cap, $this->gated_actions, true)) {
        if (!$this->has_approved_action($user->ID, $cap)) {
            $allcaps[$cap] = false;
        }
    }
}

The approval check has a 1-hour execution window. Once an action is approved, the user has 60 minutes to perform it. After that, the approved record is no longer valid for the user_has_cap check and they would need to request approval again.

Admin Notice

When pending approvals exist, AdminLocks displays a warning notice at the top of every admin page for users with manage_options. The notice shows the count of pending requests and links to the approvals management page at admin.php?page=adminlocks-approvals.

Approval Lifecycle

Every approval request follows a defined lifecycle with clear state transitions.

pending ──┬──> approved ──┬──> executed
          │               └──> expired (if not used within 1 hour)
          ├──> denied
          └──> expired (if past expires_at)

Submitting a request

Requests are submitted programmatically via the Approvals::submit() method. Each request includes the action type (capability name), contextual data (what plugin, what theme, etc.), and the requesting user's ID.

// Submit an approval request
$approvals = new \AdminLocks\Modules\Approvals();
$id = $approvals->submit('activate_plugins', [
    'plugin' => 'woocommerce/woocommerce.php',
    'reason' => 'Client needs e-commerce functionality'
]);

// The submit method:
// 1. Inserts a record with status = 'pending'
// 2. Calculates expires_at based on adminlocks_approval_expire_hours
// 3. Logs the request to the audit log
// 4. Fires the adminlocks_approval_submitted action hook

Reviewing a request

Administrators review requests via the REST API or the wp-admin approvals page. The review method accepts the approval ID, a status (approved or denied), and an optional note.

// Approve a request
$approvals->review($id, 'approved', 'Looks good, go ahead.');

// Deny a request
$approvals->review($id, 'denied', 'Not needed at this time.');

Each review action is logged to the audit log and fires the corresponding hook: adminlocks_approval_approved or adminlocks_approval_denied.

Execution tracking

After an approval is granted and the user performs the action, AdminLocks marks the approval as executed. This tracking is handled automatically through WordPress action hooks:

The consumption process finds the most recent approved record for the user and action type (within the 1-hour window) and updates its status to executed.

Auto-Expiration

Approval requests have a configurable expiration time. By default, pending requests expire after 72 hours. This is controlled by the adminlocks_approval_expire_hours option.

// Set expiration to 24 hours
update_option('adminlocks_approval_expire_hours', 24);

// Disable expiration (requests stay pending forever)
update_option('adminlocks_approval_expire_hours', 0);

An hourly cron job (adminlocks_approval_cleanup) runs the expiration check. It updates all pending records where expires_at is in the past to status = 'expired'.

There are two distinct time windows: the pending expiration (default 72 hours, configurable) controls how long a request waits for review. The execution window (fixed at 1 hour) controls how long after approval the user can perform the action.

Cloud Integration

When connected to AdminLocks Cloud, approval requests are synced to the Cloud dashboard where they can be reviewed from a centralized interface. This enables several workflow enhancements:

Extensibility hooks

Three action hooks allow you to extend the approval workflow with custom logic:

// Fired when a new approval is submitted
add_action('adminlocks_approval_submitted', function($id, $action_type, $action_data) {
    // Send a Slack notification, create a Jira ticket, etc.
}, 10, 3);

// Fired when an approval is approved
add_action('adminlocks_approval_approved', function($id) {
    // Notify the requesting user, update external systems, etc.
});

// Fired when an approval is denied
add_action('adminlocks_approval_denied', function($id) {
    // Notify the requesting user with the denial reason
});

REST API

The approval system exposes two REST endpoints. Both require the manage_options capability.

Method Endpoint Description
GET /adminlocks/v1/approvals List all pending approval requests
POST /adminlocks/v1/approvals/{id}/review Approve or deny a specific request

Review request body

// POST /wp-json/adminlocks/v1/approvals/42/review
{
    "status": "approved",  // or "denied"
    "note": "Approved for production deployment."
}

// Response
{ "ok": true }

List pending approvals response

// GET /wp-json/adminlocks/v1/approvals
[
    {
        "id": 42,
        "action_type": "activate_plugins",
        "action_data": "{\"plugin\":\"woocommerce/woocommerce.php\"}",
        "requested_by": 3,
        "requested_email": "client@example.com",
        "status": "pending",
        "expires_at": "2026-03-08 14:22:10",
        "reviewed_by": null,
        "review_note": null,
        "reviewed_at": null,
        "created_at": "2026-03-05 14:22:10"
    }
]