| name | b2c-controllers |
| description | Create storefront controllers using SFRA or classic patterns with server.get/post, middleware chains, and res.render/json. Use this skill whenever the user needs to build a page route, handle form submissions, create AJAX endpoints, extend or override existing controllers, or add middleware to a request pipeline. Also use when debugging route registration or response rendering -- even if they just say 'new page endpoint' or 'handle a POST request'. |
Controllers Skill
This skill guides you through creating storefront controllers for Salesforce B2C Commerce. Controllers handle HTTP requests and render responses for the storefront.
Overview
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
Two Controller Patterns
B2C Commerce supports two controller patterns:
| Pattern | When to Use | Module Style |
|---|
| SFRA | Storefront Reference Architecture sites | server module with middleware |
| Classic | Non-SFRA sites, simple APIs | Direct exports with .public = true |
SFRA is recommended for most storefront development. Classic controllers are useful for simple endpoints or non-SFRA projects.
File Location
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}
Naming: Controller filename becomes the URL prefix. Home.js handles Home-* requests.
SFRA Controllers (Recommended)
SFRA controllers use the server module for routing and middleware:
'use strict';
var server = require('server');
server.get('Show', function (req, res, next) {
res.render('home/homepage');
next();
});
server.post('Subscribe', function (req, res, next) {
var email = req.form.email;
res.json({ success: true });
next();
});
module.exports = server.exports();
Request Object (req)
req.querystring
req.form
req.httpMethod
req.httpHeaders
req.currentCustomer
req.locale
req.session
Response Object (res)
res.render('template', model)
res.json(object)
res.redirect(url)
res.setViewData(data)
res.getViewData()
res.setStatusCode(code)
Middleware
Apply middleware to routes for cross-cutting concerns:
var server = require('server');
var cache = require('*/cartridge/scripts/middleware/cache');
var consentTracking = require('*/cartridge/scripts/middleware/consentTracking');
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');
server.get('Show', cache.applyDefaultCache, function (req, res, next) {
res.render('home/homepage');
next();
});
server.post('Login', server.middleware.https, function (req, res, next) {
next();
});
server.post('Submit', csrfProtection.validateAjaxRequest, function (req, res, next) {
next();
});
Common Middleware
| Middleware | Purpose |
|---|
server.middleware.https | Require HTTPS connection |
cache.applyDefaultCache | Apply default page caching |
csrfProtection.validateAjaxRequest | Validate CSRF token |
consentTracking.consent | Check tracking consent |
userLoggedIn.validateLoggedIn | Require authenticated user |
Route Events
Execute code at specific points in the request lifecycle:
server.post('Submit', function (req, res, next) {
var form = req.form;
res.setViewData({ email: form.email });
next();
}, function (req, res, next) {
next();
});
this.on('route:BeforeComplete', function (req, res) {
var viewData = res.getViewData();
});
Extending Controllers
Extend existing controllers to add or modify functionality:
'use strict';
var server = require('server');
var page = module.superModule;
server.extend(page);
server.get('NewRoute', function (req, res, next) {
res.render('newtemplate');
next();
});
server.replace('Show', function (req, res, next) {
res.render('custom/homepage');
next();
});
server.prepend('Show', function (req, res, next) {
next();
});
server.append('Show', function (req, res, next) {
var viewData = res.getViewData();
viewData.customData = 'value';
res.setViewData(viewData);
next();
});
module.exports = server.();
Classic Controllers (Non-SFRA)
For non-SFRA sites or simple endpoints, use direct exports:
'use strict';
var ISML = require('dw/template/ISML');
exports.Show = function () {
var params = request.httpParameterMap;
var productId = params.pid.stringValue;
ISML.renderTemplate('product/detail', {
productId: productId
});
};
exports.Show.public = true;
exports.GetData = function () {
var result = { status: 'ok', data: [] };
response.setContentType('application/json');
response.writer.print(JSON.stringify(result));
};
exports.GetData.public = true;
Key difference: Classic controllers use exports.FunctionName.public = true instead of the server module.
Module Imports
Import B2C Commerce APIs using require():
var ProductMgr = require('dw/catalog/ProductMgr');
var Transaction = require('dw/system/Transaction');
var Logger = require('dw/system/Logger');
var URLUtils = require('dw/web/URLUtils');
var Resource = require('dw/web/Resource');
var collections = require('*/cartridge/scripts/util/collections');
var productHelper = require('*/cartridge/scripts/helpers/productHelpers');
Best Practice: Only require modules when needed, not all at the top of the file.
Error Handling
Wrap operations in try-catch blocks:
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();
});
Generating URLs
Use URLUtils to generate locale-aware URLs:
var URLUtils = require('dw/web/URLUtils');
var productUrl = URLUtils.url('Product-Show', 'pid', 'ABC123');
var loginUrl = URLUtils.https('Login-Show');
var imageUrl = URLUtils.staticURL('/images/logo.png');
Best Practices
- Always call
next() in SFRA middleware chain
- Use ViewModels to prepare data for templates
- Keep controllers thin - move business logic to scripts/helpers
- Use hooks for functionality that works for both storefront and OCAPI
- Handle errors gracefully - never expose stack traces
- Use
*/cartridge/... for portable module paths
Detailed Reference
For comprehensive patterns and examples: