dist/codex/salesforce-commerce/skills/sf-b2c-isml/SKILL.md
"Write ISML templates for B2C Commerce — <isprint>, <isif>, <isloop>, <isset>, <isinclude>, <isdecorate>, expression syntax ${pdict.*}, Resource.msg() localization, content slots, and template inheritance. Use when building B2C storefront templates."
npx skillsauth add orcaqubits/agentic-commerce-claude-plugins sf-b2c-ismlInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build ISML (Internet Store Markup Language) templates for Salesforce B2C Commerce storefronts.
Fetch live documentation FIRST:
Why: ISML syntax, tag attributes, encoding modes, and localization patterns evolve with each B2C Commerce release. Always verify current specs before generating template code.
| Tag | Purpose | Key Attributes |
|-----|---------|----------------|
| <isprint> | Output with encoding | value, encoding (required for XSS safety) |
| <isif> / <iselseif> / <iselse> | Conditional rendering | condition |
| <isloop> | Iteration | items, var, status, begin, end, step |
| <isset> | Variable assignment | name, value, scope (page/request/session) |
| <isinclude> | Include partial template | template (path) or url (controller action) |
| <isdecorate> | Decorator/layout wrapper | template (decorator path) |
| <isreplace> | Insertion point in decorator | (no attributes) |
| <isslot> | Content slot | id, context (category/folder/global), description |
| <iscache> | Template-level caching | type, hour, varyby |
| <isscript> | Inline server-side JS | (use sparingly -- prefer controllers) |
| <iscontent> | Set content type/charset | type, charset |
| <isstatus> | Set HTTP status code | code |
| <isredirect> | Server-side redirect | location |
| <iscomment> | Server-side comment | (not rendered to client) |
<isprint>| Mode | Context | When to Use |
|------|---------|-------------|
| htmlcontent | HTML body text | Default for most output (de facto SFRA standard) |
| htmlsinglequote | Single-quoted HTML attributes | <div title='...'> |
| htmldoublequote | Double-quoted HTML attributes | <div title="..."> |
| htmlunquote | Unquoted HTML attributes | Rarely needed; prefer quoted attributes |
| jshtml | JavaScript strings in HTML | <script>var x = '...';</script> |
| jsattribute | JS in HTML event attributes | onclick="..." |
| jsonvalue | JSON values | JSON data output |
| uricomponent | URL query parameters | Query string values |
| uristrict | Full URI encoding | Path segments |
| xmlcontent | XML body content | XML/RSS feeds |
| off | No encoding | Trusted content ONLY -- avoid in production |
Always specify encoding. Choose based on output context. Never use raw ${variable} for user-controlled data without encoding.
Pipeline Dictionary (pdict):
${pdict.variableName} -- access controller-passed data (SFRA: from res.render())pdict contains only what the controller explicitly passedObject Navigation:
${pdict.product.name} -- dot notation for nested properties${pdict.basket.totalGrossPrice.value} -- deep property accessOperators: Arithmetic (+, -, *, /, %), comparison (==, !=, <, >), logical (&&, ||, !), ternary (${cond ? a : b}).
<isloop> Status Object| Property | Type | Description |
|----------|------|-------------|
| first | Boolean | True on first iteration |
| last | Boolean | True on last iteration |
| count | Number | Current iteration (1-based) |
| index | Number | Current iteration (0-based) |
Access via the status attribute: <isloop items="${list}" var="item" status="loopstate">, then use ${loopstate.first}, ${loopstate.index}, etc.
<isset> Scope Rules| Scope | Lifetime | Use Case |
|-------|----------|----------|
| page | Current template render | Temporary computation within a template |
| request | Current HTTP request | Share data across included templates |
| session | Current user session | Persist across requests (use sparingly) |
Default scope is session -- always specify scope explicitly to avoid unintended session pollution.
${Resource.msg('key', 'bundleName', 'Default')}
${Resource.msgf('key', 'bundle', 'Default {0}', param)}
Common bundles: forms, common, error, product. Always provide meaningful default values. Resource bundles are .properties files in templates/resources/.
Decorators wrap child content via <isdecorate> and <isreplace>:
<!-- Child template -->
<isdecorate template="common/layout/page">
<h1>Page Content</h1>
<!-- replaces <isreplace /> in decorator -->
</isdecorate>
The decorator template defines shared layout (header, footer, <head>) and uses <isreplace /> to mark where child content is inserted. Nested decorators are supported.
<!-- Pattern: product loop skeleton -->
<!-- Fetch live docs for loopstate properties -->
<isloop items="${pdict.products}" var="product" status="loopstate">
<isprint value="${product.name}" encoding="htmlcontent" />
</isloop>
<!-- Pattern: URL generation -->
<a href="${URLUtils.url('Product-Show', 'pid', product.ID)}">
<isprint value="${product.name}" encoding="htmlcontent" />
</a>
| Method | Purpose |
|--------|---------|
| URLUtils.url() | Generate absolute URL for controller action |
| URLUtils.https() | Force HTTPS URL |
| URLUtils.staticURL() | URL to static asset in cartridge |
| URLUtils.absURL() | Absolute URL with full domain |
| URLUtils.home() | Homepage URL |
| URLUtils.continueURL() | Current request URL for form returns |
Configured in Business Manager. Slots support HTML, products, categories, or content assets. Use descriptive IDs (home-banner-top, category-promo-sidebar) and set appropriate context (global, category, folder). Slot rendering can be cached separately from template caching.
<isscript> executes on the server and can access the full SFCC API -- keep logic minimal and move heavy processing to controllers or scripts.<isset scope="session"> persists across requests -- avoid session bloat by defaulting to page scope.res.render() is available.<iscache> is separate from CDN caching; both must be configured correctly.encoding="off" unless input is fully trusted.<isscript> blocks -- move logic to controllers or scripts.<isset> to compute expensive values once and reuse.<iscache> for templates that do not change per request.<isinclude>.| Directory | Contents |
|-----------|----------|
| common/layout/ | Decorators (page, checkout, account) |
| components/ | Shared partials (header, footer, navigation) |
| product/ | Product display templates |
| search/ | Search results templates |
| account/ | Customer account templates |
| cart/ | Cart and mini-cart templates |
| checkout/ | Checkout flow templates |
Fetch the ISML tag reference, encoding mode documentation, and resource bundle guide for exact tag attributes, encoding behaviors, and localization APIs before implementing.
development
Build with Spree's headless Next.js storefront — the official `spree/storefront` repo (Next.js 16 App Router with Server Actions and Turbopack, React 19 Server Components, Tailwind CSS 4, TypeScript 5, `@spree/sdk`, Sentry), server-only auth (httpOnly JWT cookies + publishable key), MeiliSearch faceted catalog, one-page checkout with Apple/Google Pay/Klarna/Affirm/SEPA, multi-region market routing, GA4 + JSON-LD SEO, and Vercel/Docker deployment. Use when forking or customizing the storefront, or evaluating headless adoption.
tools
Build Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies for swapping service objects, gem release/versioning, and the deprecated Deface engine. Use when building a reusable Spree extension or adding non-trivial customization to an app.
development
Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.
tools
Cross-cutting Spree development patterns — the customization preference hierarchy (Events > Webhooks > Dependencies > Decorators), `Spree::Dependencies` service-object swapping, the `_decorator.rb` + `prepend` + `self.prepended` idiom, idempotent subscribers and webhook receivers, multi-store scoping discipline, prefixed IDs, calculator polymorphism (shipping/promotion/tax share the base), service-object composition with `dry-monads` or simple results, why to avoid `class_eval` reopening and Deface, and Spree-on-Rails idioms (Hotwire/Turbo Stimulus, ActiveStorage, Action Cable, Sidekiq). Use when designing the architecture of a Spree extension or solving cross-cutting concerns.