The previous lesson got one provider working. Most real installs end up with two or three configured at once, so provider-preference code (covered fully in Lesson 6) has something to actually fall back to. Before you get there, it’s worth understanding exactly where those keys live, who can read them, and the mistakes that turn “a convenient shortcut while developing” into a real security incident once the plugin ships.
At least one provider plugin installed and configured, from the previous lesson. This lesson doesn’t require a second provider, but the patterns here matter more once you add one.
Step 1: Know where the key actually lives right now
The official provider plugins save the API key you pasted into their settings screen as
a row in the wp_options table, the same table that stores your site’s general
settings, active plugin list, and theme configuration. That means:
- Anyone with database access (your host, a backup service, a misconfigured export) can read it in plain text unless the plugin specifically encrypts it, and most don’t.
- A full database export, the kind you might casually hand to a support contractor to debug an unrelated issue, carries every configured API key along with it.
- A site-cloning tool that copies the database to a staging environment copies the live key too, now two environments can spend against the same billing account.
None of this means the default storage is wrong, wp_options is the standard,
expected place for plugin settings. It means you should treat a database export with
the same care you’d treat the key itself.
Step 2: Never hardcode a key directly in plugin code
It’s tempting, especially while testing, to skip the settings screen and just write:
// File: my-plugin.php - don't do this
define( 'MY_PLUGIN_TEMP_KEY', 'sk-ant-api03-abc123...' );
or worse, to pass a literal key straight into a function call. The problem isn’t WordPress-specific, it’s the same problem with any hardcoded secret:
- If the file ever gets committed to a public (or even private-but-shared) Git repository, the key is in the history forever, rotating it doesn’t remove it from old commits.
- If you paste a code snippet into a support forum or a ticket to debug an issue, the key goes with it.
- If the plugin is ever distributed to a client or another site, your personal key ships inside it.
If a key was ever committed to version control or pasted somewhere public, rotating it in the provider’s dashboard stops future misuse, but treat the original as permanently compromised. Don’t assume “I deleted the commit” or “I edited my forum post” is sufficient, the provider-side revoke-and-reissue is the only real fix.
Step 3: Prefer wp-config.php constants for anything you control directly
For keys used in your own custom integration code (as opposed to the official provider
plugins, which manage their own storage), defining a constant in wp-config.php keeps
the value out of the database and out of your plugin’s version-controlled files:
// File: wp-config.php
define( 'MY_PLUGIN_ANTHROPIC_KEY', 'sk-ant-api03-...' );
// File: my-plugin.php
$key = defined( 'MY_PLUGIN_ANTHROPIC_KEY' ) ? MY_PLUGIN_ANTHROPIC_KEY : '';
if ( '' === $key ) {
// Fail gracefully, don't silently proceed with an empty key.
return new WP_Error( 'missing_api_key', 'No API key configured.' );
}
wp-config.php sits outside the web root on most reasonable hosting setups and is
routinely excluded from version control (it’s in the default .gitignore for most
WordPress starter projects), so this keeps the secret out of both places at once. For a
plugin you intend to distribute to other sites, document this requirement clearly
rather than inventing your own less-standard storage mechanism.
Step 4: Keep multiple providers’ keys straight
Once you’re managing keys for more than one provider, whether through the official plugins’ own settings screens or your own constants, name them so a future reader (or future you) can’t mix them up:
// File: wp-config.php
define( 'MY_PLUGIN_ANTHROPIC_KEY', 'sk-ant-...' );
define( 'MY_PLUGIN_OPENAI_KEY', 'sk-proj-...' );
define( 'MY_PLUGIN_GOOGLE_KEY', 'AIza...' );
Each provider’s key has a recognizable prefix (sk-ant-, sk-proj-, AIza), which is
a small but genuinely useful sanity check, if a constant named ..._OPENAI_KEY holds a
value starting with sk-ant-, something got copy-pasted into the wrong place.
If you’re relying entirely on the official provider plugins and never touch a key directly in your own PHP, most of this is already handled for you, the plugins own their settings screens and their own storage. This lesson matters most once you’re writing custom code that needs a key of its own, or once you’re auditing a site for where secrets actually live.
Test it: confirm a constant-based key resolves correctly
wp-env run cli wp eval 'var_dump( defined( "MY_PLUGIN_ANTHROPIC_KEY" ) );'
This should print bool(true) once the constant is defined in wp-config.php, and
bool(false) (correctly) on any environment where you haven’t set it yet, which is
exactly the signal your fallback code in Step 3 should check for.
Recap
Official provider plugins store their keys in wp_options, standard for a WordPress
setting, but worth remembering when handling database exports or backups. For your own
integration code, define keys as constants in wp-config.php rather than hardcoding
them in plugin files, this keeps secrets out of version control and out of anything you
might paste into a support ticket. Never assume a rotated key erases a previously
leaked one, treat any exposed key as permanently compromised and reissue it from the
provider’s dashboard.
Resources & further reading
- PHP AI Client SDK repository, GitHub
- wp-config.php reference, developer.wordpress.org
- Introducing the AI Client in WordPress 7.0, make.wordpress.org