App Settings & Configuration Boundaries
When this skill applies
Use this skill when deciding or implementing how a VTEX IO app should expose configurable settings.
- Defining
- Adding merchant-configurable app behavior
- Reviewing whether configuration belongs in app settings or custom data
- Reading and validating settings in app code
Do not use this skill for:
- runtime infrastructure settings in
- Master Data entity design
- policy declaration details
- route auth modeling
Decision rules
- Use app settings for stable configuration that merchants or operators should be able to manage explicitly.
- Use for app-level configuration managed through VTEX Admin, and use
store/contentSchemas.json
for Store Framework block configuration that varies by page or block instance.
- If the value is global for the app or account, it usually belongs in . If it varies per page, block, or theme composition, it usually belongs in .
- Do not use app settings as a substitute for high-volume operational data storage.
- Use JSON Schema explicitly with , , , , , and related constraints instead of a generic root only.
- Use
settingsSchema.access: "public"
only for non-sensitive values that are intentionally safe to expose to frontend code through .
- If is omitted, do not assume frontend GraphQL consumers can read the settings. Public frontend access must be an explicit choice.
- Use app settings for API keys, tokens, and secrets instead of hardcoding them in the codebase.
- Never expose secrets from app settings directly in HTTP responses, GraphQL responses, HTML, or browser-side props.
- Never expose secrets from app settings in logs either.
- For sensitive fields such as API keys or passwords, keep them as and consider marking them with . Some platform consumers, such as Apps GraphQL when using , may use this metadata to mask values in responses. Do not rely on this as the only security layer: secrets must still be treated as backend-only and never exposed in responses or logs.
- UI-specific hints such as may be supported by some renderers, but they are not part of the core JSON Schema guarantees. Do not assume the standard VTEX Admin App Settings UI will enforce them.
- Read backend settings through
ctx.clients.apps.getAppSettings(ctx.vtex.appId ?? process.env.VTEX_APP_ID)
and centralize normalization or validation in a helper instead of spreading ad hoc access patterns through handlers.
- When reading or saving this app's own settings at runtime, use the correct app identifier such as or and rely on the app token plus standard app-settings permissions. Do not declare extra License Manager policies in or add workspace-wide policies such as or undocumented policies such as just to "fix" a 403.
- Pixel apps that need configuration should also consume settings through
ctx.clients.apps.getAppSettings(...)
on the backend side of the pixel app. If a value must be available to injected JavaScript, expose only non-sensitive fields through and , keeping secrets strictly on the server side.
- Make code resilient to missing or incomplete settings by validating or applying defaults at the consumption boundary.
- Never assume settings are identical across accounts or workspaces. Each workspace may have different app configuration during development, rollout, or debugging.
Settings vs configuration builder:
- Use when the configuration is specific to this app and the merchant is expected to edit it in Apps > App Settings.
- Consider a separate app using the builder when the configuration contract needs to be shared across multiple apps, managed separately from the runtime app lifecycle, or injected directly into service context through .
- Prefer a configuration app when the main goal is structured service configuration delivered through VTEX IO runtime context, instead of settings fetched ad hoc by the app itself.
Hard constraints
Constraint: Configurable app behavior must have a schema
Merchant-configurable settings MUST be modeled through an explicit schema instead of ad hoc unvalidated objects.
Why this matters
Without a schema, configuration becomes ambiguous, harder to validate, and easier to break across environments.
Detection
If code depends on app-level configuration but no schema or validation contract exists, STOP and define it first.
Correct
json
{
"settingsSchema": {
"title": "My App Settings",
"type": "object",
"properties": {
"enableModeration": {
"title": "Enable moderation",
"type": "boolean",
"default": false,
"description": "If true, new content will require approval before going live."
},
"apiKey": {
"title": "External API key",
"type": "string",
"minLength": 1,
"description": "API key for the external moderation service.",
"format": "password"
},
"mode": {
"title": "Mode",
"type": "string",
"enum": ["sandbox", "production"],
"default": "sandbox"
}
},
"required": ["apiKey"]
}
}
Wrong
typescript
const settings = ctx.state.anything
Constraint: Sensitive settings must stay backend-only and must not be exposed to the frontend
Secrets stored in app settings such as API keys, tokens, or passwords MUST be treated as backend-only configuration.
Why this matters
App settings are a natural place for secrets, but exposing them in HTTP responses, GraphQL payloads, HTML, or frontend props turns configuration into a security leak.
Detection
If a route, resolver, or frontend-facing response returns raw settings or includes sensitive fields from settings, STOP and move the external call or secret usage fully to the backend boundary.
Correct
typescript
const { apiKey } = await ctx.clients.apps.getAppSettings(
ctx.vtex.appId ?? process.env.VTEX_APP_ID
)
const result = await externalClient.fetchData({ apiKey })
ctx.body = result
Wrong
typescript
const settings = await ctx.clients.apps.getAppSettings(
ctx.vtex.appId ?? process.env.VTEX_APP_ID
)
ctx.body = settings
Constraint: Public app settings access must never expose sensitive configuration
If
is set to
, the exposed settings MUST contain only values that are safe to ship to frontend code through
.
Why this matters
is a delivery choice, not a security control. Once settings are publicly exposed, storefront or frontend code can read them, so secrets and backend-only configuration must never be included there.
Detection
If a settings schema marks access as public and includes API keys, tokens, passwords, or any value intended only for backend integrations, STOP and keep those settings private.
Correct
json
{
"settingsSchema": {
"title": "Public Storefront Settings",
"type": "object",
"access": "public",
"properties": {
"bannerText": {
"title": "Banner text",
"type": "string"
}
}
}
}
Wrong
json
{
"settingsSchema": {
"title": "My App Settings",
"type": "object",
"access": "public",
"properties": {
"apiKey": {
"title": "External API key",
"type": "string",
"format": "password"
}
}
}
}
Constraint: Settings must not be used as operational data storage
App settings MUST represent configuration, not high-volume mutable records.
Why this matters
Settings are for configuration boundaries, not for transactional or large-scale operational data.
Detection
If a proposed setting stores records that behave like orders, reviews, logs, or queue items, STOP and move that concern to a more appropriate data mechanism.
Correct
json
{
"enableModeration": true
}
Wrong
Constraint: Code must validate or default settings at the consumption boundary
Settings-dependent code MUST tolerate missing or incomplete values safely.
Why this matters
Configuration can drift across workspaces and accounts. Code that assumes every setting is present becomes fragile.
Detection
If code reads settings and assumes required fields always exist with no validation or defaults, STOP and make the dependency explicit.
Correct
typescript
const rawSettings = await ctx.clients.apps.getAppSettings(
ctx.vtex.appId ?? process.env.VTEX_APP_ID
)
const settings = normalizeSettings(rawSettings)
const enabled = settings.enableModeration ?? false
Wrong
typescript
const enabled = settings.enableModeration.value
Preferred pattern
Use settings for stable, merchant-managed configuration, define them with explicit JSON Schema properties, and validate or normalize them where they are consumed.
For secrets, keep the read and the external call on the backend and return only the business result to the frontend.
Use frontend GraphQL access only for intentionally public settings, and keep backend-only settings behind
.
Common failure modes
- Using settings as operational storage.
- Using only without explicit and validation details.
- Reading settings without defaults or validation.
- Exposing raw settings or secrets to the frontend.
- Marking settings as when they contain backend-only or sensitive values.
- Logging settings or secrets in plain text.
- Hardcoding API keys or tokens instead of storing them in app settings.
- Adding workspace-level policies such as or invalid policies such as as a generic workaround for app settings permission errors, instead of validating the correct appId and standard app-settings permissions.
- Using when the requirement is really block-level Store Framework configuration.
- Creating schemas that are too broad or vague.
Review checklist
Reference
- Manifest - App configuration contract
- Creating an interface for your app settings - , , frontend queries, and backend settings consumption
- Configuring your app settings - Pixel app example for consuming app settings in VTEX IO