sfcc-sfra-controllers — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited sfcc-sfra-controllers (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill guides you through creating storefront controllers for Salesforce B2C Commerce using the Storefront Reference Architecture (SFRA).
Controllers are JavaScript modules that handle storefront requests. A controller URL has this structure:
https://{domain}/on/demandware.store/Sites-{SiteName}-Site/{locale}/{ControllerName}-{FunctionName}Example: https://example.com/on/demandware.store/Sites-RefArch-Site/en_US/Home-Show
SFRA uses Model-View-Controller pattern:
/controllers): Handles requests, calls models, determines view/models): Business logic, returns JSON objects/templates): ISML templates, renders pdict data onlyCartridge Path: Custom cartridges must precede app_storefront_base to override functionality. Never edit the base cartridge directly.
Controllers reside in the cartridge's controllers directory:
/my-cartridge
/cartridge
/controllers
Home.js # URL: Home-{function}
Product.js # URL: Product-{function}
Cart.js # URL: Cart-{function}'use strict';
var server = require('server');
// Handle GET request
server.get('Show', function (req, res, next) {
res.render('home/homepage');
next();
});
// Handle POST request
server.post('Subscribe', function (req, res, next) {
var email = req.form.email;
res.json({ success: true });
next();
});
module.exports = server.exports();req.querystring // Query parameters: ?q=shoes -> req.querystring.q
req.form // Form POST data: req.form.email
req.httpMethod // HTTP method: 'GET', 'POST', etc.
req.httpHeaders // Request headers
req.currentCustomer // Current customer object
req.locale // Current locale
req.session // Session objectres.render('template', model) // Render ISML template with data
res.json(object) // Return JSON response
res.redirect(url) // Redirect to URL
res.setViewData(data) // Add data to view model
res.getViewData() // Get current view model
res.setStatusCode(code) // Set HTTP status code
res.cacheExpiration(hours) // Set cache durationEach route is a series of functions with (req, res, next):
server.get('Show',
middleware1,
middleware2,
function (req, res, next) {
// Main handler
next();
}
);Critical: Always call next() to pass control, unless terminating the request.
| Middleware | Purpose |
|---|---|
server.middleware.https | Require HTTPS connection |
server.middleware.include | Mark as remote include only |
cache.applyDefaultCache | Apply 24-hour page caching |
csrfProtection.generateToken | Generate CSRF token |
csrfProtection.validateRequest | Validate CSRF for POST |
userLoggedIn.validateLoggedIn | Require authenticated user |
consentTracking.consent | Check tracking consent |
Customize without modifying base code. Always import base first:
'use strict';
var server = require('server');
var page = module.superModule; // Get base controller
server.extend(page);| Method | Use Case |
|---|---|
server.append() | Add/modify data after base logic (most common) |
server.prepend() | Run validation before base logic |
server.replace() | Completely replace route behavior |
server.append('Show', function (req, res, next) {
var viewData = res.getViewData();
viewData.customData = 'My custom value';
res.setViewData(viewData);
next();
});
module.exports = server.exports();// Use replace for payment, inventory updates, etc.
server.replace('Submit', function (req, res, next) {
// Custom implementation
res.json({ success: true });
next();
});Critical: Never use append for state-changing routes (payments, inventory). Use replace to prevent duplicate transactions.
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');
// Display form - generates token automatically
server.get('ShowForm', csrfProtection.generateToken, function (req, res, next) {
res.render('myForm', { pageTitle: 'Contact' });
// pdict.csrf.tokenName and pdict.csrf.token auto-available
next();
});
// Process form - validates token
server.post('Submit', csrfProtection.validateRequest, function (req, res, next) {
// Token validated, safe to process
next();
});Important: Middleware automatically adds CSRF to pdict. Never manually add tokens to viewData.
Template usage:
<input type="hidden" name="${pdict.csrf.tokenName}" value="${pdict.csrf.token}"/>server.get('Show', function (req, res, next) {
try {
var product = ProductMgr.getProduct(req.querystring.pid);
if (!product) {
res.setStatusCode(404);
res.render('error/notfound');
return next();
}
res.render('product/detail', { product: product });
} catch (e) {
Logger.error('Product error: ' + e.message);
res.setStatusCode(500);
res.render('error/general');
}
next();
});// B2C Commerce APIs
var ProductMgr = require('dw/catalog/ProductMgr');
var Transaction = require('dw/system/Transaction');
var Logger = require('dw/system/Logger');
var URLUtils = require('dw/web/URLUtils');
// Cartridge modules (use */ for path resolution)
var collections = require('*/cartridge/scripts/util/collections');
var productHelper = require('*/cartridge/scripts/helpers/productHelpers');Best Practice: Require modules inside route functions, not at file top.
var URLUtils = require('dw/web/URLUtils');
// Controller URL
var productUrl = URLUtils.url('Product-Show', 'pid', 'ABC123');
// HTTPS URL
var loginUrl = URLUtils.https('Login-Show');
// Static resource URL
var imageUrl = URLUtils.staticURL('/images/logo.png');Execute code at specific points in request lifecycle:
server.get('Show', function (req, res, next) {
res.setViewData({ email: req.form.email });
next();
});
// Execute after all middleware, before render
this.on('route:BeforeComplete', function (req, res) {
var viewData = res.getViewData();
// Modify view data if needed
});server.get('GetCart', function (req, res, next) {
var BasketMgr = require('dw/order/BasketMgr');
var CartModel = require('*/cartridge/models/cart');
var basket = BasketMgr.getCurrentBasket();
var cart = new CartModel(basket);
res.json(cart);
next();
});'use strict';
var server = require('server');
var cache = require('*/cartridge/scripts/middleware/cache');
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');
var userLoggedIn = require('*/cartridge/scripts/middleware/userLoggedIn');
// Public page with caching
server.get('Show',
cache.applyDefaultCache,
function (req, res, next) {
var ProductMgr = require('dw/catalog/ProductMgr');
var product = ProductMgr.getProduct(req.querystring.pid);
res.render('product/detail', { product: product });
next();
}
);
// Protected form display
server.get('EditProfile',
server.middleware.https,
userLoggedIn.validateLoggedIn,
csrfProtection.generateToken,
function (req, res, next) {
res.render('account/editProfile');
next();
}
);
// Protected form submission
server.post('SaveProfile',
server.middleware.https,
userLoggedIn.validateLoggedIn,
csrfProtection.validateRequest,
function (req, res, next) {
// Process form...
res.json({ success: true });
next();
}
);
module.exports = server.exports();res.cacheExpiration()Resource.msg() for all user-facing messages, error strings, and JSON responses. Never hardcode text in controllers.For comprehensive patterns and examples:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.