Title: Proposal: A Secrets API for WordPress 7.2
Author: Eric Mann
Published: August 25, 2026

---

 [  ](https://profiles.wordpress.org/ericmann/) [Eric Mann](https://profiles.wordpress.org/ericmann/)
9:17 pm _on_ August 25, 2026     
Tags: [proposal ( 40 )](https://make.wordpress.org/core/tag/proposal/)

# Proposal: A Secrets API for WordPress 7.2

WordPress has no first-class way to store a credential. This proposal outlines a
Secrets APIAPI An API or Application Programming Interface is a software intermediary
that allows programs to interact with each other and share data in limited, clearly
defined ways. for 7.2, along with WP-CLIWP-CLI WP-CLI is the Command Line Interface
for WordPress, used to do administrative and development tasks in a programmatic
way. The project page is [http://wp-cli.org/](http://wp-cli.org/) [https://make.wordpress.org/cli/](https://make.wordpress.org/cli/)
support for it, and explains why the accompanying UIUI User interface should wait
for 7.3.

### Why it matters

Every pluginPlugin A plugin is a piece of software containing a group of functions
that can be added to a WordPress website. They can extend functionality or add new
features to your WordPress websites. WordPress plugins are written in the PHP programming
language and integrate seamlessly with WordPress. These can be free in the WordPress.
org Plugin Directory [https://wordpress.org/plugins/](https://wordpress.org/plugins/)
or can be cost-based plugin from a third-party. that needs an API key today writes
it to the options table in plaintext. That is the only available path, so this isn’t
a criticism of plugin authors. The consequence is that credentials end up in the
same table as the site tagline, and therefore in every database dump, every backup,
every staging clone, and every `wp option get` in a shared terminal.

Plugins have been solving this independently for years, each with its own key derivation,
its own cipher choice, and its own failure modes. Site Kit encrypts its Google credentials.
WooCommerce gateways handle payment keys. SMTP plugins hold mail credentials. Each
one is a separate audit surface, and none of them can be reviewed once on behalf
of the ecosystem.

This was survivable when the average site held a Mailchimp key and a reCAPTCHA secret.
AI service integrations have made credentials both more numerous and far more expensive
to lose: a leaked model-provider key is a metered spend liability. The blast radius
increased while the storage mechanism stayed the same.

A second gap is that site owners and hosts increasingly need to answer basic operational
questions — which credentials exist on this site, what code is using them, when 
were they last rotated. None of those are answerable today, because there is no 
object to ask about. A credential is just a row in a key-value store.

CoreCore Core is the set of software required to run WordPress. The Core Development
Team builds WordPress. has no concept of a secret; there’s nothing for either problem
to attach to.

### Clarifying priorities

A tightly-scoped, stable API landed in 7.2 helps push forward on requirements like
those documented in [the Security Audit for the Connectors screen](https://core.trac.wordpress.org/ticket/64789).

**No new UI in 7.2.** The storage and retrieval semantics are the part that must
be right the first time, because every plugin, every CLICLI Command Line Interface.
Terminal (Bash) in Mac, Command Prompt in Windows, or WP-CLI for WordPress. command,
and every future adminadmin (and super admin) screen inherits them. A settings screen
can be designed properly in 7.3 once there’s a stable API to build against and real
usage to design from. The hooksHooks In WordPress theme and development, hooks are
functions that can be applied to an action or a Filter in WordPress. Actions are
functions performed when a certain event occurs in WordPress. Filters allow you 
to modify certain functions. Arguments used to hook both filters and actions look
the same. and accessors an admin screen would need are in scope now; the screen 
itself is not.

**WP-CLI is in scope.** It gives the API a first-party consumer inside the same 
release, which is the most reliable way to discover whether the surface is actually
usable before it freezes. It also closes a real leak of its own: commands that accept
credentials as arguments expose them in shell history and in the process list on
shared hosts. Reading and writing through the API fixes that in the same change.

**A feature pluginFeature Plugin A plugin that was created with the intention of
eventually being proposed for inclusion in WordPress Core. See [Features as Plugins](https://make.wordpress.org/core/handbook/about/release-cycle/features-as-plugins/)
ships before the core patchpatch A special text file that describes changes to code,
by identifying the files and lines which are added, removed, and altered. It may
also be referred to as a diff. A patch can be _applied_ to a codebase for testing..**
Rather than propose an API and ask people to evaluate it from a design document,
I’ll publish the implementation as a plugin first. This will let contributors run
the API instead of reading about it, expose the awkward parts of the surface while
they’re still inexpensive to change, and give sites on 7.0 and 7.1 something usable
immediately rather than waiting for December. The core patch then follows with an
API surface identical to the plugin’s; nothing anyone builds against it needs rewriting.

The plugin will prove out the API before we consolidate things into core.

### Proposed API

A small function set, a value object, and always-on encryption.

    ```notranslate
    wp_set_secret( string $name, string $value ): bool|WP_Error
    wp_get_secret( string $name, string $version = WP_Secret_Version::CURRENT ): WP_Secret|null|WP_Error
    wp_delete_secret( string $name ): bool|WP_Error
    wp_import_option_as_secret( string $option, string $name ): bool|WP_Error

    WP_Secret::reveal(): string
    WP_Secret::fingerprint(): string
    ```

At a call site:

    ```notranslate
    wp_set_secret( 'my-plugin/api-key', $key_from_form );

    $secret = wp_get_secret( 'my-plugin/api-key' );

    if ( is_wp_error( $secret ) ) {
        // Exists, but unusable. Key material changed. Tell the user.
    } elseif ( null === $secret ) {
        // Doesn't exist. Show the connect flow.
    } else {
        $client = new API_Client( $secret->reveal() );
    }
    ```

Supporting surface:

 * Secrets are namespaced by convention, `plugin-slug/secret-name`, so a future 
   admin screen can group by owner and cross-namespace access has something to check
   against.
 * `WP_Secret` masks itself in logs, `var_dump()`, and error output. Getting the
   raw string requires an explicit `->reveal()`, which makes every point of use 
   greppable.
 * Default storage is the options API — ciphertext blobs, `autoload=no`, excluded
   from options.php and the REST settings endpoint. The same relationship the object
   cache has to options: the default substrate, replaceable by a drop-in.
 * Two capabilitiescapability A **capability** is permission to perform one or more
   types of task. Checking if a user has a capability is performed by the `current_user_can`
   function. Each user of a WordPress site might have some permissions but not others,
   depending on their role. For example, users who have the Author role usually 
   have permission to edit their own posts (the “edit_posts” capability), but not
   permission to edit other users’ posts (the “edit_others_posts” capability)., `
   manage_secrets` and `manage_network_secrets`.
 * A change hook fires on write, carrying actor, timestamp, and old and new fingerprints.
   Fingerprints are readable; values are not.

Design decisions most likely to draw objections follow, with rationale.

#### Encryption is not optional

Secrets are encrypted at rest under a master-key envelope: a per-secret data key,
wrapped by a master key. There is no plaintext mode and no constant to disable encryption.

The obvious objection is data loss — a site rotates its salts or migrates hosts,
key material changes, and the secrets no longer decrypt. Plaintext options never
do that. With an envelope, individual secrets are encrypted under a random master
key, and the site key only wraps that master key. Rotation re-wraps a single value(
the master key) without touching stored secrets. A dedicated constant is the preferred
key source, with a zero-config fallback derived from existing salts for sites that
can’t set one. When key material really is gone, the failure is bounded and visible:
Site Health reports undecryptable secrets, and the recovery path is to re-enter 
them. Nothing is silently corrupted.

Site Kit is the empirical case. It derives from `LOGGED_IN_KEY` and `LOGGED_IN_SALT`
by default, recommends a dedicated constant, and documents that salt rotation breaks
stored credentials — and it has shipped that way at enormous scale. The ecosystem’s
response was to document the approach, not to reject it.

**Rationale:** an API that can be configured to store plaintext is an options API
with a misleading name. Making encryption unconditional is what lets a plugin author
make a claim to their users about how the key is stored. The alternative — encryption
off unless a site opts in — protects the small number of sites that rotate salts
by leaving everyone else in plaintext forever. Sites that would opt in are largely
the sites already capable of running a drop-in. Sites that would not are exactly
the ones the API exists for.

Availability of cryptographic APIs is not a concern: libsodium has been bundled 
with PHPPHP The web scripting language in which WordPress is primarily architected.
WordPress requires PHP 7.4 or higher since 7.2, and core additionally ships `sodium_compat`
for the cases where the extension has been disabled at build time. The primitives
are present on every supported configuration without a new dependency.

#### Retrieval returns one of three things

`wp_get_secret()` returns a `WP_Secret` on success, `null` when the secret does 
not exist, and `WP_Error` when it exists but could not be retrieved — wrong key,
unavailable keyring, failed decryption.

**Rationale:** collapsing “absent” and “broken” into a single false-y value is how
you get a site that erroneously re-runs its onboarding flow, or disables an integration,
when the real problem is key management. Separating them is also what lets a site
distinguish a missing credential from an unauthorized change to key material.

#### No filterFilter Filters are one of the two types of Hooks 󠀁[https://codex.wordpress.org/Plugin_API/Hooks](https://codex.wordpress.org/Plugin_API/Hooks)󠁿. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. on the retrieval path

There is no filter applied to a secret on its way out of storage. No endpoint, filter,
or capabilitycapability A **capability** is permission to perform one or more types
of task. Checking if a user has a capability is performed by the `current_user_can`
function. Each user of a WordPress site might have some permissions but not others,
depending on their role. For example, users who have the Author role usually have
permission to edit their own posts (the “edit_posts” capability), but not permission
to edit other users’ posts (the “edit_others_posts” capability). returns a secret’s
value other than the API call itself.

**Rationale:** I expect the most pushback here — a filter on secret retrieval is,
by construction, a hook that receives every credential on the site in plaintext.
Any plugin could register it, making it a documented interception point. A filter
that can intercept a credential is a filter that can steal one. The flexibility 
WordPress normally gets from filters is provided instead through explicit, replaceable
providers, described next.

#### Two extension points, independently replaceable

A drop-in, `secrets.php`, exposes storage and keyring as separate extension points:
where ciphertext is stored (Vault, Parameter Store, a host API) and what wraps the
master key (a KMS, an HSM). Neither is ever handed a plaintext secret, and neither
can turn encryption off.

**Rationale:** these are different concerns and sites have different constraints.
Replacing only the keyring while keeping default storage is a reasonable configuration,
and so is the inverse. Coupling them into one swap would force an all-or-nothing
decision that most sites can’t make, and would push people back to storing credentials
in options.

#### Two version slots, not unbounded history

`CURRENT` and `PREVIOUS`, modeled as string constants on a `final class WP_Secret_Version`
rather than a native enum. Overwriting a secret keeps the old value recoverable,
so a mistyped key is fixable. Requests already in flight won’t fail mid-rotation.
Retiring the previous slot is an explicit operator action — no timers, no cron.

**Rationale:** named version history would keep every credential a site has ever
held recoverable from a backup indefinitely. Two slots covers the real use case.
Retirement is operator-driven because core has no way to know when a third-party
integration has finished draining the old value; a timer would just guess wrong 
on a schedule. Native enums require PHP 8.1, and while 8.3 or better is the recommended
version, [core’s minimum remains 7.4](https://wordpress.org/about/requirements/)—
so string constants on a `final class` are the portable equivalent.

#### Multisitemultisite Used to describe a WordPress installation with a network of multiple blogs, grouped by sites. This installation type has shared users tables, and creates separate database tables for each blog (wp_posts becomes wp_0_posts). See also **network**, **blog**, **site** is in v0

Networknetwork (versus site, blog)-level secrets are supported. Salts are network-
wide, so distinct option rows would give logical separation and no cryptographic
separation; instead a network root key derives per-site subkeys via `sodium_crypto_kdf_derive_from_key()`.
Site secrets and network secrets are separate functions with separate capabilities,
and there is no implicit fallback from one to the other.

**Rationale:** adding this later would mean introducing a second key hierarchy alongside
the first, and migrating between them. The useful consequence of doing it now: rotating
the key on a 500-site network re-wraps one value rather than 500.

#### Import, don’t migrate

No automatic sweep of the options table. `wp_import_option_as_secret()` lets a plugin
author move one known option deliberately, on their own explicit upgrade schedule—
and flags the imported secret for rotation rather than merely reporting success.

**Rationale:** core cannot reliably tell which options are credentials, and guessing
would break sites in ways close to undebuggable. The rotation flag matters just 
as much: a credential that has been sitting in `wp_options` is already in backups,
replicas, and object caches, and encrypting it now does not change that. Rotating
is what actually fixes it.

#### Other decisions worth stating

 * **Strings in, strings out.** The API does not serialize. Nothing stops a plugin
   from `json_encode()`-ing something and storing the result, but core neither encourages
   it nor unpacks it, so a secret can never expand into an object graph on read.
 * **Fail closed.** If an external store or key backend is unreachable, reads and
   writes return an error. There is no fallback to local storage or local key wrapping.
 * **Nothing plaintext enters the object cache.** On shared hosting a persistent
   object cache is shared infrastructure. `WP_Secret` refuses serialization outright.
 * **No export.** Values are write-only. Migrations and staging pushes mean re-entry
   at the destination, so staging never holds production credentials. Fingerprints
   let an admin confirm the re-entered value matches.

### What this does and does not do

The API is a hardening interface. If a secret lives in the WordPress database and
a function exists to decrypt it, then any code running as WordPress can obtain that
secret.

In scope: database dumps and exfiltration, backups sitting in cloud storage, SQL
injection reading `wp_options`, careless disclosure surfaces — options.php, export
files, support screenshots, screen shares, debug logs — and a compromised read-only
replica.

Out of scope: code execution in the WordPress process. Nothing defends against that.
Code running inside WordPress can call `wp_get_secret()` for the same reason it 
can read environment variables or wp-config.php — the secret has to be usable, so
anything that is WordPress can use it. Registering a secret against a plugin slug
does not prevent a different plugin from asking for it; slugs aren’t authenticated,
and even a perfect allowlist wouldn’t stop code that reads the revealed value out
of memory after a legitimate call. This proposal makes no claim of per-plugin isolation,
and masked values are hygiene against shoulder-surfing rather than a privilege boundary.

What changes is the value of a stolen database, which is the overwhelmingly more
common breach.

What it does provide:

 * Credentials are not sitting in plaintext in a table that gets dumped, cloned,
   and shared.
 * Access has one chokepoint, which makes it loggable and auditable for the first
   time.
 * Rotation and retirement are defined operations rather than an `UPDATE` and a 
   hope.
 * The storage extension point lets a host or site owner move secrets out of WordPress—
   which is the only configuration where real per-caller enforcement becomes possible.

### Prior art

 * Site Kit’s credential encryption, [linked from comment #1 of the Connectors security audit](https://core.trac.wordpress.org/ticket/64789#comment:1).
 * The open ticketticket Created for both bug reports and feature development on
   the bug tracker. and pull request on the `options.php` plaintext display.
 * [@dpknauss’s comment on write-side governance](https://core.trac.wordpress.org/ticket/64789#comment:12),
   which is where the fingerprint and change-notification design came from.
 * [A proof-of-concept plugin I shipped roughly six months ago](https://github.com/ericmann/displace-secrets-manager)
   after a discussion about encrypting TOTP tokens for the Two Factor plugin, implementing
   the master-key envelope, the provider model, and a WP-CLI surface. The `wp_`-
   prefixed function names here are proposed rather than shipped.

### Timeline

[Beta 1 for 7.2 is October 20–22](https://make.wordpress.org/core/2026/08/12/wordpress-7-2-call-for-volunteers/),
which is the practical deadline for an API landing in this release. Working backward:

 * **Now through mid-September:** feedback on this proposal, particularly the questions
   below. Feature plugin published for testing.
 * **Late September:** patch on TracTrac An open source project by Edgewall Software
   that serves as a bug tracker and project management tool for WordPress., with
   the API surface matching the plugin.
 * **Before BetaBeta A pre-release of software that is given out to a large group
   of users to trial under real conditions. Beta versions have gone through alpha
   testing in-house and are generally fairly close in look, feel and function to
   the final product; however, design changes often occur as part of the process.
   1:** committed, or explicitly deferred to 7.3 rather than rushed in.

I’ve volunteered for technical contributions to 7.2 and intend to do this work rather
than propose it and hand it off.

### Feedback wanted

Objections now, while the surface is still cheap to change. Specifically:

 1. **The no-filter decision on retrieval.** Providers are meant to cover what a filter
    would normally give you — is that substitution sufficient for the cases you’d actually
    need to hook?
 2. **Two version slots.** Is `CURRENT`/`PREVIOUS` sufficient, or is there a rotation
    pattern that genuinely needs more?
 3. **Import, not migrate.** Plugin authors: does `wp_import_option_as_secret()` fit
    how you would actually move an existing key, or does it need a different shape?
 4. **WP-CLI surface.** Which commands most need this, and in what order?
 5. **Hosts.** If you run a real secret store or a real key backend and the drop-in
    surface is missing something you’d need, that’s the most useful feedback available—
    and much easier to add before anything ships than after.

Any feedback is welcome — please share your thoughts in the comments below.

Props to [@jeffpaul](https://profiles.wordpress.org/jeffpaul/), who talked me into
building the original proof of concept six months ago, and [@whyisjake](https://profiles.wordpress.org/whyisjake/)
for shepherding this proposal.

[#proposal](https://make.wordpress.org/core/tag/proposal/)

### Share this:

 * [  Threads ](https://make.wordpress.org/core/2026/08/25/proposal-a-secrets-api-for-wordpress-7-2/?share=threads)
 * [  Mastodon ](https://make.wordpress.org/core/2026/08/25/proposal-a-secrets-api-for-wordpress-7-2/?share=mastodon)
 * [  Bluesky ](https://make.wordpress.org/core/2026/08/25/proposal-a-secrets-api-for-wordpress-7-2/?share=bluesky)
 * [  X ](https://make.wordpress.org/core/2026/08/25/proposal-a-secrets-api-for-wordpress-7-2/?share=x)
 * [  Facebook ](https://make.wordpress.org/core/2026/08/25/proposal-a-secrets-api-for-wordpress-7-2/?share=facebook)
 * [  LinkedIn ](https://make.wordpress.org/core/2026/08/25/proposal-a-secrets-api-for-wordpress-7-2/?share=linkedin)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F08%2F25%2Fproposal-a-secrets-api-for-wordpress-7-2%2F%23respond&locale=en_US)

 1.  ![](https://secure.gravatar.com/avatar/d6d6254d551f10047c16ac27288de32bfa5d96a399f1bcf618ac6b49656424f7?
     s=32&d=mm&r=g)
 2.   [Chris Reynolds](https://profiles.wordpress.org/jazzs3quence/)  11:46 pm _on_
     August 25, 2026
 3.  Hey 👋 Pantheon DevRel here. We’ve been running an external secrets store in production
     since 2024 (although it only became generally available to all customers earlier
     this year) so responding to #5.
 4.  The biggest gap for us is the write path. Our secrets are read-only from the application;
     writes go through our CLICLI Command Line Interface. Terminal (Bash) in Mac, Command
     Prompt in Windows, or WP-CLI for WordPress. tool, Terminus or the dashboard. That’s
     typical for Vault and other services too, where the runtime role has read-only
     policy by design. A storage drop-in for us would need to serve `wp_get_secret()`
     and reject `wp_set_secret()` — but the APIAPI An API or Application Programming
     Interface is a software intermediary that allows programs to interact with each
     other and share data in limited, clearly defined ways. has no way to express “
     readable, but not writable here,” and every pluginPlugin A plugin is a piece of
     software containing a group of functions that can be added to a WordPress website.
     They can extend functionality or add new features to your WordPress websites. 
     WordPress plugins are written in the PHP programming language and integrate seamlessly
     with WordPress. These can be free in the WordPress.org Plugin Directory [https://wordpress.org/plugins/](https://wordpress.org/plugins/)
     or can be cost-based plugin from a third-party. settings screen assumes `set()`
     works. Is a read-only storage provider in scope, and if so what should a plugin
     do with that WP_Error?
 5.  Related: the proposal says the storage provider is never handed a plaintext secret.
     For a platform store that’s inverted — the store *is* the encryption boundary,
     and it *needs* the plaintext over an authenticated channel so the same secret 
     is usable from other environments on the same account. If the drop-in only ever
     sees WP’s ciphertext, we’d be double-encrypting and the value would be opaque 
     outside WordPress.
 6.  Happy to go deeper on any of this, including getting the right engineers into 
     the conversation (although at least some of them aren’t in WP SlackSlack Slack
     is a Collaborative Group Chat Platform [https://slack.com/](https://slack.com/).
     The WordPress community has its own Slack Channel at [https://make.wordpress.org/chat/](https://make.wordpress.org/chat/)).
 7.   * ![](https://secure.gravatar.com/avatar/c21ff90e030a07e3a20f48a0496530828cf86ef69a464d326351423eb2570ef0?
        s=32&d=mm&r=g)
      *  [Ryan McCue](https://profiles.wordpress.org/rmccue/)  11:55 am _on_ August
        27, 2026
      * Strong +1 on the write and encryption concerns here, and as such, a -1 on the
        overall proposal as is.
      * We also operate a secret store on Altis, which under the hood is backed by 
        hardware security modules/etc (AWS Parameter Store). For us to have effective
        security boundaries, these really need to be read-only from the WordPress side,
        and we manage those in the hosting panel. Our current APIAPI An API or Application
        Programming Interface is a software intermediary that allows programs to interact
        with each other and share data in limited, clearly defined ways. looks like`
        get_secret( string $key ) : string`, and that’s it.
      * The encryption also _can’t_ be handled in WordPress, as we need to ensure it’s
        handled by the external HSMs – the site itself can’t (physically) have access
        to the encryption keys without compromising the security of the secrets.
      * As the proposal stands, it’s not feasible for pluggable external secret stores
        as it doesn’t match with the paradigms used elsewhere for secret storage.
      * If the secret storage providers are “never handed a plaintext secret” and encryption
        is handled at a higher layer, it’s also not clear to me what benefit this would
        have over regular options with an encrypted value anyway.
         - ![](https://secure.gravatar.com/avatar/d6d6254d551f10047c16ac27288de32bfa5d96a399f1bcf618ac6b49656424f7?
           s=32&d=mm&r=g)
         -  [Chris Reynolds](https://profiles.wordpress.org/jazzs3quence/)  4:16 pm
           _on_ August 27, 2026
         - > Our current APIAPI An API or Application Programming Interface is a software
           > intermediary that allows programs to interact with each other and share
           > data in limited, clearly defined ways. looks like `get_secret( string 
           > $key ) : string`, and that’s it.
         - Yep, ours is basically the same. We define a `function pantheon_get_secret(
           $key, $default = NULL)` in our PHPPHP The web scripting language in which
           WordPress is primarily architected. WordPress requires PHP 7.4 or higher
           prepend fine, there is no way to `_set` in the code (intentionally).
         - > it’s also not clear to me what benefit this would have over regular options
           > with an encrypted value anyway.
         - That was the smell I was getting, too, thanks for articulating that [@rmccue](https://profiles.wordpress.org/rmccue/)
           😁
         - ![](https://secure.gravatar.com/avatar/86bb3fa9fcfe57b8d313e527e9e02bde810951b25722b4717b0953a6c8db41b2?
           s=32&d=mm&r=g)
         -  [Eric Mann](https://profiles.wordpress.org/ericmann/)  8:45 pm _on_ September
           4, 2026
         - > the APIAPI An API or Application Programming Interface is a software intermediary
           > that allows programs to interact with each other and share data in limited,
           > clearly defined ways. has no way to express “readable, but not writable
           > here,”
         - This is fairly straight-forward to address by making the setter itself a
           noop. And I entirely understand the use case here (it makes sense) of only
           allowing writes via the CLICLI Command Line Interface. Terminal (Bash) in
           Mac, Command Prompt in Windows, or WP-CLI for WordPress. rather than the
           application – I aim to support that directly.
         - > We also operate a secret store on Altis, which under the hood is backed
           > by hardware security modules/etc (AWS Parameter Store).
         - I kind of glossed over this in the first pass. The end goal is to allow 
           managed hosts to be _more secure_ than the default, never less. So in addition
           to everything outlined here already, the goal is to allow for a host to 
           swap in their own `Provider` implementation to defer to an HSM or other 
           more secure backing for protecting the secrets.
         - To that end, [https://github.com/ericmann/secrets-api](https://github.com/ericmann/secrets-api)
           ships a first pass implementation of the proposal and also bundles an example
           drop-in using AWS Secrets Store as an external provider to illustrate the
           approach.
      * ![](https://secure.gravatar.com/avatar/92bfcd3a8c3a21a033a6484d32c25a40b113ec6891f674336081513d5c98ef76?
        s=32&d=mm&r=g)
      *  [Kaspars](https://profiles.wordpress.org/kasparsd/)  2:51 pm _on_ August 29,
        2026
      * For hosting platforms that handle the decryption outside of PHPPHP The web 
        scripting language in which WordPress is primarily architected. WordPress requires
        PHP 7.4 or higher, wouldn’t it be possible to support `wp_get_secret()` and
        just noop the setter? The `WP_Error` returned by the setter could even provide
        platform-specific instructions for how to handle it.
      * I believe this proposal attempts to provide a standardized way for storing 
        sensitive values in the database on regular WP installs that are currently 
        stored in plain-text.
      * Is it ideal that the encryption key exists in the same PHP process that is 
        parsing the encrypted material? No. But it is a significant improvement over
        the current plain-text values in the DB.
         - ![](https://secure.gravatar.com/avatar/86bb3fa9fcfe57b8d313e527e9e02bde810951b25722b4717b0953a6c8db41b2?
           s=32&d=mm&r=g)
         -  [Eric Mann](https://profiles.wordpress.org/ericmann/)  8:48 pm _on_ September
           4, 2026
         - Yes, that will be the ultimate goal here. For hosts/platforms that can provide
           a _more secure_ implementation than the default we absolutely want to support
           that. We just want to make it as hard as possible to accidentally _reduce_
           the security around secrets.
         - [https://github.com/ericmann/secrets-api/tree/main/examples/aws-secrets-manager](https://github.com/ericmann/secrets-api/tree/main/examples/aws-secrets-manager)
           is an example within the current pre-alpha build of the feature pluginFeature
           Plugin A plugin that was created with the intention of eventually being 
           proposed for inclusion in WordPress Core. See [Features as Plugins](https://make.wordpress.org/core/handbook/about/release-cycle/features-as-plugins/)
           that defers to storing secrets in AWS Secrets manager. A vast improvement
           over either plain text or even encrypted values in the WP database.
 8.  ![](https://secure.gravatar.com/avatar/6c0e8a6bbd6947458422aa00742209c32bdf5246702631125f48b158aacf1f03?
     s=32&d=mm&r=g)
 9.   [emrl](https://profiles.wordpress.org/emrl/)  1:27 am _on_ August 26, 2026
 10. This is excellent, great write-up. We just implemented this using the `defuse/
     php-encryption` library and WP hooksHooks In WordPress theme and development, 
     hooks are functions that can be applied to an action or a Filter in WordPress.
     Actions are functions performed when a certain event occurs in WordPress. Filters
     allow you to modify certain functions. Arguments used to hook both filters and
     actions look the same. to automatically encrypt/decrypt values when setting/getting
     specific options. Thank you for also calling out the use of an optional dedicated
     constant.
 11. ![](https://secure.gravatar.com/avatar/cd18da60d54f0a7374dc62b122ff5050f33fad8c43ce013594f7703a37b9d906?
     s=32&d=mm&r=g)
 12.  [Andrea Fercia](https://profiles.wordpress.org/afercia/)  5:19 am _on_ August
     26, 2026
 13. Interesting proposal, thank you.
 14. ![](https://secure.gravatar.com/avatar/88e6359d68a9a5cb853e2738b07ce5e831892e39667d786da1331d3a59df1300?
     s=32&d=mm&r=g)
 15.  [Brian Haas](https://profiles.wordpress.org/masteradhoc/)  5:39 am _on_ August
     26, 2026
 16. Thank you [@ericmann](https://profiles.wordpress.org/ericmann/) for the technical
     work and preparing the proposal after the great discussion we’ve had months ago
     in the #coreCore Core is the set of software required to run WordPress. The Core
     Development Team builds WordPress.-passwords two-factor chat!
 17. Strong +1 on this. I’d love to see this implemented for Two Factor. There would
     be a huge benefit not having plugins go their own way in the future but getting
     this new secret-api integrated instead!
 18. ![](https://secure.gravatar.com/avatar/4982706c2f509f37b69fcab9a4f744a0036b341844a56f92b36caba1d8c2aa63?
     s=32&d=mm&r=g)
 19.  [Stefano Picco](https://profiles.wordpress.org/spicone/)  11:11 am _on_ August
     26, 2026
 20. That sounds great and makes a lot of sense; in my opinion, it would be a good 
     and important feature.
 21. ![](https://secure.gravatar.com/avatar/c95de1e6c222121175c2c0dddf4631d2ea45704938f53332d12710379c6153e6?
     s=32&d=mm&r=g)
 22.  [Jeffrey Paul](https://profiles.wordpress.org/jeffpaul/)  1:22 pm _on_ August
     26, 2026
 23. Note that the [POC plugin](https://github.com/ericmann/displace-secrets-manager)
     is the coreCore Core is the set of software required to run WordPress. The Core
     Development Team builds WordPress. of the [Key Encryption experiment within the AI plugin](https://github.com/WordPress/ai/blob/develop/docs/experiments/key-encryption.md),
     and that we can (and should!) continue to use that implementation space to iterate
     and test the UXUX User experience/DX/etc of this concept more broadly ahead of
     any potential core merge.
 24. ![](https://secure.gravatar.com/avatar/0eca0107f7eef0e98bf6be22ef5ac7adc8e3729e54a5ba2b264fa5ddbfa7cd41?
     s=32&d=mm&r=g)
 25.  [methnen](https://profiles.wordpress.org/methnen/)  4:57 pm _on_ August 26, 2026
 26. Brilliant idea. Would love to see this as a part of the coreCore Core is the set
     of software required to run WordPress. The Core Development Team builds WordPress..
 27. ![](https://secure.gravatar.com/avatar/0e92005e013af6afbd228cb868f6ea2edd299143b9bdf650c820115052cd62f7?
     s=32&d=mm&r=g)
 28.  [Darin Kotter](https://profiles.wordpress.org/dkotter/)  6:50 pm _on_ August 
     26, 2026
 29. This is great and would help on many projects I’ve worked on that end up building
     more or less this same thing.
 30. One thing I’d flag is we integrated the underlying PoC into the AI pluginPlugin
     A plugin is a piece of software containing a group of functions that can be added
     to a WordPress website. They can extend functionality or add new features to your
     WordPress websites. WordPress plugins are written in the PHP programming language
     and integrate seamlessly with WordPress. These can be free in the WordPress.org
     Plugin Directory [https://wordpress.org/plugins/](https://wordpress.org/plugins/)
     or can be cost-based plugin from a third-party. and one thing we’ve gotten some
     reports on is the Access Control section in that PoC README:
 31.     ```notranslate
         The SDK enforces namespace-based access control:
     
         Own namespace — A plugin can always read/write secrets in its own namespace (my-plugin/*).
         Cross-namespace — Requires the manage_secrets capability (granted to administrators by default).
         CLI — WP-CLI commands bypass namespace restrictions (shell access implies trust).
         ```
     
 32. This has caused some confusion where people think that a secret written by one
     plugin can’t be accessed by any other plugin, which maybe that PoC attempts but
     doesn’t seem to work (and really it’s about impossible to prevent another plugin
     from doing whatever they want anyway). I don’t see anything in this proposal on
     Access Control so this may not matter at all here, just flagging something that’s
     caused confusion from the initial PoC.
 33. ![](https://secure.gravatar.com/avatar/ee421d3fd85b44d745307f4aba5cdfac6c8b05237fbfe7d443b9827d164ba234?
     s=32&d=mm&r=g)
 34.  [Eduard Doloc](https://profiles.wordpress.org/rwky/)  8:56 am _on_ August 27,
     2026
 35. that is a really good idea, idk why we (as a community) haven’t came up with this
     earlier!
 36. ![](https://secure.gravatar.com/avatar/6845456dbc9056f20a4cd50a6b95db78f2ae31df5c0601c613ee5b8939c41a82?
     s=32&d=mm&r=g)
 37.  [Jon Brown](https://profiles.wordpress.org/jb510/)  12:56 pm _on_ August 27, 
     2026
 38. Brilliant and 100% needed. A couple questions though.
 39. Is there anyway to prevent pluginPlugin A plugin is a piece of software containing
     a group of functions that can be added to a WordPress website. They can extend
     functionality or add new features to your WordPress websites. WordPress plugins
     are written in the PHP programming language and integrate seamlessly with WordPress.
     These can be free in the WordPress.org Plugin Directory [https://wordpress.org/plugins/](https://wordpress.org/plugins/)
     or can be cost-based plugin from a third-party. A from reading the secret stored
     by Plugin B. I can’t really think of a reliable way, but since we’re essentially
     trying to prevent secret exfiltration from a compromised live site, it seems this
     would be needed. Is this what you meant by “ cross-namespace access has something
     to check against”? Unclear how that’d work if so.
 40. Consider what happens when the salts or encryption key needs to be rotated, is
     there a decrypt and reencrypt that can be automated. Is this the reason for storing
     current and previous values? I think so, just wanted to be certain.
 41. Architecture wise seems ok, but I’d favor putting these into a .env or other dot
     file rather than the DB. Easier to exclude from migrations, DB dumps, backups,
     etc than buried in the DB.
 42. Certainly all much better than the stays quo.
 43.  Agreed with no filterFilter Filters are one of the two types of Hooks [https://codex.wordpress.org/Plugin_API/Hooks](https://codex.wordpress.org/Plugin_API/Hooks).
     They provide a way for functions to modify data of other functions. They are the
     counterpart to Actions. Unlike Actions, filters are meant to work in an isolated
     manner, and should never have side effects such as affecting global variables 
     and output. on retrieval, 2 slots, multi site v0
 44. Whether scoped for and intended or not there are going to be people that want 
     to use this for other data, think PII, maybe even HIPAA adjacent data. Even if
     it’s just storing the name/id from sensitive form entries.
 45.  * ![](https://secure.gravatar.com/avatar/86bb3fa9fcfe57b8d313e527e9e02bde810951b25722b4717b0953a6c8db41b2?
        s=32&d=mm&r=g)
      *  [Eric Mann](https://profiles.wordpress.org/ericmann/)  9:01 pm _on_ September
        4, 2026
      * > Is there anyway to prevent pluginPlugin A plugin is a piece of software containing
        a group of functions that can be added to a WordPress website. They can extend
        functionality or add new features to your WordPress websites. WordPress plugins
        are written in the PHP programming language and integrate seamlessly with WordPress.
        These can be free in the WordPress.org Plugin Directory [https://wordpress.org/plugins/](https://wordpress.org/plugins/)
        or can be cost-based plugin from a third-party. A from reading the secret stored
        by Plugin B.
      * There are ways to make it hard but no way to make it impossible. At the lowest
        level we can check call paths to allowlist which files can retrieve which secrets.
        But it’s trivially possible for an attacker to work around that. I haven’t 
        implemented those controls at all as I don’t want to give anyone a false sense
        of security …
      * > is there a decrypt and reencrypt that can be automated. Is this the reason
        > for storing current and previous values?
      * Rotation is key anywhere. The coreCore Core is the set of software required
        to run WordPress. The Core Development Team builds WordPress. goal is to use
        key encapsulation to limit what you need to re-encrypt whenever re-encryption
        is needed. So rather than re-encrypting each _secret_ when you rotate things,
        you instead re-encrypt a higher-level wrapper.
      * But storing the current and previous values also allows for more defensive 
        programming when secrets are being used – this is a common pattern for zero-
        downtime rotations. If, say, you want to set up a new password for a remote
        system you might update your secret to use that password. But until it exists
        on the remote system, APIAPI An API or Application Programming Interface is
        a software intermediary that allows programs to interact with each other and
        share data in limited, clearly defined ways. calls will fail. So your code 
        should first attempt with the “current” value and, on failure, attempt the “
        previous” value as a fallback.
      * Like when we check both the current and immediately previous nonce values elsewhere
        in core. It’s also the same pattern used by [tools like AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/manage_update-secret-value.html)
        to allow for rolling back to a previous value (without losing it) while managing
        secret rotation. ([Google Cloud avails a similar pattern](https://docs.cloud.google.com/secret-manager/docs/view-secret-version)
        but with an unlimited number of version slots rather than just current/previous.)
 46. ![](https://secure.gravatar.com/avatar/24c0683677b463dbf6095d5ceaa13a4e50a1213891d187930e8f3601253c4f2c?
     s=32&d=mm&r=g)
 47.  [Rafael Meneses](https://profiles.wordpress.org/rtiodev/)  4:10 pm _on_ August
     27, 2026
 48. The unconditional encryption floor is right, and I hope it survives the comments
     intact. My feedback is about the ceiling.
 49. As written, the drop-in contract – “neither is ever handed a plaintext secret”–
     rejects the deployments [@rmccue](https://profiles.wordpress.org/rmccue/) and 
     [@jazzs3quence](https://profiles.wordpress.org/jazzs3quence/) describe above, 
     where the external store is the encryption boundary. The contract can’t express
     a KMS-backed store that receives plaintext over an authenticated channel, or a
     read-only store where writes go through host tooling. It’s also the only configuration
     where the “real per-caller enforcement” the proposal mentions can ever exist, 
     because the enforcement point has to live outside the PHPPHP The web scripting
     language in which WordPress is primarily architected. WordPress requires PHP 7.4
     or higher process.
 50. I’d change the rule: a provider can be stronger than the default, never weaker.
     Plaintext storage stays banned, which I read as the actual goal, and the setups
     Ryan and Chris describe stop being banned with it.
 51. Let me know what do you think about the following suggestions:
 52. 1. `WP_Secret::reveal()` should return string|WP_Error, not string. A future broker-
     held or use-only secret (the secret never enters PHP; the pluginPlugin A plugin
     is a piece of software containing a group of functions that can be added to a 
     WordPress website. They can extend functionality or add new features to your WordPress
     websites. WordPress plugins are written in the PHP programming language and integrate
     seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory
     [https://wordpress.org/plugins/](https://wordpress.org/plugins/) or can be cost-
     based plugin from a third-party. gets the capabilitycapability A **capability**
     is permission to perform one or more types of task. Checking if a user has a capability
     is performed by the `current_user_can` function. Each user of a WordPress site
     might have some permissions but not others, depending on their role. For example,
     users who have the Author role usually have permission to edit their own posts(
     the “edit_posts” capability), but not permission to edit other users’ posts (the“
     edit_others_posts” capability)., not the key) needs a `reveal()` that can refuse.
     With `reveal(): string`, that’s a compatibility break forever.
      2. Providers should
     declare capabilitiescapability A **capability** is permission to perform one or
     more types of task. Checking if a user has a capability is performed by the `current_user_can`
     function. Each user of a WordPress site might have some permissions but not others,
     depending on their role. For example, users who have the Author role usually have
     permission to edit their own posts (the “edit_posts” capability), but not permission
     to edit other users’ posts (the “edit_others_posts” capability). in the drop-in
     contract: writable or read-only, and whether encryption happens WordPress-side
     or store-side. A write to a read-only provider returns a defined error so adminadmin(
     and super admin) screens and WP-CLIWP-CLI WP-CLI is the Command Line Interface
     for WordPress, used to do administrative and development tasks in a programmatic
     way. The project page is [http://wp-cli.org/](http://wp-cli.org/) [https://make.wordpress.org/cli/](https://make.wordpress.org/cli/)
     can degrade gracefully.
 53. Thank you for writing this, I think this is a path forward to a better and more
     secure WP environments.
 54. ![](https://secure.gravatar.com/avatar/a3d65dbb7c58b7892c698cf0dc222f33f6cd231c13196f29a067dedd4d7e529c?
     s=32&d=mm&r=g)
 55.  [Bernhard Kau](https://profiles.wordpress.org/kau-boy/)  7:04 pm _on_ August 
     27, 2026
 56. I’d love to see that land in 7.2 and while I can’t give feedback in the discussion
     that happened so far, I have one question. You said that multisitemultisite Used
     to describe a WordPress installation with a network of multiple blogs, grouped
     by sites. This installation type has shared users tables, and creates separate
     database tables for each blog (wp_posts becomes wp_0_posts). See also **network**,**
     blog**, **site** is v0, which I am glad to see. But how is it implemented? Is 
     there also a `wp_get_site_secret()` function or would the function you implement
     check for networknetwork (versus site, blog) secret first and only then check 
     the single site?
 57. ![](https://secure.gravatar.com/avatar/c351da0b667baf3599ca3492cb29b3f872fca04ace42e252cd8bdc8f9eb059e9?
     s=32&d=mm&r=g)
 58.  [Prashant Baldha](https://profiles.wordpress.org/pmbaldha/)  10:24 pm _on_ August
     28, 2026
 59. This is especially valuable for plugins that need to store third-party APIAPI 
     An API or Application Programming Interface is a software intermediary that allows
     programs to interact with each other and share data in limited, clearly defined
     ways. keys 🔑 and other sensitive credentials 🔐. Today, many plugins have no 
     real alternative to storing these values in the options table. A first-class Secrets
     API with encryption, rotation, and a clear access model would give pluginPlugin
     A plugin is a piece of software containing a group of functions that can be added
     to a WordPress website. They can extend functionality or add new features to your
     WordPress websites. WordPress plugins are written in the PHP programming language
     and integrate seamlessly with WordPress. These can be free in the WordPress.org
     Plugin Directory [https://wordpress.org/plugins/](https://wordpress.org/plugins/)
     or can be cost-based plugin from a third-party. developers a much safer and more
     consistent foundation. 🚀
 60. ![](https://secure.gravatar.com/avatar/2ac5ccc9fb9799f9c6bf98c8ae287073dce99121c32f954c8112e9835601997a?
     s=32&d=mm&r=g)
 61.  [threadi](https://profiles.wordpress.org/threadi/)  9:03 am _on_ August 29, 2026
 62. Great idea – it’s about time something like this made its way into the coreCore
     Core is the set of software required to run WordPress. The Core Development Team
     builds WordPress.. Since it’s currently missing, I’ve been using my own implementation
     for years (similar to WooCommerce, SMTP, etc.), which is available as a Composer
     package: [https://github.com/threadi/crypt-for-wordpress](https://github.com/threadi/crypt-for-wordpress)
 63. Based on the feedback I’ve received, this also meets the requirements for ISO/
     IEC 27001 and 27002, which I would strongly recommend implementing here as well.
     I’ve been specifically asked about this by companies, as they otherwise wouldn’t
     have been able to use WordPress due to legal requirements. It would be great to
     be able to offer them a ready-made solution right within WordPress itself in the
     future.
 64. ![](https://secure.gravatar.com/avatar/92bfcd3a8c3a21a033a6484d32c25a40b113ec6891f674336081513d5c98ef76?
     s=32&d=mm&r=g)
 65.  [Kaspars](https://profiles.wordpress.org/kasparsd/)  2:27 pm _on_ August 29, 
     2026
 66. This is great, thanks for creating this proposal [@ericmann](https://profiles.wordpress.org/ericmann/)!
 67. Here is [another example of prior art](https://github.com/wpelevator/encrypted-secrets)
     that I built as a prototype showing multiple secret storage approaches (which 
     is smart to not discuss as part of this initial proposal 😅) — constants, environment
     variables and PHPPHP The web scripting language in which WordPress is primarily
     architected. WordPress requires PHP 7.4 or higher files.
 68. Some of the earlier discussions around automatic update signing mentioned that
     a lot of sites running WP don’t support the cryptographic primitives required 
     for this. It would be interesting to get some recent stats on this.
 69. ![](https://secure.gravatar.com/avatar/86bb3fa9fcfe57b8d313e527e9e02bde810951b25722b4717b0953a6c8db41b2?
     s=32&d=mm&r=g)
 70.  [Eric Mann](https://profiles.wordpress.org/ericmann/)  3:02 pm _on_ October 7,
     2026
 71. As discussed in last week’s dev chat on SlackSlack Slack is a Collaborative Group
     Chat Platform [https://slack.com/](https://slack.com/). The WordPress community
     has its own Slack Channel at [https://make.wordpress.org/chat/](https://make.wordpress.org/chat/),
     there’s [a PR up for this](https://github.com/WordPress/wordpress-develop/pull/13759)(
     that addresses the host provider concerns above) as well as [a Trac ticket](https://core.trac.wordpress.org/ticket/66187).
     Documentation for the APIAPI An API or Application Programming Interface is a 
     software intermediary that allows programs to interact with each other and share
     data in limited, clearly defined ways. along with some example implementations
     for things like AWS Secrets Manager and Hashicorp Vault live at [https://www.secretsapi.dev](https://www.secretsapi.dev).
 72. Further review and feedback is not just welcome but encouraged, especially as 
     we get towards a betaBeta A pre-release of software that is given out to a large
     group of users to trial under real conditions. Beta versions have gone through
     alpha testing in-house and are generally fairly close in look, feel and function
     to the final product; however, design changes often occur as part of the process.
     of 7.2!

### Leave a Reply 󠀁[Cancel reply](https://make.wordpress.org/core/2026/08/25/proposal-a-secrets-api-for-wordpress-7-2/?output_format=md#respond)󠁿

You must be [logged in](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F08%2F25%2Fproposal-a-secrets-api-for-wordpress-7-2%2F&locale=en_US)
to post a comment.

This site uses Akismet to reduce spam. [Learn how your comment data is processed.](https://akismet.com/privacy/)

# Post navigation

[← Performance Chat Summary: 25 August 2026](https://make.wordpress.org/core/2026/08/25/performance-chat-summary-25-august-2026/)

[WordPress 7.1.1 Release Schedule →](https://make.wordpress.org/core/2026/09/02/wordpress-7-1-1-release-schedule/)