sfcc-cartridge-development
Included with Lifetime
$97 forever
Build SFRA-based Salesforce Commerce Cloud cartridges with controllers, ISML templates, and hooks to customize storefront behavior
platform-salesforce-ccsfccsalesforce-commerce-cloudsfracartridgeismlcontrollerspipelines
What this skill does
# SFCC Cartridge Development
## Overview
Build custom cartridges for Salesforce Commerce Cloud (SFCC) using the Storefront Reference Architecture (SFRA), server-side JavaScript controllers, ISML templates, models, and the B2C Commerce Script API. This skill covers cartridge layering and the override mechanism, route handling with `server.js`, form handling, OCAPI/SCAPI integration, and Job Framework usage for scheduled data processing.
## When to Use This Skill
- When building a custom feature cartridge that extends SFRA functionality
- When overriding or extending existing SFRA controllers, templates, or models
- When implementing custom checkout steps or payment integrations on SFCC
- When creating scheduled jobs for data import/export (product feeds, order sync)
- When building OCAPI hooks or SCAPI integrations for headless storefronts
## Core Instructions
1. **Set up the cartridge structure and layering**
SFCC uses a cartridge path for layering. Cartridges higher in the path override those lower. A custom cartridge extends `app_storefront_base`:
```
int_acme_custom/
├── cartridge/
│ ├── controllers/ # Server-side JS controllers
│ ├── models/ # Data model wrappers
│ ├── scripts/ # Business logic helpers
│ ├── templates/
│ │ └── default/ # ISML templates
│ ├── forms/
│ │ └── default/ # Form definitions (XML)
│ ├── static/
│ │ └── default/
│ │ ├── css/
│ │ └── js/
│ └── int_acme_custom.properties # Cartridge metadata
└── package.json
```
Set the cartridge path in Business Manager:
```
int_acme_custom:app_storefront_base
```
`int_acme_custom.properties`:
```properties
## cartridge.properties
demandware.cartridges.int_acme_custom.multipleLanguageStorefront=true
```
2. **Create a server-side controller**
Controllers in SFRA use `server.js` for route registration:
```javascript
// controllers/CustomPage.js
'use strict';
var server = require('server');
var cache = require('*/cartridge/scripts/middleware/cache');
var consentTracking = require('*/cartridge/scripts/middleware/consentTracking');
/**
* CustomPage-Show : Renders a custom content page
* @name CustomPage-Show
* @function
* @memberof CustomPage
* @param {middleware} - server.middleware.https
* @param {middleware} - consentTracking.consent
* @param {middleware} - cache.applyDefaultCache
* @param {querystringparameter} - cid : content asset ID
* @param {renders} - isml
* @param {serverfunction} - get
*/
server.get('Show',
server.middleware.https,
consentTracking.consent,
cache.applyDefaultCache,
function (req, res, next) {
var ContentMgr = require('dw/content/ContentMgr');
var ContentModel = require('*/cartridge/models/content');
var contentId = req.querystring.cid;
var apiContent = ContentMgr.getContent(contentId);
if (!apiContent) {
res.setStatusCode(404);
res.render('error/notFound');
return next();
}
var contentModel = new ContentModel(apiContent);
res.render('custom/contentPage', {
content: contentModel,
breadcrumbs: [
{ htmlValue: 'Home', url: '/' },
{ htmlValue: contentModel.name, url: '' }
]
});
next();
}
);
/**
* CustomPage-Submit : Handles form POST submissions
*/
server.post('Submit',
server.middleware.https,
function (req, res, next) {
var Transaction = require('dw/system/Transaction');
var CustomObjectMgr = require('dw/object/CustomObjectMgr');
var form = req.form;
var name = form.name;
var email = form.email;
// Validate input
if (!name || !email) {
res.json({ success: false, error: 'Name and email are required.' });
return next();
}
try {
Transaction.wrap(function () {
var co = CustomObjectMgr.createCustomObject('AcmeSubmissions', email);
co.custom.name = name;
co.custom.submittedAt = new Date();
});
res.json({ success: true, message: 'Submission received.' });
} catch (e) {
var Logger = require('dw/system/Logger');
Logger.error('Submission failed: {0}', e.message);
res.json({ success: false, error: 'An error occurred. Please try again.' });
}
next();
}
);
module.exports = server.exports();
```
3. **Extend an existing SFRA controller**
Use `server.extend` to add or modify routes on an existing controller:
```javascript
// controllers/Cart.js — extending app_storefront_base Cart
'use strict';
var server = require('server');
var page = module.superModule; // Reference to the base Cart controller
server.extend(page);
/**
* Cart-Show : Append custom data to the Cart page
*/
server.append('Show', function (req, res, next) {
var viewData = res.getViewData();
// Add custom upsell products to the cart page
var ProductMgr = require('dw/catalog/ProductMgr');
var ArrayList = require('dw/util/ArrayList');
var upsells = new ArrayList();
var basket = require('dw/order/BasketMgr').getCurrentBasket();
if (basket) {
var items = basket.getAllProductLineItems();
for (var i = 0; i < items.length; i++) {
var recommendations = items[i].product.getRecommendations();
for (var j = 0; j < Math.min(recommendations.length, 2); j++) {
upsells.push(recommendations[j].getRecommendedItem());
}
}
}
viewData.upsellProducts = upsells.toArray().slice(0, 4);
res.setViewData(viewData);
next();
});
/**
* Cart-AddCustomItem : New route added to the Cart controller
*/
server.post('AddCustomItem', function (req, res, next) {
var BasketMgr = require('dw/order/BasketMgr');
var Transaction = require('dw/system/Transaction');
var ProductMgr = require('dw/catalog/ProductMgr');
var productId = req.form.pid;
var quantity = parseInt(req.form.quantity, 10) || 1;
var product = ProductMgr.getProduct(productId);
if (!product || !product.isOnline()) {
res.json({ error: true, message: 'Product not available.' });
return next();
}
var basket = BasketMgr.getCurrentOrNewBasket();
Transaction.wrap(function () {
var pli = basket.createProductLineItem(productId, basket.getDefaultShipment());
pli.setQuantityValue(quantity);
});
res.json({ success: true, itemCount: basket.productQuantityTotal });
next();
});
module.exports = server.exports();
```
4. **Write ISML templates**
```html
<--- templates/default/custom/contentPage.isml --->
<isdecorate template="common/layout/page">
<isscript>
var assets = require('*/cartridge/scripts/assets');
assets.addCss('/css/custom/content.css');
assets.addJs('/js/custom/content.js');
</isscript>
<div class="container custom-content-page">
<div class="row">
<div class="col-12">
<nav aria-label="Breadcrumb">
<ol class="breadcrumb">
<isloop items="${pdict.breadcrumbs}" var="crumb" status="loopstatus">
<isif condition="${loopstatus.last}">
<li class="breadcrumb-item active">${crumb.htmlValue}</li>
<iselse/>
Related in platform-salesforce-cc
sfcc-business-manager
IncludedConfigure Salesforce Commerce Cloud via Business Manager — manage catalogs, promotions, site preferences, and run XML import/export jobs
platform-salesforce-cc
sfcc-ocapi-scapi
IncludedIntegrate with Salesforce Commerce Cloud's headless APIs (OCAPI and Shopper APIs) to build custom storefronts and mobile commerce experiences
platform-salesforce-cc