Enterprise identity management for WordPress powered by WorkOS. SSO, directory sync, MFA, and user management.
Requires PHP: 7.4+ Requires WordPress: 5.9+ License: GPL-2.0-or-later
- Single Sign-On (SSO) via WorkOS AuthKit — redirect or headless login modes
- Directory Sync (SCIM) — automatic user provisioning and deprovisioning from your identity provider
- Role Mapping — map WorkOS organization roles to WordPress roles
- Organization Management — local caching of WorkOS organizations with multisite support
- Entitlement Gate — require organization membership to log in
- Webhook Processing — real-time sync of user, organization, and directory events
- REST API Authentication — Bearer token auth for headless/decoupled WordPress
- Login Button — shortcode (
[workos_login]), Gutenberg block, and classic widget - Login Bypass — access the native WordPress login form via
?fallback=1when WorkOS is unavailable - Activity Logging — local database table with admin viewer for tracking authentication and sync events
- Audit Logging — forward WordPress events (login, logout, post changes, user changes) to WorkOS Audit Logs
- Role-Based Login Redirects — send users to different URLs after login based on their WordPress role
- Role-Based Logout Redirects — send users to different URLs after logout based on their WordPress role
- Password Reset Integration — redirect password reset to WorkOS or fall back to WordPress
- Registration Redirect — redirect registration to WorkOS AuthKit
- Admin Bar Badge — shows the active WorkOS environment (production/staging) in the admin bar
- Changelog Page — in-admin changelog viewer rendered from CHANGELOG.md
- Diagnostics Page — system health checks and configuration status
- Onboarding Wizard — guided setup for initial plugin configuration and user sync
- WP-CLI Commands — full CLI access for scripting, bulk operations, and diagnostics
- Download the latest
.zipfrom the Releases page. - In WordPress admin, go to Plugins > Add New > Upload Plugin and upload the ZIP file.
- Activate the plugin.
- Navigate to Settings > WorkOS to configure.
- Clone the repository into
wp-content/plugins/integration-workos/. - Run
composer installto install PHP dependencies. - Run
bun install && bun run buildto install JS dependencies and build assets. - Activate the plugin in WordPress admin.
- Navigate to Settings > WorkOS to configure.
Go to Settings > WorkOS in the WordPress admin. The settings page has three tabs:
| Tab | Contents |
|---|---|
| Settings | API Key, Client ID, Environment ID, webhook secret, login mode, password fallback, audit logging toggle |
| Organization | Select or create a WorkOS organization |
| Users | Deprovision action, content reassignment, role mapping table |
The plugin supports two environments — Production and Staging — with separate credentials for each. An admin bar badge shows which environment is active.
All credentials can be set via constants in wp-config.php, which take precedence over database values:
// Generic (used for any environment)
define( 'WORKOS_API_KEY', 'sk_live_...' );
define( 'WORKOS_CLIENT_ID', 'client_...' );
define( 'WORKOS_WEBHOOK_SECRET', 'whsec_...' );
define( 'WORKOS_ORGANIZATION_ID', 'org_...' );
define( 'WORKOS_ENVIRONMENT_ID', 'environment_...' );
define( 'WORKOS_ENVIRONMENT', 'production' ); // Lock active environment
// Per-environment (takes priority over generic)
define( 'WORKOS_PRODUCTION_API_KEY', 'sk_live_...' );
define( 'WORKOS_STAGING_API_KEY', 'sk_test_...' );Users visiting wp-login.php are redirected to WorkOS AuthKit. After authentication, WorkOS redirects back to /workos/callback where the plugin exchanges the authorization code for user data and tokens.
The plugin intercepts WordPress's authenticate filter and validates credentials directly against the WorkOS API using email and password. This mode is useful for custom login forms.
When enabled, WordPress native password authentication remains available alongside WorkOS. Password reset and registration forms fall back to WordPress defaults.
If WorkOS is down or misconfigured, users can access the native WordPress login form by appending ?fallback=1 to the login URL. This bypasses the WorkOS redirect entirely.
Configure your WorkOS dashboard to send webhooks to:
https://yoursite.com/wp-json/workos/v1/webhook
The plugin processes these event types:
| Category | Events |
|---|---|
| Users | user.created, user.updated, user.deleted |
| Directory Sync | dsync.user.created, dsync.user.updated, dsync.user.deleted, dsync.group.user_added, dsync.group.user_removed |
| Organizations | organization.created, organization.updated |
| Memberships | organization_membership.created, organization_membership.updated, organization_membership.deleted |
| Connections | connection.activated, connection.deactivated, connection.deleted |
| Auth | authentication.email_verification_succeeded |
All events are verified against the webhook signing secret before processing.
The plugin adds Bearer token authentication to the WordPress REST API. Send the WorkOS access token in the Authorization header:
Authorization: Bearer <workos_access_token>
The token is verified using WorkOS JWKS and mapped to a WordPress user via their linked WorkOS ID.
The plugin creates four custom tables on activation:
| Table | Purpose |
|---|---|
{prefix}_workos_organizations |
Cached WorkOS organization data (name, slug, domains) |
{prefix}_workos_org_memberships |
User-to-organization memberships with roles |
{prefix}_workos_org_sites |
Organization-to-site mapping (multisite) |
{prefix}_workos_activity_log |
Local activity log for authentication and sync events |
User linking is stored in standard WordPress usermeta (_workos_user_id, _workos_org_id, _workos_last_synced_at, _workos_deactivated).
All commands are registered under the wp workos namespace.
# Show plugin configuration and health
wp workos status
# Output as JSON
wp workos status --format=jsonDisplays: environment, API key (masked), client ID, organization ID, environment ID, enabled status, login mode, database version, and plugin version. The source column shows whether each value comes from a constant or the database.
# Get a user with WorkOS metadata
wp workos user get <id> [--by=<id|email|workos_id>] [--format=<format>]
# List users with WorkOS link status
wp workos user list [--linked] [--unlinked] [--role=<role>] [--format=<format>] [--fields=<fields>]
# Get user IDs for piping to other commands
wp workos user list --unlinked --format=ids
# Link a WP user to a WorkOS user (validates via API)
wp workos user link <wp_user_id> <workos_user_id>
# Remove WorkOS link from a user
wp workos user unlink <wp_user_id> [--yes]
# Sync a single user: push to WorkOS (default) or pull from WorkOS
wp workos user sync <wp_user_id> [--direction=<push|pull>]
# Import a single WorkOS user into WordPress
wp workos user import <workos_user_id> [--porcelain] [--yes]Examples:
# Find a user by email and show their WorkOS metadata
wp workos user get admin@example.com --by=email
# Look up a user by their WorkOS ID
wp workos user get user_01HXYZ --by=workos_id --format=json
# List all users that haven't been synced to WorkOS yet
wp workos user list --unlinked --role=subscriber
# Pull latest profile data from WorkOS for a linked user
wp workos user sync 42 --direction=pull
# Import a WorkOS user and get just the WP user ID
wp workos user import user_01HXYZ --porcelain --yes# List local organizations
wp workos org list [--source=<local|remote>] [--format=<format>]
# Get a single organization
wp workos org get <id> [--by=<id|workos_id|remote>] [--format=<format>]
# Sync an organization from WorkOS API to local database
wp workos org sync <workos_org_id>
# List organization members
wp workos org members <id> [--by=<id|workos_id>] [--format=<format>]
# Add a user to a local organization
wp workos org add-member <org_id> <user_id> [--role=<role>]
# Remove a user from a local organization
wp workos org remove-member <org_id> <user_id> [--yes]Examples:
# List organizations from the WorkOS API
wp workos org list --source=remote --format=json
# Fetch an org directly from WorkOS without needing a local record
wp workos org get org_01HXYZ --by=remote
# Sync an organization and view its members
wp workos org sync org_01HXYZ
wp workos org members org_01HXYZ --by=workos_id
# Add a user to an org as admin
wp workos org add-member 1 42 --role=adminAll bulk commands support --dry-run to preview changes, --yes to skip confirmation, --limit to cap the number of items processed, and display progress bars during execution.
# Push all unlinked WP users to WorkOS
wp workos sync push [--role=<role>] [--limit=<n>] [--dry-run] [--yes]
# Re-sync all linked users from WorkOS
wp workos sync pull [--limit=<n>] [--dry-run] [--yes]
# Import WorkOS users into WordPress
wp workos sync import [--organization_id=<id>] [--limit=<n>] [--dry-run] [--yes]
# Import all organizations from WorkOS
wp workos sync orgs [--limit=<n>] [--dry-run] [--yes]Examples:
# Preview what a full user push would do
wp workos sync push --dry-run
# Push only subscribers, 50 at a time
wp workos sync push --role=subscriber --limit=50 --yes
# Re-sync all linked users from WorkOS
wp workos sync pull --yes
# Import the first 10 WorkOS users from a specific organization
wp workos sync import --organization_id=org_01HXYZ --limit=10 --yes
# Import all organizations
wp workos sync orgs --yesOutput format: All bulk commands report a summary on completion:
Success: 45 synced, 2 failed, 3 skipped.
Non-fatal errors are shown as warnings during execution and counted in the summary.
All commands that display data support these output formats:
| Flag | Format |
|---|---|
--format=table |
ASCII table (default) |
--format=json |
JSON array |
--format=yaml |
YAML |
--format=csv |
CSV |
--format=ids |
Space-separated IDs (user list only) |
- PHP 7.4+
- Composer
- bun (for JS/CSS assets)
- slic (for running tests)
composer install
bun install # Install JS dependencies
bun run build # Build JS/CSS assets# Using slic (Docker-based)
cd wp-content/plugins
slic here
slic use integration-workos
slic run wpunit
# Using Composer
composer test:wpunit# Check
composer lint
# Auto-fix
composer lint:fixThe plugin uses a DI container (di52) with a feature-controller pattern:
integration-workos.php # Entry point
src/WorkOS/Plugin.php # Bootstrap, container init
src/WorkOS/Controller.php # Main controller, registers feature controllers
src/WorkOS/Config.php # Centralized config with constant overrides
src/WorkOS/
Admin/Controller.php # Settings UI, user list, onboarding, diagnostics
Auth/Controller.php # Login, registration, password reset, redirects
Sync/Controller.php # UserSync, RoleMapper, DirectorySync, AuditLog
REST/Controller.php # Bearer token authentication
Webhook/Controller.php # Webhook receiver
Organization/Controller.php # Organization management, entitlement gate
CLI/Controller.php # WP-CLI commands
UI/Controller.php # Login button (shortcode, block, widget)
ActivityLog/Controller.php # Local activity logging
Each controller extends Contracts\Controller and implements isActive() for conditional activation (e.g., Admin\Controller only activates in is_admin(), CLI\Controller only activates under WP_CLI).