---
url: /commerce-klaus/packages/typescript-sfcc.md
---
[![NPM version][npm-image]][npm-url] [![Downloads][npm-downloads-image]][npm-url]

# @commerce-klaus/typescript-sfcc

TypeScript tooling for Salesforce Commerce Cloud cartridge projects.

The package currently ships two main entry points:

* a tsserver plugin that resolves SFCC-specific module patterns such as `~/...`, `*/...`, cartridge aliases, and `module.superModule`
* a CLI that typechecks cartridge projects with the same resolution behavior

## What Commerce Klaus adds

The
[official Salesforce B2C Commerce IDE Extension](https://salesforcecommercecloud.github.io/b2c-developer-tooling/vscode-extension/)
is the preferred foundation for Script API IntelliSense and standard cartridge
module resolution in VS Code. The broader
[Salesforce B2C Developer Tooling](https://salesforcecommercecloud.github.io/b2c-developer-tooling/)
also bundles XSD schemas and offers XML validation through its CLI; this checks
document structure rather than generating JavaScript types. Commerce Klaus
adds generated project contracts and checks that can run without an editor:

| Area                    | Commerce Klaus addition                                                                                | Practical benefit                                                      |
| ----------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| Project contracts       | Projects metadata, `hooks.json`, Custom API schemas, and `steptypes.json` into TypeScript declarations | Configuration and JavaScript implementation are checked together       |
| Super modules           | Resolves and types `module.superModule` chains                                                         | Cartridge overrides remain navigable and type-safe                     |
| Type checking           | Runs the cartridge-aware TypeScript compiler from the command line                                     | The same class of diagnostics can block CI without a VS Code extension |
| Registration validation | Checks hook descriptors, referenced scripts, and required CommonJS exports                             | Broken registrations fail before deployment                            |
| Shared tooling          | Reuses the same cartridge semantics across TypeScript, ESLint, Vite, Babel, and Vitest packages        | Local tools agree about which cartridge implementation wins            |

## Keep the JavaScript, add the guarantees

SFCC projects often contain years of production-tested CommonJS JavaScript. Rewriting that code in TypeScript would introduce a compilation step between the source developers inspect and the code running in the sandbox. That can make deployment artifacts, production stack traces, and urgent debugging harder to follow.

This package takes an incremental route: cartridge code remains runnable JavaScript, while TypeScript checks it through JSDoc and generated declarations. Existing modules can gain useful guarantees one function at a time, without a source transformation or a framework migration.

The generated types are especially valuable because they come from contracts the project already maintains:

* site metadata defines custom and system object attributes
* `hooks.json` and Salesforce Script API declarations define hook boundaries
* Custom API schemas define request and response shapes
* `steptypes.json` defines job parameters, lifecycle functions, and status codes

This keeps annotations small and reduces duplicated declarations that can drift away from the platform configuration. Prefer a generated function contract over repeating every parameter and return type:

```js
/** @type {SfccJobSteps.Definitions["custom.ExportCatalog"]["Functions"]["execute"]} */
const execute = function (parameters, stepExecution) {
  // parameters and stepExecution are inferred from steptypes.json
}
```

Type checking complements the ESLint compatibility rules. Types catch mismatched project contracts and Script API usage; lint rules reject syntax, globals, module patterns, and standard-library features that are not safe for the configured SFCC runtime. Together they move failures closer to the edit that introduced them, while leaving the deployed JavaScript recognizable.

For the broader modernization strategy, see [Modernize SFCC JavaScript with confidence](/blog/modernize-sfcc-javascript-with-confidence).

Commerce Klaus can read these options from one shared
[`commerce-klaus.config.ts` or `.js` file](/guide/project-configuration).
Package options remain supported and override the central values.

## Install

::: code-group

```bash [pnpm]
pnpm add -D @commerce-klaus/typescript-sfcc typescript @salesforce/b2c-cli
```

```bash [yarn]
yarn add -D @commerce-klaus/typescript-sfcc typescript @salesforce/b2c-cli
```

```bash [npm]
npm install -D @commerce-klaus/typescript-sfcc typescript @salesforce/b2c-cli
```

```bash [Vite+]
vp install -D @commerce-klaus/typescript-sfcc typescript @salesforce/b2c-cli
```

:::

## SFCC Script Types Setup

`@commerce-klaus/typescript-sfcc` is compatible with Salesforce `b2c-script-types` output.

For `dw/*` imports, the tool resolves types from a vendored workspace path:

* `.b2c-script-types/types/dw/*`

The path is calculated relative to each project config, so both setups are supported:

* `cartridges/jsconfig.json`
* `cartridges/<cartridge>/jsconfig.json`

Example workflow with the
[Salesforce B2C Developer Tooling CLI](https://salesforcecommercecloud.github.io/b2c-developer-tooling/):

```bash
b2c setup ide vscode-types --copy --force --output .b2c-script-types/jsconfig.generated.json
```

Notes:

* `sfcc-dts` is not required for `dw/*` resolution in this package.
* Keep `.b2c-script-types/types` available in the workspace before running `sfcc-ts-typecheck`.

## Custom Attributes

* `sfcc-ts-sync-types` extends `b2c-script-types` by reading all XML files under `<siteTemplatePath>/meta/*.xml` (default: `sites/site_template/meta/*.xml`) and generating custom attribute declarations into `.b2c-script-types/types/sfcc-custom-attributes.generated.d.ts`.
* Custom Object and System Object attribute interfaces are available by name to JavaScript JSDoc from `.b2c-script-types/types/sfcc-custom-attributes.generated.d.ts`, for example `CustomObjectExampleNotificationCustomAttributes` and `ProductCustomAttributes`.
* The generated declarations are consumed by both `sfcc-ts-typecheck` and the tsserver plugin, so editor diagnostics and CLI diagnostics use the same custom attribute typing.
* The generated declarations also patch `dw/object/SystemObjectMgr` so supported `type` literals narrow `getAllSystemObjects`, `querySystemObject`, and `querySystemObjects` to the matching system object class.
* Custom attribute values are inferred by metadata type. For enums, `select-multiple-flag=true` is treated as a multi-value enum and emitted as `SfccEnumValue<T>[]`.
* Attribute type values follow Salesforce metadata schema (`metadata.xsd`), where enum multi-select is modeled via `enum-of-*` plus `select-multiple-flag=true` (not via `set-of-enum-of-*`).

Custom attribute mapping reference:

| Metadata type                                            | Generated TypeScript type                 |
| -------------------------------------------------------- | ----------------------------------------- |
| `boolean`                                                | `boolean`                                 |
| `date`, `datetime`                                       | `Date`                                    |
| `double`, `int`, `integer`, `long`, `number`, `quantity` | `number`                                  |
| `email`, `password`, `string`, `text`, `url`             | `string`                                  |
| `html`                                                   | `dw.content.MarkupText`                   |
| `enum-of-string` (with value-definitions)                | `SfccEnumValue<"value1" \| "value2" ...>` |
| `enum-of-int` (with value-definitions)                   | `SfccEnumValue<1 \| 2 ...>`               |
| `enum-of-*` with `select-multiple-flag=true`             | `SfccEnumValue<...>[]`                    |
| `enum-of-*` (without value-definitions)                  | `SfccEnumValue<baseType>`                 |
| unknown `enum-of-*`                                      | `SfccEnumValue<string \| number>`         |
| `set-of-string`                                          | `string[]`                                |
| `set-of-int`, `set-of-double`                            | `number[]`                                |
| invalid `set-of-enum-of-*` (not in `metadata.xsd`)       | `unknown[]`                               |
| unknown `set-of-*`                                       | `unknown[]`                               |
| unsupported/unknown single-value type                    | `unknown`                                 |

Example (`Product.custom`):

```ts{3,6,14-15,21-22} [product.ts]
import ProductMgr = require("dw/catalog/ProductMgr")

const product = ProductMgr.getProduct("my-product-id")

if (product) {
  // enum-of-string -> SfccEnumValue<...>
  const status = product.custom.status
  if (status) {
    const value = status.getValue()
    const label = status.getDisplayValue()
    void value
    void label
  }

  // enum-of-int + select-multiple-flag=true -> SfccEnumValue<...>[]
  const modes = product.custom.modes
  if (modes && modes.length > 0) {
    const firstModeValue = modes[0].getValue()
    void firstModeValue
  }

  // unknown custom attribute -> TypeScript error
  // @ts-expect-error Property 'doesNotExist' does not exist
  product.custom.doesNotExist
}
```

Named Custom Object attribute types can be referenced directly from JavaScript JSDoc:

```js{4-7} [notification.js]
// @ts-check

/**
 * @param {CustomObjectExampleNotificationCustomAttributes} custom
 * @returns {string | undefined}
 */
function readNotification(custom) {
  return custom.eventCode
}
```

System Object attributes use the same pattern:

```js{4-7} [product.js]
// @ts-check

/**
 * @param {ProductCustomAttributes} custom
 * @returns {string | undefined}
 */
function readProductOrigin(custom) {
  return custom.origin
}
```

Schema-driven assumptions:

* The generator follows the Salesforce metadata schema namespace `http://www.demandware.com/xml/impex/metadata/2006-10-31`.
* Type inference uses these fields from attribute definitions:
  * `type`
  * `value-definitions` and `value-definition/value`
  * `select-multiple-flag` (for enum multi-select)
  * `mandatory-flag` (required vs optional property)
* Supported metadata roots are `type-extension/custom-attribute-definitions` and `custom-type/attribute-definitions`.
* `type-extension` object types are assigned to `dw/*` modules via a preferred mapping table for known SFCC system objects (for example `Product` -> `dw/catalog/Product`, `Order` -> `dw/order/Order`).
* If no preferred mapping exists, a system object type is only assigned when its `dw/*` basename match is unique; ambiguous matches are skipped.
* For enum attributes, multi-value behavior is derived from `select-multiple-flag=true`.
* For set attributes, valid values follow `metadata.xsd` (`set-of-string`, `set-of-int`, `set-of-double`); non-schema variants fall back to `unknown[]`.

## Hook Handling

`sfcc-ts-sync-types` generates `.b2c-script-types/types/sfcc-hooks.generated.d.ts` with `SfccHooks` aliases for Salesforce system hook interfaces available in the vendored Script API types. For example, `SfccHooks.OrderCalculate` refers to the `dw.order.calculate` signature.

Existing Salesforce system hook signatures can be used from JavaScript JSDoc without duplicating their parameters or return type:

```js{3-4,9} [calculate.js]
// @ts-check

/** @type {SfccHooks.OrderCalculate} */
const calculate = function (lineItemCtnr) {
  // lineItemCtnr is typed from dw/order/hooks/CalculateHooks
}

exports.calculate = calculate
```

`SfccHooks` includes only hook signatures declared by the synchronized Salesforce Script API types. Shopper API hook documents and project-specific `c_*` response fields remain project-local types.

For Shopper API hooks, add an optional `sfcc-hooks.d.ts` file next to `cartridges/jsconfig.json`. It is loaded automatically by both `sfcc-ts-typecheck` and the tsserver plugin, even when cartridge project configs include JavaScript files only. Keep document models narrow and declare only the fields read or written by the hook:

```ts{4-6} [cartridges/sfcc-hooks.d.ts]
export {}

declare global {
  namespace SfccHooks {
    type ShopperProductModifyGetResponse = (document: { c_brand: string }) => void
  }
}
```

Use the project-local alias from the hook implementation:

```js{3-4,6,9} [modifyGETResponse.js]
// @ts-check

/** @type {SfccHooks.ShopperProductModifyGetResponse} */
const modifyGETResponse = function (document) {
  document.c_brand = "Example"
}

exports.modifyGETResponse = modifyGETResponse
```

Types that only apply to a single cartridge can instead be declared in an `sfcc-hooks.d.ts` file directly inside that cartridge, next to its own `jsconfig.json` (for example `cartridges/int_storepickup/sfcc-hooks.d.ts`). All discovered files share the same global `SfccHooks` namespace, so cartridge-specific and cartridges-wide declarations can be mixed:

* `cartridges/sfcc-hooks.d.ts`: shared across all cartridges
* `cartridges/<cartridge>/sfcc-hooks.d.ts`: specific to that cartridge only

Migration from `1.x`: this package previously loaded a single `sfcc-hooks.d.ts` at the workspace root (one level above `cartridges/`). Move that file into `cartridges/` to keep it working.

Recommended `package.json` scripts:

```json{3-6} [package.json]
{
  "scripts": {
    "types:sfcc:sync": "sfcc-ts-sync-types --min-version 26.7.0",
    "types:sfcc:sync:force": "sfcc-ts-sync-types --force",
    "prepare": "sfcc-ts-sync-types --min-version 26.7.0",
    "typecheck:cartridges": "sfcc-ts-typecheck"
  }
}
```

If your CI install uses `--ignore-scripts`, run the synchronization script
explicitly before `sfcc-ts-typecheck`:

::: code-group

```bash [pnpm]
pnpm run types:sfcc:sync
```

```bash [yarn]
yarn run types:sfcc:sync
```

```bash [npm]
npm run types:sfcc:sync
```

```bash [Vite+]
vp run types:sfcc:sync
```

:::

## Custom APIs

`sfcc-ts-sync-types` generates `.b2c-script-types/types/sfcc-custom-apis.generated.d.ts` from [Custom API](https://developer.salesforce.com/docs/commerce/commerce-api/guide/custom-apis.html) contracts. It scans every `api.json` found under `cartridge/rest-apis/**` and parses the referenced OAS 3.0 `schema.yaml` files to derive:

* `SfccCustomApis.Schemas`: one entry per named schema in `components.schemas`.
* `SfccCustomApis.Operations`: one entry per `operationId`, with a `Handler`, `Parameters` (grouped by `path`/`query`/`header`), an optional `RequestBody`, and the `Response` type resolved from the first successful (`2xx`) `application/json` response.

Type the endpoint implementation script with the generated operation type:

```js{5,7,10-11,17-18} [script.js]
// @ts-check

const RESTResponseMgr = require("dw/system/RESTResponseMgr")

/** @type {SfccCustomApis.Operations["getLoyaltyInfo"]["Handler"]} */
const getLoyaltyInfo = function () {
  const customerId = request.getHttpParameterMap().get("c_customer_id").getStringValue()

  /** @type {SfccCustomApis.Operations["getLoyaltyInfo"]["Response"]} */
  const info = { tier: "silver", points: 14275 }

  return RESTResponseMgr.createSuccess(info).render()
}

getLoyaltyInfo.public = true

exports.getLoyaltyInfo = getLoyaltyInfo
```

Notes:

* `$ref` values are resolved for `components.schemas`, `components.parameters`, `components.requestBodies`, and `components.responses`.
* Typed `additionalProperties` schemas are emitted as `Record<string, ValueType>`.
* `allOf`/`oneOf`/`anyOf` compositions are not resolved yet and fall back to `unknown`.
* Schema and operation names are expected to be unique across all scanned cartridges; on a name collision, the first occurrence found wins.

## Custom job steps

`sfcc-ts-sync-types` generates `.b2c-script-types/types/sfcc-job-steps.generated.d.ts` from the effective `steptypes.json` files in cartridge-path order. The generated `SfccJobSteps.Definitions` registry contains each resolved custom type ID with:

* `Input`: parameters accepted by a metadata-driven test invocation. Optional parameters and parameters with defaults can be omitted.
* `Parameters`: the values received by the job module after defaults and type conversion.
* `StatusCode`: the built-in `OK` and `ERROR` codes plus declared custom task status codes.
* `Functions`: the configured task function or chunk lifecycle functions with their SFCC arguments and return types.

Use a generated function signature to type a job module implementation:

```js
// @ts-check

/** @type {SfccJobSteps.Definitions["custom.GenerateFeed"]["Functions"]["run"]} */
const run = function (parameters, stepExecution) {
  const mode = parameters.Mode
  const jobID = stepExecution.getJobExecution().getJobID()
  // ...
}

exports.run = run
```

Parameter metadata maps `boolean` to `boolean`, `long` and `double` to `number`, and `string` to `string`. `enum-values` become string literal unions. Date and time parameter types follow `target-type`: `long` becomes `number`, while `date` and the default conversion become `Date`.

`steptypes.json` does not describe the items passed through a chunk step. Without additional project information, generated `read`, `process`, and `write` signatures therefore use `unknown`. Augment `SfccJobSteps.ChunkStepTypes` in a project declaration file to connect those functions with application-specific types:

```ts [cartridges/sfcc-job-steps.d.ts]
export {}

declare global {
  namespace SfccJobSteps {
    interface ChunkStepTypes {
      "custom.SynchroniseWizvilleReviews": {
        ReadItem: dw.catalog.Product
        ProcessedItem: {
          product: dw.catalog.Product
          ratingAverage: number
          ratingCount: number
        }
      }
    }
  }
}
```

The generated function types then make `read` return `ReadItem`, pass it to `process`, and pass `ProcessedItem` values to `write`. Both `read` and `process` may also return `null` or `undefined` according to the chunk lifecycle. If `ProcessedItem` is omitted, it defaults to `ReadItem`, which covers chunk steps without a process function.

When the generated declaration is loaded together with `@commerce-klaus/vitest-sfcc`, `loadSfccJobStep()` accepts only registered type IDs and its `run()` method requires the corresponding `Input`. Without generated declarations, the API retains its general `string` and `Record<string, unknown>` fallback.

## tsserver Plugin

Add the plugin to your cartridge `jsconfig.json` or `tsconfig.json`:

```json{3} [cartridges/jsconfig.json]
{
  "compilerOptions": {
    "plugins": [{ "name": "@commerce-klaus/typescript-sfcc" }]
  }
}
```

The plugin is additive when the
[official Salesforce B2C Commerce VS Code extension](https://salesforcecommercecloud.github.io/b2c-developer-tooling/vscode-extension/)
is installed. Resolutions already supplied by Salesforce's TypeScript Server
plugin take precedence. Commerce Klaus only resolves modules left unresolved and
continues to load generated project declarations and transform
`module.superModule`. Without the Salesforce extension, the Commerce Klaus
resolver remains a standalone fallback.

## CLI

The package ships these CLI binaries:

* `sfcc-ts-typecheck`
* `sfcc-ts-sync-types`

Both commands use oclif, matching the B2C CLI plugin commands for flag parsing,
help, colored terminal output, errors, and structured `--json` output. Run either
command with `--help` to see its complete option reference.

The typecheck loads the project's installed TypeScript compiler, including when
it runs through the B2C CLI plugin. This keeps compiler options and diagnostics
aligned with the TypeScript version declared by the project.

The Salesforce extension provides editor IntelliSense but does not expose a
project-wide TypeScript check. Use `sfcc-ts-typecheck` locally and in CI to apply
the same cartridge-aware checks without depending on an editor extension.

Default behavior (no flags):

* searches from the current working directory upwards for `cartridges/jsconfig.json`
* if no `references` are present, it typechecks the given config itself

Basic calls:

::: code-group

```bash [pnpm]
pnpm exec sfcc-ts-typecheck
pnpm exec sfcc-ts-typecheck --project cartridges/jsconfig.json
pnpm exec sfcc-ts-typecheck --project cartridges/tsconfig.json
```

```bash [yarn]
yarn exec sfcc-ts-typecheck
yarn exec sfcc-ts-typecheck --project cartridges/jsconfig.json
yarn exec sfcc-ts-typecheck --project cartridges/tsconfig.json
```

```bash [npm]
npm exec -- sfcc-ts-typecheck
npm exec -- sfcc-ts-typecheck --project cartridges/jsconfig.json
npm exec -- sfcc-ts-typecheck --project cartridges/tsconfig.json
```

```bash [Vite+]
vp exec sfcc-ts-typecheck
vp exec sfcc-ts-typecheck --project cartridges/jsconfig.json
vp exec sfcc-ts-typecheck --project cartridges/tsconfig.json
```

:::

If your project config is outside the cartridges folder, pass the cartridges root explicitly:

::: code-group

```bash [pnpm]
pnpm exec sfcc-ts-typecheck --project config/tsconfig.cartridges.json --cartridges-dir cartridges
```

```bash [yarn]
yarn exec sfcc-ts-typecheck --project config/tsconfig.cartridges.json --cartridges-dir cartridges
```

```bash [npm]
npm exec -- sfcc-ts-typecheck --project config/tsconfig.cartridges.json --cartridges-dir cartridges
```

```bash [Vite+]
vp exec sfcc-ts-typecheck --project config/tsconfig.cartridges.json --cartridges-dir cartridges
```

:::

`package.json` example:

```json{3} [package.json]
{
  "scripts": {
    "typecheck:cartridges": "sfcc-ts-typecheck --project cartridges/tsconfig.json"
  }
}
```

Exit codes:

* `0`: no diagnostics
* `2`: diagnostics found
* `1`: runtime/config error (for example missing config file)

### Registration validation

When a cartridge `package.json` declares a `hooks` path, `sfcc-ts-typecheck` also validates the referenced `hooks.json` file:

```json{2} [cartridges/app_custom/package.json]
{
  "hooks": "./cartridge/scripts/hooks.json"
}
```

```json{2} [cartridges/app_custom/cartridge/scripts/hooks.json]
{
  "hooks": [{ "name": "dw.order.calculate", "script": "./hooks/calculate" }]
}
```

It reports malformed registrations and missing hook scripts. For Salesforce `dw.*` hooks, it also reports statically detectable missing CommonJS exports such as `exports.afterPOST` or `module.exports = { afterPOST }`.

Project-specific hook names do not necessarily identify an exported method, so their registrations are validated structurally without inferring an export name.

The checker does not maintain or validate against a list of known Salesforce extension point names. The vendored hook declarations under `.b2c-script-types/types/dw/**/hooks/*.d.ts` only cover a subset of valid `dw.*` hooks (for example, `CalculateHooks` does not model `dw.order.calculateDiscounts`), so an unmodeled hook name is not necessarily invalid.

Registration validation runs as part of `sfcc-ts-typecheck` only; the tsserver plugin does not validate `hooks.json` in the editor.

Diagnostics for a specific `hooks.json` registration (unresolved script, missing export) are anchored to that entry, not to the top of the file, so multiple registration issues in the same `hooks.json` are reported at their correct positions.

`sfcc-ts-sync-types` options:

* `--min-version X.Y.Z`: refreshes types if vendored version is older than required
* `--force`: always refreshes vendored types
* `--output <path>`: optional output path for generated `jsconfig` metadata
* `--site-template-path <path>`: optional path to the site template directory (relative to current directory or absolute). The tool reads metadata from `<path>/meta/*.xml`.

Both standalone commands also accept `--project-directory <path>` (alias
`--working-directory`) and `--json`. Colors are automatically disabled when the
terminal does not support them.

### Clean generated declarations programmatically

`cleanGeneratedTypes()` removes only the project-specific declarations managed
by Commerce Klaus. Salesforce Script API types and unrelated declaration files
in `.b2c-script-types/types` remain untouched:

```ts
import { cleanGeneratedTypes } from "@commerce-klaus/typescript-sfcc"

const preview = cleanGeneratedTypes({
  currentDirectory: process.cwd(),
  dryRun: true,
})
```

Each result entry distinguishes a file that exists from one that was actually
removed, so the same API works for previews and cleanup automation.

[npm-url]: https://www.npmjs.com/package/@commerce-klaus/typescript-sfcc

[npm-image]: https://badgen.net/npm/v/@commerce-klaus/typescript-sfcc

[npm-downloads-image]: https://badgen.net/npm/dw/@commerce-klaus/typescript-sfcc
