Customize SFRA

Storefront Reference Architecture (SFRA) is extensible. Customize SFRA without editing the app_storefront_base cartridge or other plug-ins, such as plugin_applepay. The extensible design lets you more easily take security updates, bug fixes, and adopt new features.

Only existing customers can access some of the links on this page. Visit Salesforce Commerce Cloud GitHub Repositories and Access for information about how to get access to the repositories.

Tip

Add Custom Cartridges 

Implementing a site requires at least one custom cartridge. However, if you intend to create multiple sites, we suggest you create multiple custom cartridges. Each cartridge can separate functionality specific to a brand or locale, so that you can reuse most of your cartridge stack for a new site.

All available LINK and plug-in cartridges are on GitHub. Plug-in cartridges are in the SFRA project in GitHub. Include link and plug-in cartridges on your cartridge path and base path as you would any other cartridge in your stack.

Don’t rename the app_storefront_base cartridge or other provided plug-ins. Keep your version of the app_storefront_base cartridge and other plug-ins up-to-date. By doing so, you make sure that you have the latest bug fixes and performance improvements.

Note

Adding Custom Cartridges When sgmf-scripts Is Globally Installed 

If you globally installed sgmf-scripts through npm, follow these steps to add a custom cartridge:

  1. Create a folder for your project. For example:

    1mkdir mysite
  2. Navigate into the folder.

    1cd mysite
  3. Install the sgmf-scripts node.

    1npm install sgmf-scripts
  4. Use the sgmf-scripts --createCartridge command.

    1sgmf-scripts --createCartridge app_custom_storefront

    These directories and files are created in your folder:

    1├── cartridges
    2    └── app_custom_mybrand
    3        └── cartridge
    4               ...
    5├── dw.json
    6├── node_modules
    7└── package.json
  5. Install the dependencies required by the new cartridge:

    1npm install

To see all the available SFRA-commands, enter sgmf-scripts --help.

Note

Adding Custom Cartridges When sgmf-scripts Isn't Globally Installed 

If you want to avoid globally installing sgmf-scripts, follow these steps to add a custom cartridge:

  1. Create a folder for your project. For example: mysite.

    1mkdir mysite
  2. Navigate into the folder.

    1cd mysite
  3. Install the sgmf-scripts node via npm.

    1npm install sgmf-scripts
  4. Use Node.js to call the sgmf-scripts createCartridges command.

    1node node_modules/sgmf-scripts --createCartridge app_custom_mybrand

    The following directories and files are created in your folder:

    1├── cartridges
    2    └── app_custom_mybrand
    3        └── cartridge
    4               ...
    5├── dw.json
    6├── node_modules
    7└── package.json
  5. Install the dependencies required by the new cartridge:

    1npm install

To see all the available SFRA-commands, enter sgmf-scripts --help.

Note

Updating Your package.json 

Because you’re not modifying the app_storefront_base cartridge directly, Salesforce B2C Commerce provides a mechanism to selectively override CSS styles and client-side JavaScript. In the package.json file, the paths property lists every cartridge with CSS and client-side JavaScript functionality customized in your site. When building your cartridge stack, the paths property lets you import functionality from CSS and JavaScript files in other cartridges and selectively override it. When you use the command-line compile tool, it compiles the JavaScript and CSS across cartridges.

  1. Navigate to the top level of your custom cartridge. For example, mysite.

  2. Open the package.json file and modify the paths property. The paths.base property points to the local directory containing app_storefront_base. Add properties for the cartridges you want to import functionality from.

    Example: adding plugin_ratings, plugin_reviews, and app_storefront_base to a custom cartridge:

    1"paths": {
    2    "base": "../storefront-reference-architecture/cartridges/app_storefront_base/",
    3    "ratings": "../plugin_ratings/cartridges/plugin_ratings/",
    4    "reviews": "../plugin_reviews/cartridges/plugin_reviews/",
    5    "applepay": "../plugin-applepay/cartridges/plugin_applepay/"
    6  }
    7}
  3. Import the CSS files you want to include and override into the my_repository/cartridges/my_cartridge/cartridge/client/default/myfile.scss file.

    The following sample global .scss file inherits most of its styles from the base cartridge. The location of the base cartridge is defined in package.json. This example uses the base property defined in the package.json to import style sheets.

    1@charset "UTF-8";
    2
    3@import "~base/variables";
    4@import "bootstrap/scss/bootstrap";
    5@import "~base/bootstrap_overrides";
    6@import "~base/utilities/responsiveUtils";
    7@import "font-awesome/scss/font-awesome";
    8@import "flag-icon";
    9@import "components/menu";
    10@import "~base/components/common";
    11@import "~base/components/footer";
    12@import "components/footer";
    13@import "~base/components/hero";
    14@import "~base/components/notification";
    15@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro&subset=latin-ext);
    16
    17body {
    18    font-family: 'Source Sans Pro', sans-serif;
    19}
    20
    21.modal-background {
    22    display: none !important;
    23}
  4. Use the path object to include JavaScript modules from other cartridges when you want to inherit and override client-side JavaScript functionality.

    The following example uses the path described by the base property to require in a client-side script in the base cartridge.

    1'use strict';
    2var base = require('../base/product/base');

After Creating Your Custom Cartridge 

After you create your custom cartridge, refer to Create and Manage Sites for Developmen

Best Practices When Creating Custom Cartridges 

  • Create unit tests for models.
  • Check for possible naming collisions between cartridges.

Anti-patterns:

  • Don’t override pdict variables.
  • Don’t delete properties off the pdict.
  • Don’t replace something in pdict with something else with a different signature.

Build Your Cartridge Stack 

Each cartridge is built independently, uploaded, and then added to the cartridge stack. Change and upload cartridges independently, as long as you develop against same code version.

The command-line tools are designed to work in the top-level folder of each plug-in or cartridge repository. For example, watch only works for the directory in which you start it.

Cartridges provided by B2C Commerce have to be built only once for each version and uploaded. Create a separate build script for routine maintenance of cartridges you update and upload. In contrast, create a lightweight script to dynamically build and upload just the cartridges you’re currently working on.

Customize Templates 

The system searches the cartridge path for the first template that matches the specified name and location. To override a template, create a custom cartridge. In the custom cartridge, include a template with the same name as the template you want to override.

The location of your template in your cartridge must match the location of the original template in the original cartridge. Finally, add your custom cartridge to the cartridge path, placing your cartridge to the left of the original cartridge.

Decorator Templates in SFRA

There are only two decorator templates:

  • page.isml: Contains navigation information
  • checkout.isml: Doesn’t contain navigation information. Removing navigation information has been shown to improve the percentage of cart abandonment.

These templates are located in app_storefront_base/cartridge/templates/default/common/layout/.

All of the pt_..._VARS and pt_..._UI templates that existed in previous versions of SiteGenesis were removed.

Note

The navigation for a page is set through the new decorator templates. Client-side scripts and CSS files are set for each template using the assets.addJs and assets.addCss functions for each template. The top of every page template contains code similar to the following:

1<isdecorate template="common/layout/page">
2    <isscript>
3        var assets = require('~/cartridge/scripts/assets.js');
4        assets.addCss('/css/cart.css');
5    </isscript>

Put _.css files and _.js files with the same names and in the same locations as indicated in the original template. This strategy overrides any like-named _.css files and _.js files in any cartridge to the right of the current cartridge on the cartridge path.

Overriding CSS and Client-Side JavaScript for a Page 

Override CSS and client-side JavaScript for a specific template. For example, if the template declares the following assets:

1<isdecorate template="common/layout/page">
2
3    <isscript>
4        var assets = require('*/cartridge/scripts/assets');
5        assets.addJs('/js/productDetail.js');
6        assets.addCss('/css/product/detail.css');
7    </isscript>

Create a productDetail.js and detail.css and place them in the same location as indicated in the template. The platform uses the first template with the correct name and location that it finds on the cartridge path.

When creating the CSS file you want to customize, create a file with the same name and location as the file you ‘re customizing. In your file, import the original file.

Example: Customizing Detail.Scss.

1@import "base/product/detail";
2
3.description-and-detail {
4    margin-top: 2em;
5
6    .details {
7
8        @include media-breakpoint-up(sm) {
9            margin-bottom: 2em;
10        }
11    }
12}

Best Practices with ISML 

Usually, business logic isn’t included in app_storefront_base templates. Any logic in the templates is specific to rendering. The app_storefront_base cartridge demonstrates recommended strategies for using ISML:

  • iscache: Set by default to 24 hours. We recommend changing this value in the controller.
  • isset: Only used to set a variable used by isinclude. Don’t use complex expressions in the isset.
  • isscript: Only used to add client-side JavaScript/CSS to the page using the asset module.
  • ismodule: Only used for content assets.
  • iscontent: Change this value in the controller.
  • iscookie: Change this value in the controller.
  • isredirect: Change this value in the controller.
  • isstatus: Change this value in the controller.
  • isobject: Used to wrap products for analytics and the Storefront Toolkit.

Customize Models 

Models in SFRA provide a JSON object layer for the application. The models convert objects returned by the B2C Commerce script APIs into pure JSON objects designed for the storefront. The models also apply business logic for the storefront.

A model passed in for rendering a template is assigned to the viewData variable. Add extra properties to the viewData variable — properties that are not provided by the app_storefront_base cartridge.

Controllers create and update models. To customize a model, you create the model and then add data to it that can be used for rendering.

Anything in the modules folder or the toplevel folder is globally available and extendable.

Customize models to:

  • Extend the model to include more data to be used when rendering the template
  • Add extra models with objects used to render templates
  • Change models to be used to render templates in a different framework

Customize SFRA Controllers and Routes 

Customize SFRA controllers and routes to work for your own storefront.

Inheriting Functionality from Another Controller and Extending It 

It’s important to understand when to extend a controller and when to override it, because this decision can significantly impact functionality and performance.

  • When do I want to Override?

    It’s best to override if you want to avoid executing the middleware of the controller or script you’re modifying.

    When extending a controller, you first execute the original middleware, and then execute the additional steps included in your extension. If the original middleware steps include interaction with a third-party system, that interaction is still executed. If your extension also includes the interaction, the interaction is executed twice. Similarly, if the original middleware includes one or more steps that are computationally expensive, you can avoid executing the original middleware.

  • When do I want to Extend?

    If the middleware you want to override is looking up a string or performing inexpensive operations, extend the controller or module.

  • How do I extend or Override?

    Use the module.superModule mechanism to import the functionality from a controller and then override or add to it.

Example: Adding Product Reviews to Product.Js 

This example customizes the product detail page to include product review information. The code for this example is available in the Plugin_reviews demo cartridge.

In this example, the Product.js controller uses the following APIs for customization:

  • module.superModule: Imports functionality from the first controller with the same name and location found to the right of the current cartridge on the cartridge path.
  • server.extend: Inherits the existing server object and extends it with a list of new routes from the super module. In this case, it adds the routes from the module.superModule Project.js file.
  • server.append: Modifies the Show route by appending middleware that adds properties to the viewData object for rendering. Using server.append causes a route to execute both the original middleware chain and any additional steps. If you’re interacting with a web service or third-party system, don’t use server.append.
  • res.getViewData: Gets the current viewData object from the response object.
  • res.setViewData: Updates the viewData object used for rendering the template. Create your customized template in the same location and with the same name as the template rendered by the superModule controller. For example, if this controller inherits functionality from app_storefront_base, the rendering template depends on the product type being rendered. The rendering template can be either product/productDetails, product/bundleDetails, or product/setDetails.
1// Product.js
2
3'use strict';
4
5var server = require('server');
6var page = module.superModule;        //inherits functionality from next Product.js found to the right on the cartridge path
7server.extend(page);                  //extends existing server object with a list of new routes from the Product.js found by module.superModule
8
9server.append('Show', function (req, res, next) { //adds additional middleware
10    var viewData = res.getViewData();
11    viewData.product.reviews = [{
12        text: 'Lorem ipsum dolor sit amet, cibo utroque ne vis, has no sumo graece.' +
13          ' Dicta persius his id. Ea maluisset scripserit contentiones quo, est ne movet dicam.' +
14          ' Equidem scriptorem vis no. Civibus tacimates interpretaris has et,' +
15          ' ei offendit ocurreret vis, eos purto pertinax eleifend ea.',
16        rating: 3.5
17    }, {
18        text: 'Very short review',
19        rating: 5
20    }, {
21        text: 'Lorem ipsum dolor sit amet, cibo utroque ne vis, has no sumo graece.',
22        rating: 1.5
23    }];
24
25    res.setViewData(viewData);
26    next();
27});
28
29module.exports = server.exports();

Replacing or Adding a Route 

If you want to completely replace a route, rather than append it, use module.superModule to inherit the functionality of the controller and route you want to replace. Then register the functions you want the route to use.

Example: replacing the Product-Varation route

In your custom cartridge, create a Product.js file in the same location as the Product.js file in the base cartridge. Use the following code to import the functionality of Product.js and redefine it.

1var page = require('app_storefront_base/cartridge/controller/Product');
2var server = require('server);
3
4server.extend(page);
5
6server.replace('Show', server.middleware.get, function(req, res, next){
7    res.render('myNewTemplate');
8    next();
9});

Overriding Instead of Replacing a Step in the Middleware Chain 

We recommend that you replace a route to change individual steps in a middleware chain.

You can’t change a step in the middleware chain. However, you can either replace the whole route or append a new step after the chain completes, but before the template renders. Usually, if you’re appending a step, it’s to override or add data to the ViewData object.

Be careful when appending existing routes. If you append a step, the existing middleware chain executes no matter what the appended step does. Therefore, it’s possible to execute the route twice. If the route calls a web service or updates a third-party system, such as an inventory management system, it can take the same action twice.

Important

Information about middleware filtering classes is available in the SFRA JSDoc in the server-side documentation for Global.

Note

Changing the Access on an Existing Route 

Use the middleware functions provided by B2C Commerce or create your own. We recommend that you replace a route when changing access.

These middleware filtering functions are provided by B2C Commerce:

  • get: Filter for get requests
  • htt: Filter for http requests
  • https:Filter for https requests
  • include: Filter for remote includes
  • post: Filter for post requests

If the request doesn’t match the filtering condition, the function returns an Error with the text Params do not match route.

Customizing a Controller with a Web Service or a Call to a Third Party 

In general, customizing a controller with a web service or third-party call uses the same mechanisms as other types of customizations. However, it’s important to make sure that you don’t execute the controller twice. Executing the controller twice is possible if you use server.append to customize the ViewData object passed to the rendering template. Instead, simply replace the route entirely.