Install
$ agentstack add skill-salesforcecommercecloud-b2c-developer-tooling-b2c-custom-job-steps ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Custom Job Steps Skill
This skill guides you through creating new custom job steps for Salesforce B2C Commerce batch processing.
> Running an existing job? If you need to execute jobs or import site archives via CLI, use the b2c-cli:b2c-job skill instead.
When to Use
- Creating a new scheduled job for batch processing
- Building a data import job (customers, products, orders)
- Building a data export job (reports, feeds, sync)
- Implementing data sync between systems
- Creating cleanup or maintenance tasks
Overview
Custom job steps allow you to execute custom business logic as part of B2C Commerce jobs. There are two execution models:
| Model | Use Case | Progress Tracking | |-------|----------|-------------------| | Task-oriented | Single operations (FTP, import/export) | Limited | | Chunk-oriented | Bulk data processing | Fine-grained |
File Structure
my_cartridge/
├── cartridge/
│ ├── scripts/
│ │ └── steps/
│ │ ├── myTaskStep.js # Task-oriented script
│ │ └── myChunkStep.js # Chunk-oriented script
│ └── my_cartridge.properties
└── steptypes.json # Step type definitions (at cartridge ROOT)
Important: The steptypes.json file must be placed in the root folder of the cartridge, not inside the cartridge/ directory. Only one steptypes.json file per cartridge.
Step Type Definition (steptypes.json)
{
"step-types": {
"script-module-step": [
{
"@type-id": "custom.MyTaskStep",
"@supports-parallel-execution": "false",
"@supports-site-context": "true",
"@supports-organization-context": "false",
"description": "My custom task step",
"module": "my_cartridge/cartridge/scripts/steps/myTaskStep.js",
"function": "execute",
"timeout-in-seconds": 900,
"parameters": {
"parameter": [
{
"@name": "InputFile",
"@type": "string",
"@required": "true",
"description": "Path to input file"
},
{
"@name": "Enabled",
"@type": "boolean",
"@required": "false",
"default-value": "true",
"description": "Enable processing"
}
]
},
"status-codes": {
"status": [
{
"@code": "OK",
"description": "Step completed successfully"
},
{
"@code": "ERROR",
"description": "Step failed"
},
{
"@code": "NO_DATA",
"description": "No data to process"
}
]
}
}
],
"chunk-script-module-step": [
{
"@type-id": "custom.MyChunkStep",
"@supports-parallel-execution": "true",
"@supports-site-context": "true",
"@supports-organization-context": "false",
"description": "Bulk data processing step",
"module": "my_cartridge/cartridge/scripts/steps/myChunkStep.js",
"before-step-function": "beforeStep",
"read-function": "read",
"process-function": "process",
"write-function": "write",
"after-step-function": "afterStep",
"total-count-function": "getTotalCount",
"chunk-size": 100,
"transactional": "false",
"timeout-in-seconds": 1800,
"parameters": {
"parameter": [
{
"@name": "CategoryId",
"@type": "string",
"@required": "true"
}
]
}
}
]
}
}
From Step Type to Runnable Job (jobs.xml)
steptypes.json only declares a step type — it does not create a job. To get a job that b2c job run can execute and Business Manager can schedule, author a job definition (jobs.xml) that references your step type, then import it:
b2c job import ./my-job-archive # jobs.xml at the archive root
A minimal valid definition wires one step into a flow and includes the required `` element:
/export/products.csv
2025-01-01
00:00:00.000Z
Key rules (full details in the [jobs.xml Reference](references/JOBS-XML.md)):
- **`
is required** by the schema — ajobs.xmlwithout it fails import validation. Usefor an on-demand/manually-run job, or` to schedule it. - The `
**type** attribute references the step type; its value must match the **@type-id** you declared insteptypes.json(don't confuse the two —steptypes.jsonuses@type-id,jobs.xmlusestype`). - `
children must appear in order:description → parameters → flow/split → rules → triggers`. - The cartridge carrying the step's
steptypes.json+ module must be deployed and on the cartridge path before the job can resolve the step type.
After import, run it with b2c job run MyNightlyExport --wait (see the b2c-cli:b2c-job skill).
Task-Oriented Steps
Use for single operations like FTP transfers, file generation, or import/export.
Script (scripts/steps/myTaskStep.js)
'use strict';
var Status = require('dw/system/Status');
var Logger = require('dw/system/Logger');
/**
* Execute the task step
* @param {Object} parameters - Job step parameters
* @param {dw.job.JobStepExecution} stepExecution - Step execution context
* @returns {dw.system.Status} Execution status
*/
exports.execute = function (parameters, stepExecution) {
var log = Logger.getLogger('job', 'MyTaskStep');
try {
var inputFile = parameters.InputFile;
var enabled = parameters.Enabled;
if (!enabled) {
log.info('Step disabled, skipping');
return new Status(Status.OK, 'SKIP', 'Step disabled');
}
// Your business logic here
log.info('Processing file: ' + inputFile);
// Return success
return new Status(Status.OK);
} catch (e) {
log.error('Step failed: ' + e.message);
return new Status(Status.ERROR, 'ERROR', e.message);
}
};
Status Codes
// Success
return new Status(Status.OK);
return new Status(Status.OK, 'CUSTOM_CODE', 'Custom message');
// Error
return new Status(Status.ERROR);
return new Status(Status.ERROR, null, 'Error message');
Important: Custom status codes work only with OK status. If you use a custom code with ERROR status, it is replaced with ERROR. Custom status codes cannot contain commas, wildcards, leading/trailing whitespace, or exceed 100 characters.
Chunk-Oriented Steps
Use for bulk processing of countable data (products, orders, customers).
Important: You cannot define custom exit status for chunk-oriented steps. Chunk modules always finish with either OK or ERROR.
Required Functions
| Function | Purpose | Returns | |----------|---------|---------| | read() | Get next item | Item or nothing | | process(item) | Transform item | Processed item or nothing (filters) | | write(items) | Save chunk of items | Nothing |
Optional Functions
| Function | Purpose | Returns | |----------|---------|---------| | beforeStep() | Initialize (open files, queries) | Nothing | | afterStep(success) | Cleanup (close files) | Nothing | | getTotalCount() | Return total items for progress | Number | | beforeChunk() | Before each chunk | Nothing | | afterChunk() | After each chunk | Nothing |
Script (scripts/steps/myChunkStep.js)
'use strict';
var ProductMgr = require('dw/catalog/ProductMgr');
var Transaction = require('dw/system/Transaction');
var Logger = require('dw/system/Logger');
var File = require('dw/io/File');
var FileWriter = require('dw/io/FileWriter');
var log = Logger.getLogger('job', 'MyChunkStep');
var products;
var fileWriter;
/**
* Initialize before processing
*/
exports.beforeStep = function (parameters, stepExecution) {
log.info('Starting chunk processing');
// Open resources
var outputFile = new File(File.IMPEX + '/export/products.csv');
fileWriter = new FileWriter(outputFile);
fileWriter.writeLine('ID,Name,Price');
// Query products
products = ProductMgr.queryAllSiteProducts();
};
/**
* Get total count for progress tracking
*/
exports.getTotalCount = function (parameters, stepExecution) {
return products.count;
};
/**
* Read next item
* Return nothing to signal end of data
*/
exports.read = function (parameters, stepExecution) {
if (products.hasNext()) {
return products.next();
}
// Return nothing = end of data
};
/**
* Process single item
* Return nothing to filter out item
*/
exports.process = function (product, parameters, stepExecution) {
// Filter: skip offline products
if (!product.online) {
return; // Filtered out
}
// Transform
return {
id: product.ID,
name: product.name,
price: product.priceModel.price.value
};
};
/**
* Write chunk of processed items
*/
exports.write = function (items, parameters, stepExecution) {
for (var i = 0; i ` for any step's full parameters and defaults. **Scope** is the execution scope (Organization, Site, or both).
| Type ID | Scope | Key required params |
| --- | --- | --- |
| `ImportCatalog` | Organization | `NoFilesFoundHandling`, `ImportMode`, `ImportFailedHandling` |
| `ImportInventoryLists` | Organization | `NoFilesFoundHandling`, `ImportMode`, `ImportFailedHandling` |
| `ImportPriceBook` | Organization | `NoFilesFoundHandling`, `ImportMode`, `ImportFailedHandling` |
| `ImportContent` | Site | `NoFilesFoundHandling`, `ImportMode`, `ImportFailedHandling` |
| `ImportCustomObjects` | Organization & Sites | `NoFilesFoundHandling`, `ImportFailedHandling` |
| `ExportCatalog` | Organization | `CatalogID` |
| `ExportInventoryLists` | Site | (none) |
| `ExportPriceBook` | Organization | `PriceBookID` |
| `ExportContent` | Organization & Sites | `LibraryID` |
| `ExportOrders` | Site | `Confirmation Status`, `Shipment Status`, `Payment Status` |
| `ExecutePreconfiguredDataReplicationProcess` | Organization | `ReplicationProcessID` |
| `SearchReindex` | Site | `Indexer Type` |
| `ExecuteScriptModule` | Organization & Sites | `ExecuteScriptModule.Module` |
Import steps share a common set of file-handling parameters (`WorkingFolder`, `FileNamePattern`, `ImportMode`, `NoFilesFoundHandling`, `ImportFailedHandling`, `AfterImportFileHandling`, `ArchiveFolder`); export steps share `ExportFile` / `FileNamePrefix` / `OverwriteExportFile`. Processing steps (replication, reindex, cache invalidation, `ExecutePipeline`/`ExecuteScriptModule`/`IncludeStepsFromJob`) have their own parameters. Read any step's doc for the exact list.
### Referencing an IMPEX-staged file from a prior step
Standard import steps read from the instance **IMPEX** area. The `WorkingFolder` parameter is resolved relative to `IMPEX/src/` (and defaults to `IMPEX/src/`); `FileNamePattern` is a regex that selects which file(s) in that folder to import. This is the hand-off contract: **a step that writes a file under `IMPEX/src/...` can be followed by a standard import step that reads it** — no download/upload round-trip.
In a custom step, write to that location with `dw.io.File` using the `IMPEX` constant:
```javascript
var File = require('dw/io/File');
// Custom step writes a catalog import file into IMPEX/src/jobdata/
exports.beforeStep = function () {
var dir = new File(File.IMPEX + '/src/jobdata');
dir.mkdirs();
outputFile = new File(dir, 'catalog-' + Date.now() + '.xml');
fileWriter = new dw.io.FileWriter(outputFile);
// ... write valid catalog XML (validate against the `catalog` XSD:
// b2c docs schema catalog) ...
};
Then the standard ImportCatalog step in the next stage of the flow reads it by pointing WorkingFolder at src/jobdata (relative to IMPEX/src/ → use jobdata) with a FileNamePattern of catalog-.*\.xml.
Chaining custom + standard steps in one flow
A flow can interleave your custom steps with standard ones. Example: a custom step pulls data from an external system and generates a catalog XML in IMPEX; a standard ImportCatalog step then applies it; finally a standard replication step publishes the change to production.
jobdata
jobdata
catalog-.*\.xml
Merge
ERROR
ERROR
Archive
nightly-catalog-publish
The custom step's OutputFolder and the standard step's WorkingFolder agree on jobdata (i.e. IMPEX/src/jobdata/), so the file written in step 1 is exactly what step 2 imports; step 3 then replicates the result. (In Business Manager you build the same flow visually: add your custom step, then the standard ImportCatalog step after it, then the replication step, setting each step's parameters in its form.)
In-flow standard step vs. the CLI equivalent
Some standard steps overlap with b2c CLI commands (the CLI's b2c job import/b2c job export are themselves the sfcc-site-archive-import/-export system jobs). Choose based on where the data lives:
- Use an in-flow standard step when the file is produced or already staged on the instance — especially when an earlier step in the same flow generated it (no round-trip), when it should run on a Business Manager schedule, or when it must follow custom processing server-side. Example: the standard
ImportCatalogstep consuming a catalog XML that a prior custom step wrote to IMPEX. - Use the CLI (
b2c job import,b2c job export) when you are moving data between your machine and the instance — uploading a local archive, downloading an export, or scripting a one-off from CI.
Rule of thumb: data already on (or generated on) the instance → in-flow standard step; data crossing the machine/instance boundary → CLI. See the b2c-cli:b2c-job skill for the CLI side.
Best Practices
- Use chunk-oriented for bulk data - better progress tracking and resumability
- Close resources in
afterStep()- queries, files, connections - Set explicit timeouts - default may be too short
- Log progress - helps debugging
- Handle errors gracefully - return proper Status objects
- Don't rely on transactional=true - use
Transaction.wrap()for control
Related Skills
b2c-cli:b2c-job- For running existing jobs and importing site archives via CLIb2c-cli:b2c-docs- To look up standard job step type IDs and their parameters (b2c docs read job-steps,b2c docs read)b2c:b2c-webservices- When job steps need to call external HTTP services or APIs, use the webservices skill for service configuration and HTTP client patterns
Detailed Reference
- [Task-Oriented Steps](references/TASK-ORIENTED.md) - Full task step patterns
- [Chunk-Oriented Steps](references/CHUNK-ORIENTED.md) - Full chunk step pattern
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: SalesforceCommerceCloud
- Source: SalesforceCommerceCloud/b2c-developer-tooling
- License: Apache-2.0
- Homepage: https://salesforcecommercecloud.github.io/b2c-developer-tooling/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.