Safety Pattern

Production Guardrails: Auto-Deactivating on Live URLs

⏱ 18 min

The duplicate-and-approve workflow from Lesson 5 assumes a human is paying attention and will genuinely review a duplicate before approving it. That assumption fails the moment someone wires an agent up to run unattended, or forgets which environment a script is pointed at. This lesson adds a second, independent layer that doesn’t rely on anyone remembering anything: destructive page-builder-editing abilities check WordPress’s own real environment type and refuse to run at all outside development or staging.

What you'll learn in this lesson
wp_get_environment_type(), for real
The actual core function, its possible values, and its default behavior.
Gating a permission_callback on environment
A reusable check that wraps every destructive ability from this course.
Why a URL allowlist is a weaker, secondary check
A belt-and-suspenders comparison against known production domains, and its real limits.
Testing the gate actually blocks something
Simulating production locally and confirming the ability really refuses.
Prerequisites

Lesson 5’s create-edit-duplicate and apply-approved-edit abilities. Course 4’s permission_callback patterns, this lesson adds one more layer on top of those, it doesn’t replace them.

Step 1: What wp_get_environment_type() actually returns

wp_get_environment_type() is a real WordPress core function, available since WordPress 5.5, that returns one of four values: 'local', 'development', 'staging', or 'production'. It reads the WP_ENVIRONMENT_TYPE constant (typically set in wp-config.php) or an equivalent system environment variable of the same name. If neither is set, the documented default is 'production', which means an unconfigured site is already treated as the most restrictive case by default, not the most permissive one.

// File: wp-config.php
define( 'WP_ENVIRONMENT_TYPE', 'staging' );

Step 2: A reusable safety check

Write one function that every destructive ability’s permission_callback calls first:

// File: my-plugin.php
function my_plugin_is_safe_for_ai_editing(): bool {
	$environment = wp_get_environment_type();
	return in_array( $environment, array( 'local', 'development', 'staging' ), true );
}

Step 3: Wire it into the destructive abilities from Lesson 5

The check belongs directly inside each ability’s own permission_callback, registered alongside the rest of the ability, the same place Lesson 5 already checks capabilities. There’s no separate hook for this, an ability’s permission_callback is just a regular PHP callable, so the environment check is simply the first line inside it, run before any capability check:

// File: my-plugin.php
'permission_callback' => function ( $input ) {
	if ( ! my_plugin_is_safe_for_ai_editing() ) {
		return new WP_Error(
			'production_environment_blocked',
			__( 'AI page builder editing is disabled outside staging and development.', 'my-plugin' )
		);
	}

	$original_id = get_post_meta( $input['duplicate_id'], '_ai_edit_of', true );
	if ( ! $original_id ) {
		return new WP_Error( 'not_a_duplicate', __( 'This is not a tracked AI edit duplicate.', 'my-plugin' ) );
	}

	return current_user_can( 'publish_posts' ) && current_user_can( 'edit_post', $original_id );
},
Gate every destructive ability, not just the approval step

Apply my_plugin_is_safe_for_ai_editing() to create-edit-duplicate and every builder element-editing ability too, not only apply-approved-edit. A duplicate created on a production site, even one that never gets approved, is still a real post consuming storage and potentially indexed or discovered before anyone reviews it. The environment gate should block the entire workflow from starting on production, not just its final, most destructive step.

Step 4: A weaker, secondary check for misconfigured hosts

Some hosting environments never set WP_ENVIRONMENT_TYPE correctly, or override it in ways a plugin author doesn’t control. As a defense-in-depth addition, not a replacement, you can also compare the site’s own URL against an explicit allowlist of known non-production domains:

// File: my-plugin.php
function my_plugin_is_known_safe_domain(): bool {
	$safe_domains = array( 'staging.example.com', 'example.local', 'localhost' );
	$host         = wp_parse_url( home_url(), PHP_URL_HOST );
	return in_array( $host, $safe_domains, true );
}

This is deliberately a secondary, custom pattern, not a core API, and it’s weaker than wp_get_environment_type() because it requires maintaining a list by hand and doesn’t benefit from any WordPress-wide convention. Use it as a second opinion alongside the environment check, never as a replacement for it.

Test it: simulate production locally

Temporarily set the environment type to production in your local wp-config.php and confirm the gate actually fires:

# File: terminal
wp config set WP_ENVIRONMENT_TYPE production --type=constant
wp eval '
$result = wp_get_ability( "page-builder-ai/apply-approved-edit" )->execute( array( "duplicate_id" => 123 ) );
var_dump( $result );
'

You should get a WP_Error with the code production_environment_blocked. Set the constant back to staging or development afterward and confirm the same call now proceeds to the capability checks instead of stopping immediately.

An unconfigured site is already locked down, which can surprise you locally

If you spin up a fresh local environment and every destructive ability refuses to run with no obvious reason, check whether WP_ENVIRONMENT_TYPE is actually set at all. Since the documented default is 'production' when it’s missing, a local install that never explicitly configured the constant is correctly, if confusingly, being treated as production. That’s the safe failure mode, but it means you must set the constant explicitly in every local and staging environment rather than assuming a fresh install defaults to something permissive.

Recap

wp_get_environment_type() is a real, current core function returning 'local', 'development', 'staging', or 'production', defaulting to 'production' when unconfigured. Wrapping every destructive page-builder ability’s permission_callback with a check against that value means an AI agent’s builder-editing abilities are structurally unreachable in production, independent of whether anyone remembers to turn them off by hand. A secondary domain allowlist can back this up for misconfigured hosts, but the environment check is the primary, reliable layer.

Resources & further reading

← The Duplicate-and-Approve Workflow Extending the Pattern to WPBakery and Beaver Builder →