Site Kit Portal Pin by Christopher Ross releases
Pins Site Kit OAuth state across WP Engine Portal copies (dev → prod) so connections survive environment overwrites.
by Christopher Ross · github.com/thisismyurl/sitekit-portal-pin · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/thisismyurl/sitekit-portal-pin/archive/refs/heads/main.zipReadme
Site Kit Portal Pin
Automatically restores Google Site Kit's connection on WP Engine production after a Portal copy overwrites your OAuth state.
The problem
You use Google Site Kit on a WP Engine site. You copy your dev environment to production using WP Engine Portal. Site Kit immediately shows as disconnected — missing_delegation_consent, needs_reauthentication, Google Analytics and Search Console gone from the dashboard. You have to go through the full Google OAuth flow again.
WP Engine Portal copies the database from dev to prod. Dev has empty or stale Site Kit credentials. When those land on production, the connection breaks.
The fix
Site Kit Portal Pin takes a daily snapshot of your production Site Kit auth state and stores it in an HMAC-sealed file outside wp-content/ where Portal copies cannot reach it. When it detects broken post-copy auth on the next admin page load, it silently restores your known-good credentials. No manual re-authentication.
What it does
- Snapshots every
googlesitekit_*option and the Site Kit owner's user meta daily via WP Cron - Stores the snapshot at
dirname( WP_CONTENT_DIR ) . '/.sitekit-prod-snapshot.json'— one level abovewp-content/, untouched by Portal copies - Seals the snapshot with an HMAC using your site's WordPress auth salt; rejects anything that fails integrity check
- Detects broken post-copy state on admin page loads (throttled to once per 5 minutes, administrators only)
- Restores Site Kit options and owner user meta from snapshot when broken state is found
- Guards against stale snapshots — skips restore if snapshot is older than 30 days
What it doesn't do
- No admin settings page. One line in
wp-config.phpis all the configuration there is. - Doesn't intercept or modify Google OAuth flows.
- Doesn't touch anything outside the
googlesitekit_*namespace. - Doesn't run on non-production environments, AJAX requests, WP Cron runs, or for non-administrators.
- Doesn't run on git push deployments (those don't touch the database — no fix needed).
Requirements
- WP Engine hosting with Portal environment copying
- Google Site Kit installed and connected on production
- PHP 7.4+, WordPress 6.0+
Installation
- Upload to
/wp-content/plugins/thisismyurl-sitekit-portal-pin/and activate. - Add your production URL to
wp-config.php:
define( 'SITEKIT_PORTAL_PIN_PROD_URL', 'https://yoursite.com' );
Use the exact URL from Settings > General > Site Address (URL), without a trailing slash.
- Confirm with WP-CLI:
wp sitekit-pin statusshould showProduction: YESandAuth healthy: YES. - Seed the first snapshot:
wp sitekit-pin snapshot(don't wait for the daily cron on a fresh install).
Configuration
Three options, checked in priority order:
| Method | Example |
|---|---|
PHP constant in wp-config.php (recommended) |
define( 'SITEKIT_PORTAL_PIN_PROD_URL', 'https://yoursite.com' ); |
| WP option | update_option( 'sitekit_portal_pin_prod_url', 'https://yoursite.com' ); |
| Filter | add_filter( 'sitekit_portal_pin_prod_url', fn() => 'https://yoursite.com' ); |
Plugin silently no-ops if no production URL is configured.
WP-CLI commands
wp sitekit-pin status # Show enabled state, production flag, auth health, snapshot age, HMAC status, next cron
wp sitekit-pin snapshot # Take a snapshot now (prod + healthy auth required)
wp sitekit-pin restore # Restore from snapshot now (prod required; runs even when the gate is off)
Extending
The plugin exposes filters so you can control its behaviour without editing it. All automatic behaviour is also governed by a single master gate; wp sitekit-pin restore is deliberately exempt so manual recovery is never blocked.
| Filter | Governs |
|---|---|
sitekit_portal_pin_enabled |
Master on/off for automatic snapshot cron and auto-restore. Default true. |
sitekit_portal_pin_is_production |
The environment-detection result. |
sitekit_portal_pin_is_auth_healthy |
The auth-health heuristic (receives the owner user). |
sitekit_portal_pin_should_restore |
Per-trigger gate checked before an auto-restore fires. |
sitekit_portal_pin_max_restore_age |
Age cap in seconds; a snapshot older than this is not auto-restored. 0 disables the check. Default 30 days. |
sitekit_portal_pin_pre_pin |
Short-circuit the cron snapshot (return non-null to skip). |
sitekit_portal_pin_tracked_options |
The full option set snapshot and restore act on (the googlesitekit_* scan plus the baseline floor). |
sitekit_portal_pin_usermeta_prefix |
The owner user_meta key prefix captured. |
sitekit_portal_pin_snapshot_path |
The absolute path the snapshot file is written to and read from. |
sitekit_portal_pin_prod_url |
The production URL used for environment detection. |
// Example: turn off automatic pin/restore in a specific environment.
add_filter( 'sitekit_portal_pin_enabled', '__return_false' );
How the snapshot survives a Portal copy
The snapshot file lives at dirname( WP_CONTENT_DIR ) . '/.sitekit-prod-snapshot.json'. On a typical WP Engine install, this resolves to the document root level — above wp-content/. WP Engine Portal copies replace wp-content/ and the database, but don't touch files above wp-content/. So the snapshot persists across copies.
Security notes
- Snapshot is sealed with
hash_hmac( 'sha256', $body, wp_salt('auth') ). A snapshot that fails HMAC verification is rejected with an admin notice and never restored. - File permissions are set to
0640on write. - Restore path is gated to
manage_options— only administrators can trigger a credential mutation on an admin page load. - On uninstall, the snapshot file is deleted along with the plugin's two DB options.
Versioning
Versions follow X.Yjjj.hhmm — major version, last digit of year, Julian day, 24-hour time (Toronto). For example: 1.6190.1000 = major 1, year 2026, day 190, 10:00.
About
Built by Christopher Ross to solve a real problem on a real site. This is a narrow, focused tool. It does one thing and only one thing.
GitHub: github.com/thisismyurl · WordPress.org: profiles.wordpress.org/thisismyurl
License
GPL-2.0-or-later. See LICENSE.
Read the full README on GitHub →
Releases
| Tag | Published | Asset | Downloads |
|---|---|---|---|
| 1.6216.1411 | Aug 4, 2026 | — | — |
| 1.6190.1000 | Jul 9, 2026 | — | — |
| 1.6148.2110 | May 29, 2026 | — | — |
| v1.6147 | May 27, 2026 | — | — |
| v1.0.1 | May 23, 2026 | — | — |
| v1.0.0 | May 6, 2026 | sitekit-portal-pin.php | 3 |