| name | apex-secrets-and-protected-cmdt |
| description | Storing API keys, signing secrets, and third-party tokens that Apex must consume — Protected Custom Metadata in a managed package, Protected Custom Settings, Encrypted Custom Fields, Apex Crypto, and what to NEVER do (hardcode, unprotected CMDT, System.debug). NOT for callout authentication — see apex-named-credentials-patterns; NOT for record-level data encryption — see Shield Platform Encryption. |
| category | apex |
| salesforce-version | Spring '25+ |
| well-architected-pillars | ["Security"] |
| triggers | ["where do I store an API key that apex needs to call a third party","protected custom metadata type secret salesforce admin cannot read","namespaceaccessible getter for hmac signing key apex","should I put a secret in custom setting or custom metadata","rotate webhook signing secret without code deploy","subscriber org admin can see my protected cmdt values"] |
| inputs | ["Type of secret (callout auth, signing key, symmetric crypto key, lookup token)","Whether the code lives in a managed package or unmanaged DX project","Rotation cadence and operational ownership","Subscriber-vs-source-org threat model"] |
| outputs | ["Storage decision (Named/External Credential, Protected CMDT, Protected Custom Setting, Encrypted Field, off-platform vault)","Apex retrieval pattern with @NamespaceAccessible where required","Documented rotation procedure","Source-control exclusion rules for `customMetadata/*-md.xml`"] |
| dependencies | [] |
| version | 1.0.0 |
| author | Pranav Nagrecha |
| updated | 2026-04-28T00:00:00.000Z |
| tags | ["apex","secrets","cmdt","encryption","protected","named-credentials","security"] |
Apex Secrets and Protected Custom Metadata
Activate when Apex needs to consume a secret value — API key, HMAC signing key, third-party token, per-tenant credential — and the engineer is reaching for String API_KEY = '...'; or a plain Custom Setting. The platform offers a small set of correct mechanisms; pick the one that matches the secret's purpose and the deployment shape (managed package vs unmanaged DX), then document a rotation procedure.
Before Starting
- Identify the secret's purpose: callout authentication, signature verification, symmetric encryption, or lookup token. The right storage differs.
- Confirm whether the consuming Apex ships in a managed package (with a namespace) or an unmanaged DX project. "Protected" Custom Metadata and "Protected" Custom Settings are only protected against subscribers — they offer zero protection in the source org.
- Decide who owns rotation and on what cadence. A secret with no rotation procedure is a future incident.
Core Concepts
The canonical decision tree
| Secret purpose | Correct storage |
|---|
| Callout authentication (Authorization header, OAuth, mTLS) | Named Credential / External Credential — always. Apex never sees the credential. |
| HMAC signing / webhook verification key | Protected Custom Metadata in a managed package, retrieved via @NamespaceAccessible Apex |
| Symmetric encryption key (AES) | Crypto.generateAesKey(256) at install time, store ciphertext via Protected CMDT or off-platform vault — never hardcode |
| Per-tenant lookup token / config secret | Protected Custom Setting (Hierarchy) in a managed package |
| Field-level data encryption at rest | Shield Platform Encryption (Encrypted Custom Fields) |
| Anything that needs admin-invisibility in the source org | Off-platform vault (HashiCorp, AWS KMS) — Salesforce cannot hide a value from a System Admin in the org that owns the metadata |
Why "Protected" only protects against subscribers
Protected Custom Metadata Types and Protected Custom Settings expose values only to Apex code in the same managed-package namespace. In the subscriber org, the admin cannot read them via UI, SOQL, Workbench, the Tooling API, or anonymous Apex. In the packaging org (or any unmanaged project) the System Admin retains full visibility — protection is a , not a cryptographic one.