Skip to content
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2bac314
feat(core): Add encryption and secret-access strategies
michaelbromley Jul 27, 2026
35b4816
feat(core): Add `secret` flag to encrypt custom fields and config arg…
michaelbromley Jul 27, 2026
74896f2
feat(core): Gate API access to decrypted secret field and config arg …
michaelbromley Jul 27, 2026
e25415f
test(core): Add e2e coverage for secret custom fields and config args
michaelbromley Jul 27, 2026
e126543
docs: Add secret fields guide
michaelbromley Jul 27, 2026
36f7ae7
docs(core): Correct secret fields @since tags to 3.8.0
michaelbromley Jul 29, 2026
6b3ab7f
fix(core): Gate secret config args centrally for all operation types
michaelbromley Jul 29, 2026
23cf103
feat(core): Detect encryption key mismatch at bootstrap
michaelbromley Jul 29, 2026
66568d6
fix(core): Require encryption secret via config, not process.env
michaelbromley Jul 29, 2026
8b4ce03
fix(core): Restrict encryption key-check to SuperAdmin
michaelbromley Jul 29, 2026
e2626c8
fix(core): Redact secret config args by definition, not ciphertext
michaelbromley Jul 29, 2026
d407ded
fix(core): Derive encryption key with scrypt and warn on weak secret
michaelbromley Jul 29, 2026
48d9cba
docs: update timestamps in auto-generated documentation files
vendure-ci-automation-bot[bot] Jul 29, 2026
2fe1d9d
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] Jul 29, 2026
0a6c2b1
fix(core): Preserve duplicate operation secrets by position, not code
michaelbromley Jul 29, 2026
e34b3d0
fix(core): Pass the owning entity to SecretAccessStrategy for custom …
michaelbromley Jul 29, 2026
20d17fa
fix(core): Reject secret:true on unsupported custom field types
michaelbromley Jul 29, 2026
54dea2d
fix(core): Only pass ciphertext to EncryptionStrategy.decrypt()
michaelbromley Jul 29, 2026
23cb84b
test(core): Make secret custom field entity assertion self-contained
michaelbromley Jul 30, 2026
66a2b69
fix(core): Preserve secret custom fields on all write paths
michaelbromley Jul 30, 2026
4d6c1fa
fix(core): Skip secret machinery for preview and test operations
michaelbromley Jul 30, 2026
6ce4cb9
fix(core): Correctness, robustness and cleanup from secret-field review
michaelbromley Jul 30, 2026
246f330
test(core): Expand secret field coverage from review
michaelbromley Jul 30, 2026
5f5bf89
docs: update SecretAccessStrategy documentation with line number fix …
vendure-ci-automation-bot[bot] Jul 30, 2026
a1263c9
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] Jul 30, 2026
c478ae3
refactor(core): Discover secret custom-field inputs from the schema
michaelbromley Jul 30, 2026
d530ef9
refactor(core): Single source of truth for configurable-operation reg…
michaelbromley Jul 30, 2026
fdec628
refactor(core): Share the CustomFieldsInput type-name suffix
michaelbromley Jul 30, 2026
8d3690c
docs: update source line numbers in custom field documentation files
vendure-ci-automation-bot[bot] Jul 30, 2026
be0841b
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] Jul 30, 2026
1cc06cb
fix(core): Namespace the encryption key-check settings store key unde…
michaelbromley Jul 30, 2026
409c602
feat(dashboard): Default secret fields to a masked password input
michaelbromley Jul 30, 2026
ce3d70e
fix(dashboard): Query secret flag for custom fields and config args
michaelbromley Jul 30, 2026
3c5a987
refactor(dashboard): Share secret input-component resolution
michaelbromley Jul 30, 2026
fb25eeb
fix(dashboard): Do not surface the secret redaction placeholder
michaelbromley Jul 31, 2026
484ca4f
docs: Generate docs for source changes
vendure-ci-automation-bot[bot] Jul 31, 2026
e8ac5b2
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] Jul 31, 2026
331e50f
docs: Tighten secret fields guide from review feedback
michaelbromley Jul 31, 2026
4819ba3
chore(dashboard): Extract i18n message for hidden secret field label
michaelbromley Jul 31, 2026
c94c3a4
Merge remote-tracking branch 'origin/minor' into feat/secret-fields
michaelbromley Jul 31, 2026
cc5e87f
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] Jul 31, 2026
1499461
docs: Generate docs for source changes
vendure-ci-automation-bot[bot] Jul 31, 2026
0afbc8f
docs: Regenerate manifest.json
vendure-ci-automation-bot[bot] Jul 31, 2026
b746f78
fix(dashboard): Set secret flag when mapping struct fields to field c…
michaelbromley Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions docs/docs/guides/developer-guide/secret-fields/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
title: "Secret fields"
metaTitle: "Encrypting secret custom fields and config args in Vendure"
metaDescription: "Store sensitive values such as API keys encrypted at rest and control who can read them via the API using the `secret` flag."
---

Some data stored by Vendure is sensitive — payment-gateway API keys, webhook signing secrets and
similar. The `secret` flag lets you mark a custom field or a config arg so that its value is
**encrypted in the database** and is **only returned in decrypted form via the API to users who are
permitted to see it**.

## Enabling encryption

Encryption requires a stable secret key. Configure an [`EncryptionStrategy`](/reference/typescript-api/configuration/encryption-strategy/)
in your VendureConfig and pass it the secret. The strategy does not read the environment itself —
Comment thread
michaelbromley marked this conversation as resolved.
Outdated
following Vendure's convention, you read the environment variable in your config and pass the value
in explicitly:

```ts
import { DefaultEncryptionStrategy, VendureConfig } from '@vendure/core';

export const config: VendureConfig = {
// ...
systemOptions: {
encryptionStrategy: new DefaultEncryptionStrategy({
secret: process.env.VENDURE_ENCRYPTION_KEY,
}),
},
};
```

```sh
VENDURE_ENCRYPTION_KEY=your-long-random-secret
```

:::warning
The key must remain the same across restarts and deployments. If it changes, existing encrypted
data can no longer be decrypted. Do not commit it to source control, and back it up securely.
:::

If a `secret` field or arg is configured but no encryption key is available, the server refuses to
start, with a message showing exactly which configuration to add.

## Marking a custom field as secret

Set `secret: true` on a `string` or `text` custom field:

```ts
customFields: {
Customer: [
{ name: 'externalApiKey', type: 'string', secret: true },
],
},
```

The value is encrypted when saved and decrypted when loaded. `secret` cannot be combined with
`unique` or an explicit `length`, and is not supported on `relation`, `struct` or list fields.

## Marking a config arg as secret

Set `secret: true` on a `string` config arg — for example the API key of a payment method handler:

```ts
args: {
apiKey: { type: 'string', secret: true },
},
```

This works for any configurable operation — payment method handlers and eligibility checkers,
shipping calculators and checkers, promotion conditions and actions, collection filters, and any
custom `ConfigurableOperationDef` you define yourself. The encryption, redaction and
preservation-on-edit behaviour is handled centrally, so a custom operation does not need to
implement any of it. `secret` is only supported on non-list `string` args.
Comment thread
michaelbromley marked this conversation as resolved.
Outdated

## Who can read the value

Reading the decrypted value requires the `Permission.ReadSecret` permission, which by default is
held only by the SuperAdmin. A user without it receives a redaction placeholder instead of the real
value. This behaviour is defined by the [`SecretAccessStrategy`](/reference/typescript-api/configuration/secret-access-strategy/),
which you can replace to apply your own logic (for example, revealing a secret only to certain
roles). Its input is a discriminated union on `kind`: for a `customField` it includes the entity
instance, its type and the field name; for a `configArg` it includes the arg name and, where the
GraphQL query path allows it to be determined, the owning entity type and field.

Writing a secret is **not** gated by `ReadSecret` — any user who is allowed to create or update the
entity may set its secret fields.

## Editing without re-entering the secret

Because the API never returns the real value to an unauthorised user, the placeholder is what their
edit form has to submit back. When the placeholder is submitted on an update, the stored value is
left unchanged. To change the value, submit a new one; to clear it, submit an empty value. On a
_create_, the placeholder is rejected, since there is no existing value to preserve.

## Moving data between environments

Secret values are stored encrypted, so a database dump contains ciphertext, not the original values.
The key that encrypted them lives only in your environment configuration, never in the database. This
means a dump can only be read in an environment that uses the **same** encryption key.

The first time an encryption key is used against a database, Vendure stores a small "key check" value
(a known string encrypted with that key). On every startup it verifies the configured key against
this check, so if you restore a dump into an environment with a different key — or change the key of
an existing database — the server **fails to start** with a clear error, rather than failing later
with scattered decryption errors when a secret is first read.

As a result:

- **To move data you want to keep usable** (for example promoting a staging database to production),
use the **same `VENDURE_ENCRYPTION_KEY`** in both environments.
- **To copy data into an environment that uses a different key** (for example pulling production data
onto a developer machine without sharing the production key), first clear the secret values in the
dump: set the encrypted custom-field columns to `NULL` and remove the secret config-arg values.
Otherwise the restored data cannot be decrypted with the local key.

Key rotation (re-encrypting existing data under a new key) is not currently supported. If you have
intentionally changed the key and there is no encrypted data to preserve, delete the
`vendure.encryption.keyCheck` entry from the settings store to re-bind the database to the new key.

## Existing data and limitations

- Enabling `secret` on a field that already holds plain-text values does not break anything, but
those values are only encrypted the next time each record is saved.
- Decrypted values are available to all internal code (events, jobs, plugins). The redaction applies
only to the GraphQL API, so take care not to re-leak secrets in logs or exports.
- `secret` fields cannot be filtered or sorted, because the stored value is ciphertext.
- Key rotation is not supported in this version; changing the key makes existing data unreadable.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "AssetOptions"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="752" packageName="@vendure/core" />
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="754" packageName="@vendure/core" />

The AssetOptions define how assets (images and other files) are named and stored, and how preview images are generated.

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/typescript-api/auth/auth-options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "AuthOptions"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="366" packageName="@vendure/core" />
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="368" packageName="@vendure/core" />

The AuthOptions define how authentication and authorization is managed.

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/typescript-api/auth/cookie-options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "CookieOptions"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="261" packageName="@vendure/core" />
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="263" packageName="@vendure/core" />

Options for the handling of the cookies used to track sessions (only applicable if
`authOptions.tokenMethod` is set to `'cookie'`). These options are passed directly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "SuperadminCredentials"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="928" packageName="@vendure/core" />
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="930" packageName="@vendure/core" />

These credentials will be used to create the Superadmin user & administrator
when Vendure first bootstraps.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "CurrencyCode"
generated: true
---
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="1136" packageName="@vendure/common" />
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="1138" packageName="@vendure/common" />

ISO 4217 currency code

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/typescript-api/common/job-state.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "JobState"
generated: true
---
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2394" packageName="@vendure/common" />
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2400" packageName="@vendure/common" />

The state of a Job in the JobQueue

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "LanguageCode"
generated: true
---
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2412" packageName="@vendure/common" />
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2418" packageName="@vendure/common" />

Languages in the form of a ISO 639-1 language code with optional
region or script modifier (e.g. de_AT). The selection available is based
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/reference/typescript-api/common/permission.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Permission"
generated: true
---
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="4753" packageName="@vendure/common" />
<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="4761" packageName="@vendure/common" />

Permissions for administrators and customers. Used to control access to
GraphQL resolvers via the [Allow](/reference/typescript-api/request/allow-decorator#allow) decorator.
Expand Down Expand Up @@ -97,6 +97,7 @@ enum Permission {
ReadPaymentMethod = 'ReadPaymentMethod'
ReadProduct = 'ReadProduct'
ReadPromotion = 'ReadPromotion'
ReadSecret = 'ReadSecret'
ReadSeller = 'ReadSeller'
ReadSettings = 'ReadSettings'
ReadShippingMethod = 'ReadShippingMethod'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "ConfigArgs"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/common/configurable-operation.ts" sourceLine="140" packageName="@vendure/core" />
<GenerationInfo sourceFile="packages/core/src/common/configurable-operation.ts" sourceLine="152" packageName="@vendure/core" />

A object which defines the configurable arguments which may be passed to
functions in those classes which implement the [ConfigurableOperationDef](/reference/typescript-api/configurable-operation-def/#configurableoperationdef) interface.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "ConfigurableOperationDefOptions"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/common/configurable-operation.ts" sourceLine="230" packageName="@vendure/core" />
<GenerationInfo sourceFile="packages/core/src/common/configurable-operation.ts" sourceLine="242" packageName="@vendure/core" />

Common configuration options used when creating a new instance of a
[ConfigurableOperationDef](/reference/typescript-api/configurable-operation-def/#configurableoperationdef) (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "ConfigurableOperationDef"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/common/configurable-operation.ts" sourceLine="335" packageName="@vendure/core" />
<GenerationInfo sourceFile="packages/core/src/common/configurable-operation.ts" sourceLine="347" packageName="@vendure/core" />

A ConfigurableOperationDef is a special type of object used extensively by Vendure to define
code blocks which have arguments which are configurable at run-time by the administrator.
Expand Down Expand Up @@ -82,6 +82,7 @@ class ConfigurableOperationDef<T extends ConfigArgs = ConfigArgs> {
code: string
args: T
description: LocalizedStringArray
protected encryptionStrategy?: EncryptionStrategy;
constructor(options: ConfigurableOperationDefOptions<T>)
init(injector: Injector) => ;
destroy() => ;
Expand All @@ -107,6 +108,11 @@ class ConfigurableOperationDef<T extends ConfigArgs = ConfigArgs> {
<MemberInfo kind="property" type={`<a href='/reference/typescript-api/configurable-operation-def/localized-string-array#localizedstringarray'>LocalizedStringArray</a>`} />


### encryptionStrategy

<MemberInfo kind="property" type={`<a href='/reference/typescript-api/configuration/encryption-strategy#encryptionstrategy'>EncryptionStrategy</a>`} />


### init

<MemberInfo kind="method" type={`(injector: <a href='/reference/typescript-api/common/injector#injector'>Injector</a>) => `} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "LocalizedStringArray"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/common/configurable-operation.ts" sourceLine="43" packageName="@vendure/core" />
<GenerationInfo sourceFile="packages/core/src/common/configurable-operation.ts" sourceLine="44" packageName="@vendure/core" />

An array of string values in a given [LanguageCode](/reference/typescript-api/common/language-code#languagecode), used to define human-readable string values.
The `ui` property can be used in conjunction with the Vendure Admin UI to specify a custom form input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "ApiOptions"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="82" packageName="@vendure/core" />
<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="84" packageName="@vendure/core" />

The ApiOptions define how the Vendure GraphQL APIs are exposed, as well as allowing the API layer
to be extended with middleware.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "DefaultConfig"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/config/default-config.ts" sourceLine="75" packageName="@vendure/core" />
<GenerationInfo sourceFile="packages/core/src/config/default-config.ts" sourceLine="77" packageName="@vendure/core" />

The default configuration settings which are used if not explicitly overridden in the bootstrap() call.

Loading
Loading