SFCC Job Development Skill
This skill guides you through creating custom job steps for Salesforce B2C Commerce batch processing.
Quick Checklist
[ ] Choose the right model: task-oriented vs chunk-oriented [ ] steptypes.json exists at the cartridge ROOT (not under cartridge/) [ ] Step script exports required functions for its step type [ ] All resources closed (iterators, file readers/writers) in afterStep [ ] Timeouts are explicit and realistic for the dataset [ ] Transactions scoped intentionally (avoid giant single transactions)
Overview
Custom job steps execute custom business logic as part of B2C Commerce jobs. 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 │ │ └── myChunkStep.js │ └── my_cartridge.properties └── steptypes.json ← Must be here, NOT inside cartridge/
Important: Only one steptypes.json per cartridge. Must be at cartridge root.
Task-Oriented Steps
Use for single operations like FTP transfers, file generation, or quick database updates.
'use strict';
var Status = require('dw/system/Status');
var Logger = require('dw/system/Logger');
exports.execute = function (parameters, stepExecution) {
var log = Logger.getLogger('job', 'MyTaskStep');
try {
var inputFile = parameters.InputFile;
if (!parameters.Enabled) {
log.info('Step disabled, skipping');
return new Status(Status.OK, 'SKIP', 'Step disabled');
}
// Your business logic here
log.info('Processing file: ' + inputFile);
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. ERROR status replaces custom codes.
Chunk-Oriented Steps
Use for bulk processing of countable data (products, orders, customers).
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 |
|---|---|
beforeStep() | Initialize (open files, queries) |
afterStep(success) | Cleanup (close files) |
getTotalCount() | Return total items for progress |
beforeChunk() | Before each chunk |
afterChunk() | After each chunk |
Script Example
'use strict';
var ProductMgr = require('dw/catalog/ProductMgr');
var Transaction = require('dw/system/Transaction');
var Logger = require('dw/system/Logger');
var log = Logger.getLogger('job', 'MyChunkStep');
var products;
exports.beforeStep = function (parameters, stepExecution) {
log.info('Starting chunk processing');
products = ProductMgr.queryAllSiteProducts();
};
exports.getTotalCount = function (parameters, stepExecution) {
return products.count; // Use built-in count!
};
exports.read = function (parameters, stepExecution) {
if (products.hasNext()) {
return products.next();
}
// Return nothing = end of data
};
exports.process = function (product, parameters, stepExecution) {
if (!product.online) {
return; // Filtered out
}
return {
id: product.ID,
name: product.name,
price: product.priceModel.price.value
};
};
exports.write = function (items, parameters, stepExecution) {
for (var i = 0; i < items.size(); i++) {
var item = items.get(i);
// Write item...
}
};
exports.afterStep = function (success, parameters, stepExecution) {
if (products) {
products.close(); // Critical: close iterators!
}
log.info(success ? 'Completed successfully' : 'Failed');
};
Important: Chunk-oriented steps always finish with either OK or ERROR. Custom exit status codes are not supported.
steptypes.json Configuration
Task-Oriented Step
{
"step-types": {
"script-module-step": [
{
"@type-id": "custom.MyTaskStep",
"@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"
}
]
},
"status-codes": {
"status": [
{ "@code": "OK", "description": "Step completed" },
{ "@code": "ERROR", "description": "Step failed" }
]
}
}
]
}
}
Chunk-Oriented Step
{
"step-types": {
"chunk-script-module-step": [
{
"@type-id": "custom.MyChunkStep",
"@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
}
]
}
}
Key Configuration Attributes
| Attribute | Required | Description |
|---|---|---|
@type-id | Yes | Unique ID (must start with custom.) |
@supports-site-context | No | Available in site-scoped jobs |
@supports-organization-context | No | Available in org-scoped jobs |
module | Yes | Path to script module |
function | Yes | Function name (task-oriented) |
chunk-size | Yes* | Items per chunk (*chunk steps only) |
timeout-in-seconds | No | Step timeout (recommended) |
Context Constraints: @supports-site-context and @supports-organization-context cannot both be true or both be false.
Parameter Types
| Type | Description | Example |
|---|---|---|
string | Text value | "my-value" |
boolean | true/false | true |
long | Integer | 12345 |
double | Decimal | 123.45 |
Critical Performance Pattern
Use SeekableIterator's built-in count instead of creating separate iterators:
// ✅ CORRECT - Uses built-in count
exports.getTotalCount = function() {
return products.getCount(); // Instant, no DB hit
}
// ❌ WRONG - Creates duplicate query!
exports.getTotalCount = function() {
var counter = ProductMgr.queryAllSiteProducts(); // Unnecessary!
var count = 0;
while (counter.hasNext()) { counter.next(); count++; }
counter.close();
return count;
}
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 - •Use streaming APIs for file processing
- •Avoid accumulating objects in global scope
Chunk Size Guidelines
| Operation Type | Recommended Size |
|---|---|
| Simple attribute updates | 500-1000 |
| Complex object creation | 100-300 |
| File I/O operations | 200-500 |
| General operations | 250 |
How to Choose a Chunk Size (Beyond Rules of Thumb)
- •Processing complexity: complex per-record logic (extra lookups, multiple writes) usually needs smaller chunks.
- •Memory behavior: if memory grows with chunk size, shrink chunks and ensure references are released after
write. - •Optimistic locking risk: for “high contention” objects (e.g., profiles that can be updated by storefront traffic), smaller chunks reduce the blast radius of failures and make retries cheaper.
transactional Setting Guidance
Keep "transactional": false in steptypes.json unless you have a very specific requirement.
- •When
transactionalis enabled, the platform can treat the step as one large transaction, increasing rollback risk on errors. - •Prefer explicit, local
Transaction.wrap()scopes insideprocessorwriteto control what is committed and when.
Troubleshooting
| Issue | Solution |
|---|---|
| OutOfMemoryError | Use streaming APIs, close iterators, reduce chunk size |
| ScriptingTimeoutError | Use chunk-oriented model, review algorithm |
| Transaction timeouts | Reduce chunk size, commit per chunk |
| Job not visible in BM | Check code version is active |
Related Skills
- •sfcc-logging - Comprehensive logging patterns, custom log files, NDC tracing. Essential for job debugging and monitoring.
Detailed References
- •Task-Oriented Steps - Full task step patterns
- •Chunk-Oriented Steps - Full chunk step patterns
- •steptypes.json Reference - Complete schema
External reading:
- •Rhino Inquisitor: Mastering Chunk-Oriented Job Steps
- •Salesforce Docs: Custom job steps (chunk-oriented)