| name | sfcc-isml-development |
| description | SFRA-first guide for developing ISML templates in Salesforce B2C Commerce (Bootstrap 4 conventions). Use this when creating, modifying, or troubleshooting SFRA templates, decorators, components, forms, includes, and caching. |
ISML Development (SFRA + Bootstrap 4)
This skill is SFRA-only. It intentionally avoids SiteGenesis patterns.
SFRA storefront markup is built around Bootstrap 4 (grid, forms, alerts, utilities). Keep your ISML consistent with existing SFRA markup and CSS conventions.
When to Use
- You’re editing
templates/default/** in an SFRA cartridge.
- You need to apply/extend SFRA layout (
common/layout/page / common/layout/checkout).
- You’re building reusable components via
<isinclude> and Bootstrap 4 markup.
- You’re debugging output encoding, caching, or remote include behaviour.
When NOT to Use
- For business logic, data fetching, persistence, or complex transformations (belongs in controllers/models/scripts).
- For redesigning UI with a different framework (SFRA baseline expects Bootstrap 4).
Quick Checklist
[ ] No business logic in ISML (no data fetches, no persistence)
[ ] <isscript> used only for SFRA asset registration (CSS/JS)
[ ] Output encoding is NOT disabled unless justified and safe
[ ] <iscontent>/<isredirect>/<iscache> placement constraints respected
[ ] Remote includes used sparingly with security middleware
[ ] Template receives all data via pdict (controller owns data)
[ ] Markup uses Bootstrap 4 classes (grid, forms, alerts)
Where Templates Live (SFRA)
/my-cartridge/cartridge/templates/
/default # Default templates (SFRA)
/product/detail.isml
/util/modules.isml # Optional: <ismodule> tag definitions
/fr_FR # Locale overrides (use sparingly)
SFRA Golden Rule: Templates Are Presentation-Only
NEVER use <isscript> for business logic. The only exception is asset management:
<isscript>
var assets = require('*/cartridge/scripts/assets.js');
assets.addCss('/css/product.css');
assets.addJs('/js/product.js');
</isscript>
All data comes from controllers via pdict:
<div class="price h5 mb-0">${pdict.product.price.sales.formatted}</div>
Bootstrap 4 Conventions (SFRA UI Baseline)
Use these classes/patterns by default:
| Goal | Bootstrap 4 pattern |
|---|
| Layout | container → row → col-* |
| Spacing | mt-*, mb-*, py-*, px-* |
| Buttons | btn btn-primary, btn btn-outline-primary |
| Alerts | alert alert-danger, alert alert-success |
| Forms | form-group, form-control, is-invalid, invalid-feedback |
| Responsive show/hide | d-none d-md-block, d-md-none |
Avoid mixing Bootstrap 5-only classes (e.g. g-*, row-cols-*, btn-close, badge bg-*) into SFRA baseline templates.
Essential Tags
Conditional Logic
<isif condition="${pdict.product.available}">
<span class="badge badge-success">${Resource.msg('label.instock','product',null)}</span>
<iselseif condition="${pdict.product.preorderable}">
<span class="badge badge-info">${Resource.msg('label.preorder','product',null)}</span>
<iselse>
<span class="badge badge-secondary">${Resource.msg('label.outofstock','product',null)}</span>
</isif>
Loops
<isloop items="${products}" var="product" status="loopstate">
<div class="col-6 col-sm-4 col-lg-3 mb-3">
<div class="card h-100">
<div class="card-body">
<div class="small text-muted">#${loopstate.count}</div>
<div class="font-weight-bold">${product.name}</div>
</div>
<isif condition="${loopstate.first}">
<div class="card-footer bg-transparent">
<span class="badge badge-warning">${Resource.msg('label.featured','common',null)}</span>
</div>
</isif>
</div>
</div>
</isloop>
Loop status: count (1-based), index (0-based), first, last, odd, even
Variables
<isset name="productName" value="${product.name}" scope="page"/>
<span>${productName}</span>
<isremove name="productName" scope="page"/>
Scopes (required): page, request, session, pdict
Output
<isprint value="${product.name}"/>
<isprint value="${htmlContent}" encoding="off"/>
<isprint value="${price}" style="CURRENCY"/>
<isprint value="${order.creationDate}" style="DATE_SHORT"/>
Includes
<isinclude template="product/components/price"/>
<isinclude url="${URLUtils.url('Product-GetPrice', 'pid', product.ID)}"/>
Prefer local includes for components. Only use remote includes when you need an independent cache policy or request isolation; see the remote include reference.
Decorator Pattern
Decorator template (common/layout/page.isml):
<!DOCTYPE html>
<html>
<head><title>${pdict.pageTitle}</title></head>
<body>
<isinclude template="components/header"/>
<main>
<isreplace/>
</main>
<isinclude template="components/footer"/>
</body>
</html>
Using decorator:
<isdecorate template="common/layout/page">
<div class="content">
<h1>${pdict.welcomeMessage}</h1>
</div>
</isdecorate>
SFRA provides two primary decorators:
common/layout/page - Standard storefront pages
common/layout/checkout - Checkout process pages
Use <isdecorate> on almost all storefront templates; keep full <html>/<head>/<body> only in layout templates.
Tag Location Constraints
| Tag | Constraint |
|---|
<iscontent> | Must be before DOCTYPE |
<isredirect> | Must be before DOCTYPE |
<iscache> | Place at beginning |
<isreplace/> | Only inside decorator templates |
<isbreak>/<iscontinue>/<isnext> | Only inside <isloop> |
Caching
<iscache type="relative" hour="24"/>
<iscache type="daily" hour="0" minute="0"/>
<iscache type="relative" hour="1" varyby="price_promotion"/>
Place <iscache> at the beginning of the template.
Prefer controller caching middleware (SFRA */cartridge/scripts/middleware/cache) for page-level caching decisions; use <iscache> selectively for fragments.
Content Type
<iscontent type="text/html" charset="UTF-8"/>
<iscontent type="application/json" charset="UTF-8"/>
Custom Modules
Define in util/modules.isml:
<ismodule template="components/productcard"
name="productcard"
attribute="product"
attribute="showPrice"/>
Use anywhere:
<isinclude template="util/modules"/>
<isproductcard product="${product}" showPrice="${true}"/>
In SFRA, most reuse is done via <isinclude template="...">. Use <ismodule> custom tags only when it measurably improves readability/consistency.
Security: XSS Prevention
Always rely on default encoding. <isprint> automatically HTML-encodes:
<isprint value="${pdict.searchPhrase}" />
<isprint value="${pdict.searchPhrase}" encoding="off" />
For JavaScript context, use SecureEncoder:
<script>
var term = "${require('dw/util/SecureEncoder').forJavaScript(pdict.searchPhrase)}";
</script>
For HTML attributes, prefer SecureEncoder.forHTMLAttribute(...). Avoid encoding="off" unless the value is fully controlled and intentionally contains HTML.
Built-in Utilities
Pre-imported, use directly:
<a href="${URLUtils.url('Product-Show', 'pid', product.ID)}">View</a>
<img src="${URLUtils.staticURL('/images/logo.png')}" alt="Logo"/>
${Resource.msg('button.addtocart', 'product', null)}
${Resource.msgf('cart.items', 'cart', null, cartCount)}
${StringUtils.truncate(description, 100, '...')}
Expressions
${pdict.product.name}
${pdict.product.price.sales.value}
${product.getAvailabilityModel().isInStock()}
${price > 100 ? 'expensive' : 'affordable'}
${firstName + ' ' + lastName}
Comments
<iscomment>
ISML comment - stripped from output.
Use for sensitive documentation.
</iscomment>
Best Practices
- Use Bootstrap 4 components/utilities for UI consistency
- Use
<isdecorate> with SFRA layouts (common/layout/page, common/layout/checkout)
- Keep templates “dumb”: controllers/models prepare view models; templates render
- Use
<isinclude template> for component composition; keep components small
- Default encoding prevents XSS; use
SecureEncoder for JS/attribute contexts
- Minimize remote includes; when used, require
server.middleware.include + explicit security + cache policy
- Prefer controller caching middleware; use
<iscache> thoughtfully
Common SFRA Form Pattern (Bootstrap 4)
<form action="${URLUtils.url('Account-Login')}" method="post" class="login">
<input type="hidden" name="${pdict.csrf.tokenName}" value="${pdict.csrf.token}"/>
<div class="form-group">
<label class="form-control-label" for="login-email">
${Resource.msg('label.login.email','login',null)}
</label>
<input
id="login-email"
type="email"
class="form-control <isif condition="${pdict.loginForm.username.invalid}">is-invalid</isif>"
name="${pdict.loginForm.username.htmlName}"
value="${pdict.loginForm.username.value}"
autocomplete="email"
required>
<isif condition="${pdict.loginForm.username.invalid}">
<div class="invalid-feedback">
<isprint value="${pdict.loginForm.username.error}"/>
</div>
</isif>
</div>
<button type="submit" class="btn btn-primary btn-block">
${Resource.msg('button.login','login',null)}
</button>
</form>
MCP ISML Tools
search_isml_elements("loop")
get_isml_element("isprint")
list_isml_elements()
Related Skills
- sfcc-localization - Essential companion skill for localizing template text. Covers resource bundles,
Resource.msg(), parameterized messages, locale fallback, and internationalization best practices. Always use resource bundles instead of hardcoded text in templates.
Detailed References
External Reference