home / skills / salesforcecommercecloud / b2c-developer-tooling / b2c-controllers

This skill guides you in creating storefront controllers for SFRA or classic B2C patterns to handle requests, render responses, and generate URLs.

npx playbooks add skill salesforcecommercecloud/b2c-developer-tooling --skill b2c-controllers

Review the files below or copy the command above to add this skill to your agents.

Files (3)
SKILL.md
8.4 KB
---
name: b2c-controllers
description: 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.
---

# 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:

```javascript
'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)

```javascript
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)

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
'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:

```javascript
'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()`:

```javascript
// 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:

```javascript
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:

```javascript
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

1. **Always call `next()`** in SFRA middleware chain
2. **Use ViewModels** to prepare data for templates
3. **Keep controllers thin** - move business logic to scripts/helpers
4. **Use hooks** for functionality that works for both storefront and OCAPI
5. **Handle errors gracefully** - never expose stack traces
6. **Use `*/cartridge/...`** for portable module paths

## Detailed Reference

For comprehensive patterns and examples:
- [SFRA Patterns](references/SFRA-PATTERNS.md) - Full SFRA patterns with middleware
- [Classic Patterns](references/CLASSIC-PATTERNS.md) - Non-SFRA controller patterns

Overview

This skill creates storefront controllers for Salesforce B2C Commerce using SFRA or classic patterns. It helps you build page handlers, form endpoints, AJAX responses, and middleware-enabled routes while supporting URL generation with URLUtils. The skill focuses on practical controller structure, request/response handling, and safe error and middleware patterns.

How this skill works

It inspects controller conventions and generates modules under a cartridge's controllers directory. For SFRA it scaffolds server.get/server.post routes with middleware chains, view data handling, res.render/res.json usage, and server.exports. For classic sites it produces direct exports with .public = true and ISML rendering. It also wires common imports like ProductMgr, Transaction, Logger, and URLUtils and recommends where to place business logic in helper scripts.

When to use it

  • Building storefront pages that render ISML templates or view models
  • Handling form submissions, AJAX calls, or JSON endpoints
  • Applying middleware like HTTPS enforcement, CSRF checks, or caching
  • Extending or overriding existing controller routes in SFRA
  • Generating locale-aware URLs or static resource links with URLUtils

Best practices

  • Prefer SFRA server module for most storefront controllers; use classic only for simple non-SFRA endpoints
  • Keep controllers thin: move business logic into helper scripts or services
  • Always call next() in SFRA middleware handlers to continue the chain
  • Validate input, wrap risky operations in try/catch, and avoid exposing stack traces
  • Use */cartridge/ paths for portable imports and require modules only when needed

Example use cases

  • Create a Home-Show route that renders the homepage with a prepared view model
  • Add a POST Subscribe endpoint that accepts form.email and returns res.json success
  • Apply cache and consent middleware to a product listing page for optimized delivery
  • Extend an existing Cart-Show route to append custom view data before render
  • Build a JSON API endpoint in a classic controller and mark it public for AJAX clients

FAQ

When should I use server.middleware.https?

Use server.middleware.https on routes that handle sensitive data or logins to force HTTPS before the handler executes.

How do I generate a locale-aware controller URL?

Use URLUtils.url('Controller-Action', 'param', value) for standard URLs or URLUtils.https/url/staticURL for HTTPS and static resources.