Reuse

Storing Reusable Prompt Templates as WordPress Options

⏱ 14 min

Every example so far has kept prompt wording as a PHP string literal inside a method, exactly the pattern Lesson 1 recommended, one place, not copy-pasted. This lesson takes that a step further: moving the wording out of PHP entirely and into a versioned wp_options entry, so the exact phrasing of a system instruction or a few-shot example can be adjusted without a code deploy, and so you have a real place to track what changed and when.

What you'll learn in this lesson
Why hardcoded prompt strings are a maintenance problem
Wording changes should not require a plugin release.
A versioned option shape
Storing a template alongside a version number, not just a bare string.
Registering sensible defaults
add_option() on activation, so the feature works before anyone edits anything.
A safe migration pattern
Adding new default fields later without clobbering an already-customized template.
Prerequisites

Lesson 1’s system instruction patterns. This lesson assumes you already have a working prompt and are looking to change where its wording lives, not what the wording says.

Step 1: Why a hardcoded string becomes a real problem

A prompt string as a PHP constant works fine until someone who isn’t a developer, a content lead, a support manager, needs the tone adjusted, “less formal,” “shorter replies,” “always mention the refund policy.” Right now that means editing PHP and shipping a release. Storing the template in wp_options instead means the exact same change is a database update, something an admin screen, or even a direct wp option update call, can make without touching code at all.

Step 2: A versioned template shape

Store more than a bare string, wrap it with a version number so you can tell later whether an option holds your current default or an older, possibly customized one:

// File: my-plugin/class-prompt-templates.php
define( 'MY_PLUGIN_SUPPORT_TEMPLATE_VERSION', 2 );

function get_default_support_template(): array {
	return array(
		'version'  => MY_PLUGIN_SUPPORT_TEMPLATE_VERSION,
		'template' =>
			'You are drafting support replies for a WordPress plugin support desk. ' .
			'Never promise a specific release date. Never confirm a bug exists ' .
			'before it has been reproduced. Do not use exclamation points. ' .
			'Reply in two short paragraphs: the first acknowledges the issue, the ' .
			'second states the next concrete step.',
	);
}

Step 3: Registering the default and reading it at runtime

Register the default once, on activation, so the feature has working wording from the first request, then read it wherever the prompt is actually used:

// File: my-plugin/my-plugin.php
register_activation_hook( __FILE__, function () {
	add_option(
		'my_plugin_support_reply_template',
		get_default_support_template(),
		'',
		false // autoload: false, this is only read when a support reply is drafted.
	);
} );
// File: my-plugin/class-support-reply-drafter.php
public function draft_reply( string $ticket_body ): string {
	$stored = get_option( 'my_plugin_support_reply_template' );

	$system_instruction = is_array( $stored ) && ! empty( $stored['template'] )
		? $stored['template']
		: get_default_support_template()['template'];

	return wp_ai_client_prompt()
		->with_text( $ticket_body )
		->using_system_instruction( $system_instruction )
		->using_temperature( 0.3 )
		->using_max_tokens( 220 )
		->generate_text();
}

Falling back to get_default_support_template() if the option is somehow missing or malformed means a corrupted or manually cleared option degrades to a working default rather than an empty, useless system instruction.

Set autoload to false for prompt templates

add_option()’s fourth argument controls whether an option loads on every single page request via alloptions. A prompt template is only ever read when an AI feature actually runs, so it doesn’t belong in that autoloaded set, passing false keeps it out of the cache WordPress builds for every request.

Step 4: Migrating when your default template changes

When you ship a new default wording in a plugin update, you don’t want to silently overwrite a site’s already-customized template, but you also want a fresh install, or a site that never touched the setting, to get the improvement. Comparing the stored version number against your current one is the standard pattern:

// File: my-plugin/class-prompt-templates.php
add_action( 'plugins_loaded', function () {
	$stored = get_option( 'my_plugin_support_reply_template' );

	if ( ! is_array( $stored ) ) {
		update_option( 'my_plugin_support_reply_template', get_default_support_template(), false );
		return;
	}

	$default = get_default_support_template();

	// Only replace the template if the site never customized it, and a newer
	// default is available.
	if (
		( $stored['version'] ?? 0 ) < $default['version']
		&& empty( $stored['customized'] )
	) {
		update_option( 'my_plugin_support_reply_template', $default, false );
	}
} );

Setting a customized flag to true whenever an admin screen saves an edited template is what makes this migration safe, it only ever overwrites a template nobody has touched yet, never one a site owner deliberately changed.

Watch for placeholder collisions with real content

If your template format uses a token syntax for substitution, {{post_title}} for instance, and you interpolate raw, user-controlled content into the same string before substitution, a title that happens to contain literal curly braces can collide with your own placeholder syntax. Substitute placeholders first, working only with your own template string, then append or insert user content as separate with_text() calls rather than string-concatenating it into the template itself.

Test it: change wording without touching PHP

wp-env run cli wp option get my_plugin_support_reply_template --format=json

wp-env run cli wp eval '
$current = get_option( "my_plugin_support_reply_template" );
$current["template"] .= " Sign off every reply with the agent name Alex.";
$current["customized"] = true;
update_option( "my_plugin_support_reply_template", $current, false );
'

Run your support reply feature again with no code changes. The output should now consistently include the sign-off you just added purely through the stored option, no deploy required.

Recap

Prompt wording that lives only as a PHP string requires a code release to change even a single word. Storing it instead as a versioned wp_options entry, registered with a sensible default on activation, read with a safe fallback, and migrated carefully so a site’s own customizations are never silently overwritten, turns prompt wording into something editable on its own, separate from your plugin’s release cycle.

Resources & further reading

← Handling Ambiguous or Bad Model Output Storing Prompt Templates as Abilities →