Claude
Skills
Sign in
Back

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