vtex-io-service-configuration-apps

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Service Configuration Apps

服务配置应用

When this skill applies

适用场景

Use this skill when a VTEX IO service should receive structured configuration from another app through the
configuration
builder instead of relying only on local app settings.
  • Creating a configuration app with the
    configuration
    builder
  • Exposing configuration entrypoints from a service app
  • Sharing one configuration contract across multiple services or apps
  • Separating configuration lifecycle from runtime app lifecycle
  • Reading injected configuration through
    ctx.vtex.settings
Do not use this skill for:
  • simple app-local configuration managed only through
    settingsSchema
  • Store Framework block settings through
    contentSchemas.json
  • generic service runtime wiring unrelated to configuration
  • policy design beyond the configuration-specific permissions required
当VTEX IO服务需要通过
configuration
构建器从其他应用接收结构化配置,而非仅依赖本地应用设置时,可使用本规范:
  • 使用
    configuration
    构建器创建配置应用
  • 从服务应用暴露配置入口
  • 跨多个服务或应用共享同一配置契约
  • 将配置生命周期与运行时应用生命周期分离
  • 通过
    ctx.vtex.settings
    读取注入的配置
本规范不适用于以下场景:
  • 仅通过
    settingsSchema
    管理的简单应用本地配置
  • 通过
    contentSchemas.json
    配置的Store Framework区块设置
  • 与配置无关的通用服务运行时连接逻辑
  • 超出配置专属权限范围的策略设计

Decision rules

决策规则

  • Treat the service app and the configuration app as separate responsibilities.
  • The service app owns runtime (
    node
    ,
    graphql
    , etc.), declares the
    configuration
    builder in
    manifest.json
    , defines
    configuration/schema.json
    , and reads injected values through
    ctx.vtex.settings
    .
  • The configuration app does not own the service runtime. It should not declare
    node
    or
    graphql
    builders and usually has only the
    configuration
    builder.
  • The configuration app points to the target service in the
    configuration
    field and provides concrete values in
    <service-app>/configuration.json
    .
  • Use a configuration app when the configuration contract should live independently from the app that consumes it.
  • Prefer a configuration app when multiple apps or services need to share the same configuration model.
  • In service apps, expose configuration entrypoints explicitly through
    settingsType: "workspace"
    in
    node/service.json
    routes or events, or through
    @settings
    in GraphQL when the service should receive configuration from a configuration app.
  • In configuration apps, the folder name under
    configuration/
    and the key in the
    configuration
    field should match the target service app ID, for example
    shipping-service
    in
    vendor.shipping-service
    .
  • The shape of
    configuration.json
    must respect the JSON Schema declared by the service app.
  • Read received configuration from
    ctx.vtex.settings
    inside the service runtime instead of making your own HTTP call just to fetch those values.
  • Handlers and resolvers should cast or validate
    ctx.vtex.settings
    to match the configuration schema and apply defaults consistent with that schema.
  • Treat configuration apps as a way to inject structured runtime configuration through VTEX IO context, not as a replacement for arbitrary operational data storage.
  • Use
    settingsSchema
    when configuration is local to one app and should be edited directly in Apps > App Settings. Use configuration apps when the contract should be shared, versioned, or decoupled from the consuming app lifecycle.
  • If a service configured through a configuration app fails to resolve workspace app configuration due to permissions, explicitly evaluate whether the manifest needs the
    read-workspace-apps
    policy for that scenario. Do not add this policy by default to unrelated services.
  • For service configuration contracts, prefer closed schemas with
    additionalProperties: false
    and use
    definitions
    plus
    $ref
    when the structure becomes more complex.
  • 服务应用与配置应用需划分独立的职责边界。
  • 服务应用负责运行时(
    node
    graphql
    等),在
    manifest.json
    中声明
    configuration
    构建器,定义
    configuration/schema.json
    ,并通过
    ctx.vtex.settings
    读取注入值。
  • 配置应用不负责服务运行时,不应声明
    node
    graphql
    构建器,通常仅包含
    configuration
    构建器。
  • 配置应用在
    configuration
    字段中指向目标服务,并在
    <service-app>/configuration.json
    中提供具体配置值。
  • 当配置契约需要独立于消费它的应用存在时,使用配置应用。
  • 当多个应用或服务需要共享同一配置模型时,优先使用配置应用。
  • 在服务应用中,若需要从配置应用接收配置,需通过
    node/service.json
    路由或事件中的
    settingsType: "workspace"
    显式暴露配置入口,或在GraphQL中通过
    @settings
    声明。
  • 在配置应用中,
    configuration/
    下的文件夹名称和
    configuration
    字段中的键需与目标服务应用ID匹配,例如
    vendor.shipping-service
    中的
    shipping-service
  • configuration.json
    的结构必须符合服务应用声明的JSON Schema要求。
  • 直接在服务运行时中通过
    ctx.vtex.settings
    读取接收的配置,不要额外发起HTTP调用获取这些值。
  • 处理函数和解析器需对
    ctx.vtex.settings
    进行类型转换或验证,使其匹配配置Schema,并应用与Schema一致的默认值。
  • 配置应用的作用是通过VTEX IO上下文注入结构化运行时配置,不能替代任意业务运营数据存储。
  • 当配置仅属于单个应用,且需要直接在应用>应用设置中编辑时,使用
    settingsSchema
    。当配置契约需要共享、版本管理,或与消费应用的生命周期解耦时,使用配置应用。
  • 如果通过配置应用配置的服务因权限问题无法解析工作区应用配置,需显式评估该场景下manifest是否需要
    read-workspace-apps
    策略,不要为不相关的服务默认添加该策略。
  • 对于服务配置契约,优先使用带
    additionalProperties: false
    的闭合Schema,当结构较复杂时使用
    definitions
    $ref
    的方式定义。

Hard constraints

硬性约束

Constraint: Service apps must explicitly opt in to receiving configuration

约束:服务应用必须显式声明接收配置

A service app MUST declare where configuration can be injected, using
settingsType: "workspace"
in
node/service.json
routes or events, or the
@settings
directive in GraphQL.
Why this matters
Configuration apps do not magically apply to all service entrypoints. The service must explicitly mark which routes, events, or queries resolve runtime configuration.
Detection
If a service is expected to receive configuration but its routes, events, or GraphQL queries do not declare
settingsType
or
@settings
, STOP and expose the configuration boundary first.
Correct
json
{
  "routes": {
    "status": {
      "path": "/_v/status/:code",
      "public": true,
      "settingsType": "workspace"
    }
  }
}
Wrong
json
{
  "routes": {
    "status": {
      "path": "/_v/status/:code",
      "public": true
    }
  }
}
服务应用必须声明配置的注入位置,可通过
node/service.json
路由或事件中的
settingsType: "workspace"
声明,或在GraphQL中使用
@settings
指令声明。
重要性说明 配置应用不会自动应用到所有服务入口,服务必须显式标记哪些路由、事件或查询需要解析运行时配置。
检测方式 如果预期服务会接收配置,但它的路由、事件或GraphQL查询未声明
settingsType
@settings
,请停止操作,先暴露配置边界。
正确示例
json
{
  "routes": {
    "status": {
      "path": "/_v/status/:code",
      "public": true,
      "settingsType": "workspace"
    }
  }
}
错误示例
json
{
  "routes": {
    "status": {
      "path": "/_v/status/:code",
      "public": true
    }
  }
}

Constraint: Configuration shape must be defined with explicit schema files

约束:配置结构必须通过显式Schema文件定义

Configuration apps and the services they configure MUST use explicit schema files instead of implicit or undocumented payloads.
Why this matters
Without
configuration/schema.json
and matching
configuration.json
contracts, shared configuration becomes ambiguous and error-prone across apps.
Detection
If a configuration app is introduced without a clear schema file or the service accepts loosely defined configuration payloads, STOP and define the schema first.
Correct
json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/ServiceConfiguration",
  "definitions": {
    "ServiceConfiguration": {
      "type": "object",
      "properties": {
        "bank": {
          "type": "object",
          "properties": {
            "account": { "type": "string" },
            "workspace": { "type": "string", "default": "master" },
            "version": { "type": "string" },
            "kycVersion": { "type": "string" },
            "payoutVersion": { "type": "string" },
            "host": { "type": "string" }
          },
          "required": ["account", "version", "kycVersion", "payoutVersion", "host"],
          "additionalProperties": false
        }
      },
      "required": ["bank"],
      "additionalProperties": false
    }
  }
}
Wrong
json
{
  "anything": true
}
配置应用和它们配置的服务必须使用显式Schema文件,不能使用隐式或未文档化的载荷。
重要性说明 如果没有
configuration/schema.json
和匹配的
configuration.json
契约,跨应用的共享配置会变得模糊且容易出错。
检测方式 如果引入的配置应用没有清晰的Schema文件,或者服务接收的配置载荷定义松散,请停止操作,先定义Schema。
正确示例
json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/ServiceConfiguration",
  "definitions": {
    "ServiceConfiguration": {
      "type": "object",
      "properties": {
        "bank": {
          "type": "object",
          "properties": {
            "account": { "type": "string" },
            "workspace": { "type": "string", "default": "master" },
            "version": { "type": "string" },
            "kycVersion": { "type": "string" },
            "payoutVersion": { "type": "string" },
            "host": { "type": "string" }
          },
          "required": ["account", "version", "kycVersion", "payoutVersion", "host"],
          "additionalProperties": false
        }
      },
      "required": ["bank"],
      "additionalProperties": false
    }
  }
}
错误示例
json
{
  "anything": true
}

Constraint: Consuming apps must read injected configuration from runtime context, not by inventing extra fetches

约束:消费应用必须从运行时上下文读取注入的配置,不要额外发起拉取请求

When a service is configured through a configuration app, it MUST consume the injected values from
ctx.vtex.settings
instead of creating its own ad hoc HTTP call just to retrieve the same configuration.
Why this matters
The purpose of configuration apps is to let VTEX IO inject the structured configuration directly into service context. Adding a custom fetch layer on top creates unnecessary complexity and loses the main runtime advantage of the builder.
Detection
If a service already exposes
settingsType
or
@settings
but still performs its own backend fetch to retrieve the same configuration, STOP and move the read to
ctx.vtex.settings
.
Correct
typescript
export async function handleStatus(ctx: Context) {
  const settings = ctx.vtex.settings
  const code = ctx.vtex.route.params.code

  const status = resolveStatus(code, settings)
  ctx.body = { status }
}
Wrong
typescript
export async function handleStatus(ctx: Context) {
  const settings = await ctx.clients.partnerApi.getSettings()
  ctx.body = settings
}
当服务通过配置应用配置时,必须从
ctx.vtex.settings
中消费注入的值,不要自行发起临时HTTP调用获取相同的配置。
重要性说明 配置应用的设计目的是让VTEX IO直接将结构化配置注入服务上下文,在之上添加自定义拉取层会造成不必要的复杂度,也丧失了该构建器的核心运行时优势。
检测方式 如果服务已经暴露了
settingsType
@settings
,但仍然自行发起后端拉取请求获取相同配置,请停止操作,改为从
ctx.vtex.settings
读取。
正确示例
typescript
export async function handleStatus(ctx: Context) {
  const settings = ctx.vtex.settings
  const code = ctx.vtex.route.params.code

  const status = resolveStatus(code, settings)
  ctx.body = { status }
}
错误示例
typescript
export async function handleStatus(ctx: Context) {
  const settings = await ctx.clients.partnerApi.getSettings()
  ctx.body = settings
}

Preferred pattern

推荐模式

Model the service and the configuration app as separate contracts:
  1. The service app exposes where configuration can be resolved.
  2. The service app defines accepted structure in
    configuration/schema.json
    .
  3. The configuration app declares the service as a builder and supplies values in
    configuration.json
    .
  4. The service reads the injected configuration through
    ctx.vtex.settings
    .
Example: service app
vendor.shipping-service
manifest.json
:
json
{
  "vendor": "vendor",
  "name": "shipping-service",
  "version": "1.0.0",
  "builders": {
    "node": "7.x",
    "configuration": "1.x"
  }
}
configuration/schema.json
:
json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/ShippingConfiguration",
  "definitions": {
    "ShippingConfiguration": {
      "type": "object",
      "properties": {
        "carrierApi": {
          "type": "object",
          "properties": {
            "baseUrl": { "type": "string" },
            "apiKey": { "type": "string", "format": "password" },
            "timeoutMs": { "type": "integer", "default": 3000 }
          },
          "required": ["baseUrl", "apiKey"],
          "additionalProperties": false
        }
      },
      "required": ["carrierApi"],
      "additionalProperties": false
    }
  }
}
Example: configuration app
vendor.shipping-config
manifest.json
:
json
{
  "vendor": "vendor",
  "name": "shipping-config",
  "version": "1.0.0",
  "builders": {
    "configuration": "1.x"
  },
  "configuration": {
    "shipping-service": "1.x"
  }
}
configuration/shipping-service/configuration.json
:
json
{
  "carrierApi": {
    "baseUrl": "https://api.carrier.com",
    "apiKey": "secret-api-key-here",
    "timeoutMs": 5000
  }
}
Example: Node service consuming injected configuration
typescript
export async function createShipment(ctx: Context, next: () => Promise<void>) {
  const settings = ctx.vtex.settings as {
    carrierApi: {
      baseUrl: string
      apiKey: string
      timeoutMs?: number
    }
  }

  const timeoutMs = settings.carrierApi.timeoutMs ?? 3000

  const response = await ctx.clients.carrier.createShipment({
    baseUrl: settings.carrierApi.baseUrl,
    apiKey: settings.carrierApi.apiKey,
    timeoutMs,
    payload: ctx.state.shipmentPayload,
  })

  ctx.body = response
  await next()
}
Example: GraphQL query using
@settings
graphql
type ShippingStatus {
  orderId: ID!
  status: String!
}

type Query {
  shippingStatus(orderId: ID!): ShippingStatus
    @settings(type: "workspace")
}
typescript
export const resolvers = {
  Query: {
    shippingStatus: async (_: unknown, args: { orderId: string }, ctx: Context) => {
      const settings = ctx.vtex.settings as {
        carrierApi: { baseUrl: string; apiKey: string }
      }

      return ctx.clients.carrier.getStatus({
        baseUrl: settings.carrierApi.baseUrl,
        apiKey: settings.carrierApi.apiKey,
        orderId: args.orderId,
      })
    },
  },
}
Minimum working checklist for service configuration apps:
  • The service app declares the
    configuration
    builder in
    manifest.json
    .
  • The service app defines a valid
    configuration/schema.json
    .
  • The configuration app provides
    <service-app>/configuration.json
    with values compatible with the schema.
  • Service routes or events that need configuration declare
    settingsType: "workspace"
    .
  • When the flow depends on workspace app resolution, the service manifest evaluates whether
    read-workspace-apps
    is required.
Use this approach when configuration should be shared, versioned, and injected by VTEX IO runtime rather than fetched ad hoc by service code.
将服务和配置应用建模为独立的契约:
  1. 服务应用暴露配置的可解析位置。
  2. 服务应用在
    configuration/schema.json
    中定义可接收的配置结构。
  3. 配置应用将服务声明为构建器,并在
    configuration.json
    中提供配置值。
  4. 服务通过
    ctx.vtex.settings
    读取注入的配置。
示例:服务应用
vendor.shipping-service
manifest.json
json
{
  "vendor": "vendor",
  "name": "shipping-service",
  "version": "1.0.0",
  "builders": {
    "node": "7.x",
    "configuration": "1.x"
  }
}
configuration/schema.json
json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/ShippingConfiguration",
  "definitions": {
    "ShippingConfiguration": {
      "type": "object",
      "properties": {
        "carrierApi": {
          "type": "object",
          "properties": {
            "baseUrl": { "type": "string" },
            "apiKey": { "type": "string", "format": "password" },
            "timeoutMs": { "type": "integer", "default": 3000 }
          },
          "required": ["baseUrl", "apiKey"],
          "additionalProperties": false
        }
      },
      "required": ["carrierApi"],
      "additionalProperties": false
    }
  }
}
示例:配置应用
vendor.shipping-config
manifest.json
json
{
  "vendor": "vendor",
  "name": "shipping-config",
  "version": "1.0.0",
  "builders": {
    "configuration": "1.x"
  },
  "configuration": {
    "shipping-service": "1.x"
  }
}
configuration/shipping-service/configuration.json
json
{
  "carrierApi": {
    "baseUrl": "https://api.carrier.com",
    "apiKey": "secret-api-key-here",
    "timeoutMs": 5000
  }
}
示例:Node服务消费注入的配置
typescript
export async function createShipment(ctx: Context, next: () => Promise<void>) {
  const settings = ctx.vtex.settings as {
    carrierApi: {
      baseUrl: string
      apiKey: string
      timeoutMs?: number
    }
  }

  const timeoutMs = settings.carrierApi.timeoutMs ?? 3000

  const response = await ctx.clients.carrier.createShipment({
    baseUrl: settings.carrierApi.baseUrl,
    apiKey: settings.carrierApi.apiKey,
    timeoutMs,
    payload: ctx.state.shipmentPayload,
  })

  ctx.body = response
  await next()
}
示例:使用
@settings
的GraphQL查询
graphql
type ShippingStatus {
  orderId: ID!
  status: String!
}

type Query {
  shippingStatus(orderId: ID!): ShippingStatus
    @settings(type: "workspace")
}
typescript
export const resolvers = {
  Query: {
    shippingStatus: async (_: unknown, args: { orderId: string }, ctx: Context) => {
      const settings = ctx.vtex.settings as {
        carrierApi: { baseUrl: string; apiKey: string }
      }

      return ctx.clients.carrier.getStatus({
        baseUrl: settings.carrierApi.baseUrl,
        apiKey: settings.carrierApi.apiKey,
        orderId: args.orderId,
      })
    },
  },
}
服务配置应用最小可用检查清单:
  • 服务应用在
    manifest.json
    中声明了
    configuration
    构建器。
  • 服务应用定义了合法的
    configuration/schema.json
  • 配置应用提供了
    <service-app>/configuration.json
    ,其值与Schema兼容。
  • 需要配置的服务路由或事件声明了
    settingsType: "workspace"
  • 当流程依赖工作区应用解析时,服务manifest已评估是否需要
    read-workspace-apps
    权限。
当配置需要被共享、版本管理,且由VTEX IO运行时注入而非服务代码临时拉取时,请使用本方案。

Common failure modes

常见故障模式

  • Using app settings when the real need is a shared configuration contract across apps.
  • Creating configuration apps without explicit schema files.
  • Forgetting
    settingsType
    or
    @settings
    in the service that should receive configuration.
  • Fetching configuration over HTTP even though it is already injected in
    ctx.vtex.settings
    .
  • Treating configuration apps as general-purpose operational storage.
  • 当实际需求是跨应用共享配置契约时,仍然使用应用设置。
  • 创建配置应用时未提供显式Schema文件。
  • 在需要接收配置的服务中遗漏了
    settingsType
    @settings
    声明。
  • 即使配置已经注入到
    ctx.vtex.settings
    中,仍然通过HTTP拉取配置。
  • 将配置应用当作通用运营存储使用。

Review checklist

评审检查清单

  • Is a configuration app really needed instead of plain
    settingsSchema
    ?
  • Could this case be solved with local app settings and
    settingsSchema
    instead of a separate configuration app?
  • Does the service explicitly opt in to configuration resolution with
    settingsType
    or
    @settings
    ?
  • When configuration is injected through service routes or events, is
    settingsType: "workspace"
    declared where needed?
  • Is the configuration contract defined through
    configuration/schema.json
    and matched by
    configuration.json
    ?
  • Does the service read configuration from
    ctx.vtex.settings
    instead of inventing extra fetches?
  • If the flow depends on reading installed workspace apps or their configuration, was
    read-workspace-apps
    evaluated intentionally instead of added by default?
  • Does the configuration schema stay closed and explicit enough for a shared contract?
  • Is the configuration contract clearly separate from operational data storage?
  • 是否真的需要配置应用,而非普通的
    settingsSchema
    就能满足需求?
  • 本场景是否可以通过本地应用设置加
    settingsSchema
    解决,而非单独的配置应用?
  • 服务是否通过
    settingsType
    @settings
    显式声明参与配置解析?
  • 当配置通过服务路由或事件注入时,是否在需要的位置声明了
    settingsType: "workspace"
  • 配置契约是否通过
    configuration/schema.json
    定义,且与
    configuration.json
    匹配?
  • 服务是否从
    ctx.vtex.settings
    读取配置,而非额外发起拉取请求?
  • 如果流程依赖读取已安装的工作区应用或其配置,是否是主动评估后才添加
    read-workspace-apps
    权限,而非默认添加?
  • 配置Schema是否足够闭合、明确,适合作为共享契约?
  • 配置契约是否与运营数据存储明确分离?

Reference

参考资料