Agent skill
b2c-controllers
Create storefront controllers in SFRA or classic B2C Commerce patterns. Use when building pages, handling form submissions, creating AJAX endpoints, or working with server.get/server.post, res.render, res.json, and middleware chains. Also covers URLUtils for URL generation.
Install this agent skill to your Project
npx add-skill https://github.com/SalesforceCommerceCloud/b2c-developer-tooling/tree/main/skills/b2c/skills/b2c-controllers
SKILL.md
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');
// 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;
// Process subscription...
res.json({ success: true });
next();
});
module.exports = server.exports();
Request Object (req)
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 object
Response Object (res)
res.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
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');
// Apply caching
server.get('Show', cache.applyDefaultCache, function (req, res, next) {
res.render('home/homepage');
next();
});
// Require HTTPS
server.post('Login', server.middleware.https, function (req, res, next) {
// Handle login...
next();
});
// CSRF protection for forms
server.post('Submit', csrfProtection.validateAjaxRequest, function (req, res, next) {
// Handle form submission...
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) {
// Additional middleware
next();
});
// Execute after all middleware, before render
this.on('route:BeforeComplete', function (req, res) {
var viewData = res.getViewData();
// Modify view data if needed
});
Extending Controllers
Extend existing controllers to add or modify functionality:
'use strict';
var server = require('server');
var page = module.superModule; // Get parent controller
server.extend(page);
// Add new route
server.get('NewRoute', function (req, res, next) {
res.render('newtemplate');
next();
});
// Override existing route
server.replace('Show', function (req, res, next) {
// Custom implementation
res.render('custom/homepage');
next();
});
// Prepend to existing route
server.prepend('Show', function (req, res, next) {
// Runs before original handler
next();
});
// Append to existing route
server.append('Show', function (req, res, next) {
// Runs after original handler
var viewData = res.getViewData();
viewData.customData = 'value';
res.setViewData(viewData);
next();
});
module.exports = server.exports();
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; // Required: marks function as accessible
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():
// 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');
var Resource = require('dw/web/Resource');
// Cartridge modules (use */ for cartridge path resolution)
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');
// Controller URL
var productUrl = URLUtils.url('Product-Show', 'pid', 'ABC123');
// Result: /on/demandware.store/Sites-RefArch-Site/en_US/Product-Show?pid=ABC123
// HTTPS URL
var loginUrl = URLUtils.https('Login-Show');
// Static resource URL
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:
- SFRA Patterns - Full SFRA patterns with middleware
- Classic Patterns - Non-SFRA controller patterns
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
api-client-development
Creating typed API clients with OpenAPI specs, authentication, and OAuth scopes for SCAPI and similar APIs. Use when adding a new SCAPI client, generating types from an OpenAPI spec, setting up OAuth middleware, or integrating a new Commerce API endpoint.
testing
Writing tests for the B2C CLI project using Mocha, Chai, and MSW. Use when writing unit or integration tests, mocking HTTP requests with MSW, testing CLI commands with oclif, isolating config in tests, setting up test coverage, using sinon stubs, c8 coverage, capturing or silencing stdout, or creating MSW handlers.
documentation
Updating user guides, CLI reference, and API documentation for the B2C CLI project. Use when adding or changing CLI command docs, writing JSDoc for TypeDoc generation, updating Vitepress sidebar config, or creating new guide pages.
cli-command-development
Creating new CLI commands and topics for the B2C CLI using oclif. Use when adding a new command, creating a topic, adding flags or arguments, implementing table output, or extending BaseCommand/OAuthCommand/InstanceCommand.
sdk-module-development
Adding new modules and exports to the @salesforce/b2c-tooling-sdk package. Use when creating a new SDK module, adding barrel file exports, configuring package.json exports, or building client factory functions.
b2c-code
Deploy and manage code versions/cartridges on B2C Commerce instances/sandboxes with the b2c cli. Always reference when using the CLI to upload cartridges, deploy code, activate code versions, manage code versions, or watch for file changes during development.
Didn't find tool you were looking for?