Install AI Tools

B2C Commerce tools, documentation, and skills for your assistant.

Claude

Using Claude Desktop? See Claude Desktop (Cowork) setup.

Install the plugin Recommended

bash
claude plugin marketplace add SalesforceCommerceCloud/b2c-developer-tooling
claude plugin install b2c-dx-mcp@b2c-developer-tooling

Start a new Claude Code session. To install for the current project only, run it from your project directory with --scope project.

Manual MCP setup
bash
claude mcp add --transport stdio --scope user b2c-dx-mcp -- npx -y @salesforce/b2c-dx-mcp@latest

Start a new session. To configure the current project only, run it from your project directory with --scope project. See Claude Code MCP setup.

Codex/ChatGPT

Using the ChatGPT desktop app (ChatGPT Work)? See ChatGPT Work setup.

Install the plugin Recommended

bash
codex plugin marketplace add SalesforceCommerceCloud/b2c-developer-tooling
codex plugin add b2c-dx-mcp@b2c-developer-tooling

Start a new Codex session in your project. Codex plugins also work in ChatGPT Work.

Manual MCP setup
bash
codex mcp add b2c-dx-mcp -- npx -y @salesforce/b2c-dx-mcp@latest

Or add this to ~/.codex/config.toml (or $CODEX_HOME/config.toml if customized):

toml
[mcp_servers.b2c-dx-mcp]
command = "npx"
args = ["-y", "@salesforce/b2c-dx-mcp@latest"]

Start a new session. See Codex MCP configuration.

VS Code

Install the plugin Recommended

  1. Open the Command Palette (Cmd/Ctrl+Shift+P) and run Chat: Install Plugin from Source.
  2. Enter SalesforceCommerceCloud/b2c-developer-tooling.
  3. Select b2c-dx-mcp and follow the installation prompts.
  4. Start a new chat in GitHub Copilot.
Manual MCP setup

Add this to .vscode/mcp.json in your workspace:

json
{
  "servers": {
    "b2c-dx-mcp": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@salesforce/b2c-dx-mcp@latest"]
    }
  }
}

See VS Code MCP setup.

Copilot CLI setup

Cursor

Reload the MCP server in Cursor after installation.

Manual MCP setup

Add this to .cursor/mcp.json in your project:

json
{
  "mcpServers": {
    "b2c-dx-mcp": {
      "command": "npx",
      "args": ["-y", "@salesforce/b2c-dx-mcp@latest"]
    }
  }
}

For all projects, use ~/.cursor/mcp.json instead.

See Cursor's MCP documentation.

OpenCode

Add this to opencode.json in your project:

json
{
  "mcp": {
    "b2c-dx-mcp": {
      "type": "local",
      "command": ["npx", "-y", "@salesforce/b2c-dx-mcp@latest"],
      "enabled": true
    }
  }
}

Restart OpenCode. For all projects, use ~/.config/opencode/opencode.json. See OpenCode MCP setup.

Gemini

Run:

bash
gemini mcp add --scope user b2c-dx-mcp -- npx -y @salesforce/b2c-dx-mcp@latest

Start a new Gemini CLI session. To configure the current project only, run it from your project directory with --scope project. See Gemini CLI MCP setup.

No separate skills plugins needed.

Other clients and manual setup →
Skip to content
View as Markdown
View as Markdown

Migrating from sfcc-ci's JavaScript API ​

This guide is for users who call sfcc-ci programmatically via require('sfcc-ci') in Node.js scripts. If you only use the CLI, see the CLI migration guide instead.

Key Paradigm Shifts ​

Callbacks to async/await ​

sfcc-ci uses Node.js-style callbacks. The SDK uses Promises and async/await.

sfcc-ci:

javascript
const sfcc = require('sfcc-ci');

sfcc.code.list('my-instance.demandware.net', token, function (err, versions) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(versions);
});

b2c-tooling-sdk:

typescript
import {listCodeVersions} from '@salesforce/b2c-tooling-sdk/operations/code';

const versions = await listCodeVersions(instance);
console.log(versions);

The SDK is TypeScript-first with full type definitions. All examples use TypeScript, but the SDK works in plain JavaScript (ESM) as well.

Token Passing to Config-Based Auth ​

With sfcc-ci you authenticate once, then thread the token through every call:

javascript
sfcc.auth.auth(clientId, clientSecret, function (err, token) {
  sfcc.code.list(instance, token, function (err, versions) {
    sfcc.job.run(instance, 'my-job', [], token, function (err, execution) {
      // ...
    });
  });
});

The SDK resolves credentials from dw.json, environment variables, or explicit overrides. You never pass a token — the SDK creates authenticated clients that handle token lifecycle internally:

typescript
import {resolveConfig} from '@salesforce/b2c-tooling-sdk/config';
import {createAccountManagerClient} from '@salesforce/b2c-tooling-sdk/clients';

const config = await resolveConfig();

// Instance operations (code, jobs, WebDAV, BM roles) use a B2CInstance
const instance = config.createB2CInstance();
const versions = await listCodeVersions(instance);

// Account Manager operations (users, AM roles, orgs) use a separate client
const amClient = createAccountManagerClient({}, config.createOAuth());
const users = await amClient.listUsers({size: 25});

Both instance and amClient manage their own tokens — you never see or pass a token string. See the Authentication Setup guide for credential configuration details.

Untyped Objects to Typed Clients ​

sfcc-ci returns plain objects with no type information. The SDK provides:

  • Typed operation results — CodeVersion, JobExecution, etc.
  • openapi-fetch typed clients — OCAPI, SLAS, ODS with full IDE autocompletion
  • TypeScript generics — request params, bodies, and response shapes are all type-checked
typescript
// IDE autocomplete knows `versions` is CodeVersion[], each with .id, .active, etc.
const versions = await listCodeVersions(instance);

// The OCAPI client is fully typed — invalid paths or params are compile-time errors
const {data, error} = await instance.ocapi.GET('/sites/{site_id}', {
  params: {path: {site_id: 'RefArch'}},
});

Quick Start: Replacing a Typical sfcc-ci Script ​

Here is a realistic before/after for a CI/CD deploy script.

Before (sfcc-ci):

javascript
const sfcc = require('sfcc-ci');

sfcc.auth.auth(process.env.CLIENT_ID, process.env.CLIENT_SECRET, function (err, token) {
  if (err) throw err;

  sfcc.code.deploy('my-sandbox.demandware.net', './build/code.zip', token, {}, function (err) {
    if (err) throw err;

    sfcc.code.activate('my-sandbox.demandware.net', 'version1', token, function (err) {
      if (err) throw err;
      console.log('Deployed and activated!');
    });
  });
});

After (b2c-tooling-sdk):

typescript
import {resolveConfig} from '@salesforce/b2c-tooling-sdk/config';
import {findAndDeployCartridges, activateCodeVersion} from '@salesforce/b2c-tooling-sdk/operations/code';

const config = await resolveConfig();
const instance = config.createB2CInstance();

await findAndDeployCartridges(instance, './cartridges', {reload: true});
await activateCodeVersion(instance, 'version1');
console.log('Deployed and activated!');

Key differences:

  • No ZIP file — the SDK discovers cartridges from source directories and handles zipping/uploading
  • No token threading — credentials come from dw.json or environment variables
  • Flat control flow — async/await instead of nested callbacks
  • Error handling — use standard try/catch

API Mapping by Module ​

Authentication (sfcc.auth) ​

sfcc-ci requires an explicit auth call that returns a token:

javascript
sfcc.auth.auth(clientId, clientSecret, function (err, token) {
  // token must be passed to all subsequent calls
});

The SDK resolves config from multiple sources (environment variables, dw.json, explicit overrides) and creates authenticated instances automatically:

typescript
import {resolveConfig} from '@salesforce/b2c-tooling-sdk/config';

// Credentials from SFCC_OAUTH_CLIENT_ID / SFCC_OAUTH_CLIENT_SECRET env vars,
// or clientId / clientSecret in dw.json, or explicit overrides:
const config = await resolveConfig({
  clientId: 'my-client-id',
  clientSecret: 'my-client-secret',
});

// Instance handles token lifecycle internally
const instance = config.createB2CInstance();

Code Management (sfcc.code) ​

sfcc-ciSDK
code.list(instance, token, cb)listCodeVersions(instance)
code.deploy(instance, archive, token, opts, cb)findAndDeployCartridges(instance, dir, opts)
code.activate(instance, version, token, cb)activateCodeVersion(instance, versionId)
typescript
import {
  listCodeVersions,
  activateCodeVersion,
  deleteCodeVersion,
  findAndDeployCartridges,
} from '@salesforce/b2c-tooling-sdk/operations/code';

// List code versions
const versions = await listCodeVersions(instance);
for (const v of versions) {
  console.log(`${v.id} active=${v.active}`);
}

// Deploy cartridges from source directory (discovers, zips, uploads via WebDAV)
const result = await findAndDeployCartridges(instance, './cartridges', {reload: true});

// Activate a specific version
await activateCodeVersion(instance, 'version1');

// Delete a code version
await deleteCodeVersion(instance, 'old-version');

TIP

The SDK deploys from cartridge source directories, not pre-built ZIP archives. findAndDeployCartridges handles discovery (via .project files), archiving, and upload in one call.

Job Execution (sfcc.job) ​

sfcc-ciSDK
job.run(instance, jobId, params, token, cb)executeJob(instance, jobId, opts?)
job.status(instance, jobId, execId, token, cb)getJobExecution(instance, jobId, execId)
(no equivalent)waitForJob(instance, jobId, execId, opts?)
typescript
import {executeJob, waitForJob, siteArchiveImport} from '@salesforce/b2c-tooling-sdk/operations/jobs';

// Run a custom job
const execution = await executeJob(instance, 'my-custom-job', {
  parameters: [{key: 'param1', value: 'value1'}],
});

// Wait for completion (polls automatically)
const completed = await waitForJob(instance, 'my-custom-job', execution.id);
console.log(`Job finished: ${completed.execution_status}`);

// Import a site archive (upload + run import job + wait)
const importResult = await siteArchiveImport(instance, './site-data.zip');

waitForJob has no sfcc-ci equivalent — sfcc-ci scripts typically implement their own polling loop. The SDK handles this with configurable polling intervals and timeouts.

Instance / WebDAV (sfcc.instance, sfcc.webdav) ​

sfcc-ciSDK
instance.upload(instance, file, token, opts, cb)instance.webdav.put(path, data)
instance.import(instance, file, token, cb)siteArchiveImport(instance, zipPath)
webdav.upload(instance, path, file, token, opts, cb)instance.webdav.put(path, data)
typescript
import {readFileSync} from 'node:fs';

// Upload a file via WebDAV
const data = readFileSync('./my-file.zip');
await instance.webdav.put('/cartridges/my-file.zip', data);

// Check if a file exists
const exists = await instance.webdav.exists('/cartridges/my-file.zip');

// List directory contents
const listing = await instance.webdav.propfind('/cartridges/');

// Delete a file
await instance.webdav.delete('/cartridges/my-file.zip');

The WebDAV client exposes a richer API than sfcc-ci: put, propfind, mkcol, delete, exists, copy, move.

Account Manager Users (sfcc.user) ​

sfcc-ciSDK
user.create(org, user, mail, ...)createUser(client, opts)
user.list(org, role, login, ...)listUsers(client, opts)
user.update(login, changes, ...)updateUser(client, opts)
user.delete(login, purge, ...)deleteUser(client, userId) / purgeUser(client, userId)
user.reset(login, ...)resetUser(client, userId)
user.grant(login, role, scope, ...)grantRole(client, opts)
user.revoke(login, role, scope, ...)revokeRole(client, opts)

Account Manager operations use a separate client (not instance), since they talk to Account Manager rather than a B2C instance:

typescript
import {resolveConfig} from '@salesforce/b2c-tooling-sdk/config';
import {createAccountManagerClient} from '@salesforce/b2c-tooling-sdk/clients';

const config = await resolveConfig();

// Unified client for users, roles, orgs, and API clients
const client = createAccountManagerClient({}, config.createOAuth());

// List users
const users = await client.listUsers({size: 25});

// Create a user
const newUser = await client.createUser({
  mail: 'user@example.com',
  firstName: 'Test',
  lastName: 'User',
  organizationId: 'my-org-id',
});

// Grant a role
await client.grantRole(newUser.id, 'bm-admin', 'my-realm');

See the Account Manager guide for more details on AM operations.

Account Manager & BM Roles (sfcc.role) ​

The SDK separates Account Manager roles from Business Manager instance roles into distinct modules:

sfcc-ciSDKModule
role.list(token, count)listRoles(client, opts)operations/roles
role.listLocal(instance, ...)listBmRoles(instance, opts)operations/bm-roles
user.grantLocal(instance, login, role, ...)grantBmRole(instance, roleId, login)operations/bm-roles
user.revokeLocal(instance, login, role, ...)revokeBmRole(instance, roleId, login)operations/bm-roles
typescript
// Account Manager roles (organization-level) — use the unified client directly
const amRoles = await client.listRoles({size: 50});

// Business Manager roles (instance-level)
import {listBmRoles, grantBmRole, revokeBmRole} from '@salesforce/b2c-tooling-sdk/operations/bm-roles';
const bmRoles = await listBmRoles(instance);
await grantBmRole(instance, 'Administrator', 'user@example.com');
await revokeBmRole(instance, 'Administrator', 'user@example.com');

Organizations (sfcc.org) ​

typescript
// Use the unified client directly
const orgs = await client.listOrgs({size: 25});
const org = await client.getOrg('my-org-id');

Using Typed Clients Directly ​

For APIs that have a typed client but no high-level operations wrapper, use the openapi-fetch client directly. All typed clients follow the same pattern: client.METHOD('/path', {params, body}) returning {data, error}.

SLAS Client Management ​

sfcc-ci has slas.tenant.* and slas.client.* methods. The SDK provides a typed SLAS client:

typescript
import {resolveConfig} from '@salesforce/b2c-tooling-sdk/config';
import {createSlasClient} from '@salesforce/b2c-tooling-sdk/clients';

const config = await resolveConfig();
const slas = createSlasClient({shortCode: 'kv7kzm78'}, config.createOAuth());

// List tenants (sfcc-ci: slas.tenant.list(...))
const {data: tenants} = await slas.GET('/tenants');

// Get a specific tenant (sfcc-ci: slas.tenant.get(...))
const {data: tenant} = await slas.GET('/tenants/{tenantId}', {
  params: {path: {tenantId: 'my-tenant'}},
});

// List SLAS clients (sfcc-ci: slas.client.list(...))
const {data: clients} = await slas.GET('/tenants/{tenantId}/clients', {
  params: {path: {tenantId: 'my-tenant'}},
});

Direct OCAPI Access ​

For any OCAPI Data API endpoint, use instance.ocapi:

typescript
// Get site details
const {data: site} = await instance.ocapi.GET('/sites/{site_id}', {
  params: {path: {site_id: 'RefArch'}},
});

// Search for something via OCAPI
const {data, error} = await instance.ocapi.POST('/customer_search', {
  body: {query: {text_query: {fields: ['email'], search_phrase: 'test@example.com'}}},
});

if (error) {
  console.error('Search failed:', error);
}

The OCAPI client is generated from the OpenAPI spec, so all paths, parameters, and response types are fully typed.

Comprehensive Mapping Table ​

sfcc-ci MethodSDK EquivalentImport Path
auth.auth(clientId, secret, cb)resolveConfig() + config.createB2CInstance()*/config
code.list(instance, token, cb)listCodeVersions(instance)*/operations/code
code.deploy(instance, archive, token, opts, cb)findAndDeployCartridges(instance, dir, opts)*/operations/code
code.activate(instance, version, token, cb)activateCodeVersion(instance, versionId)*/operations/code
code.compare(...)Not ported
code.diffdeploy(...)Not ported
manifest.generate(...)Not ported
cartridge.add(...)addCartridge(instance, siteId, opts)*/operations/sites
instance.upload(instance, file, token, opts, cb)instance.webdav.put(path, data)*/instance
instance.import(instance, file, token, cb)siteArchiveImport(instance, zipPath)*/operations/jobs
job.run(instance, jobId, params, token, cb)executeJob(instance, jobId, opts?)*/operations/jobs
job.status(instance, jobId, execId, token, cb)getJobExecution(instance, jobId, execId)*/operations/jobs
webdav.upload(instance, path, file, token, opts, cb)instance.webdav.put(path, data)*/instance
user.create(org, user, ...)createUser(client, opts)*/operations/users
user.list(org, role, ...)listUsers(client, opts)*/operations/users
user.update(login, changes, ...)updateUser(client, opts)*/operations/users
user.delete(login, purge, ...)deleteUser(client, id) / purgeUser(client, id)*/operations/users
user.reset(login, ...)resetUser(client, id)*/operations/users
user.grant(login, role, scope, ...)grantRole(client, opts)*/operations/users
user.revoke(login, role, scope, ...)revokeRole(client, opts)*/operations/users
user.createLocal(...)Not ported
user.searchLocal(...)Not ported
user.updateLocal(...)Not ported
user.deleteLocal(...)Not ported
user.grantLocal(instance, login, role, ...)grantBmRole(instance, roleId, login)*/operations/bm-roles
user.revokeLocal(instance, login, role, ...)revokeBmRole(instance, roleId, login)*/operations/bm-roles
role.list(token, count)listRoles(client, opts)*/operations/roles
role.listLocal(instance, ...)listBmRoles(instance, opts)*/operations/bm-roles
slas.tenant.add/get/list/delete(...)createSlasClient() typed client*/clients
slas.client.add/get/list/delete(...)createSlasClient() typed client*/clients

All import paths use the @salesforce/b2c-tooling-sdk prefix (abbreviated as * above).

What the SDK Adds Beyond sfcc-ci ​

The SDK provides many capabilities that sfcc-ci does not have:

  • Content library operations — fetch, parse, and export content libraries
  • CIP analytics — run SQL queries against Commerce Intelligence Platform
  • MRT operations — full Managed Runtime lifecycle (projects, bundles, deployments, environments, redirects, notifications)
  • Log operations — tail and search instance logs in real-time
  • Scaffold system — generate project templates from scaffolds
  • Workspace discovery — detect project types (PWA Kit, SFRA, Storefront Next, cartridges)
  • Plugin/middleware system — extend with custom config sources, HTTP middleware, lifecycle hooks
  • CDN zones management — manage CDN configuration
  • Full TypeScript type safety — typed API clients generated from OpenAPI specs

See the SDK Reference for the complete API surface.

Error Handling ​

sfcc-ci uses callback error parameters:

javascript
sfcc.code.list(instance, token, function (err, result) {
  if (err) {
    console.error('Failed:', err);
    return;
  }
  // use result
});

The SDK throws errors that you catch with try/catch:

typescript
import {HTTPError} from '@salesforce/b2c-tooling-sdk/errors';

try {
  const versions = await listCodeVersions(instance);
} catch (error) {
  if (error instanceof HTTPError) {
    console.error(`HTTP ${error.statusCode}: ${error.message}`);
  } else {
    console.error('Unexpected error:', error);
  }
}

SDK operations throw Error with descriptive messages. HTTP-level errors include status codes and response details via the cause property.

Next Steps ​