Scripting in ISML

Use typed SFCC APIs, variables, imports, completion, and navigation inside ISML expressions and script blocks

ISML combines markup with server-side expressions. Intellij SFCC uses its scripting engine to analyze ${ … } expressions and <isscript> blocks, while the markup service handles tags and template structure. This connects script completion, hover, signatures, navigation, references, and diagnostics to your template source

Start with a typed expression

The expression uses the type established in the script block: after product. the list offers the Product API with its details, and typing narrows it to name
cartridge/templates/default/product/example.isml
<isscript>
    var ProductMgr = require('dw/catalog/ProductMgr');
    var product = ProductMgr.getProduct('demo-product');
</isscript>
<isif condition="${product}">
    <isprint value="${product.name}" />
    <a href="${URLUtils.url('Product-Show', 'pid', product.ID)}">
        View product
    </a>
</isif>

Invoke completion after product. in the expression. The type comes from ProductMgr.getProduct, so its API members can be suggested. Hover a member, use Go to Definition, or invoke signature help inside URLUtils.url. Recognized route arguments can also offer project route completion

Template variables and pdict

The scripting view declares the standard template helpers Resource, URLUtils, StringUtils, and out, and models pdict as dw.system.PipelineDictionary. They can participate in scripting intelligence without imports in each expression

Knowing the type of pdict does not establish the shape of every arbitrary value passed into it. A property named pdict.product is not automatically proved to be a dw.catalog.Product. It might be an SFRA view model or a plain object

When the template's contract is known, describe the actual shape:

<isscript>
    /** @type {{ id: string, displayName: string }} */
    var tile = pdict.tile;
</isscript>
<isprint value="${tile.displayName}" />

An annotation documents an expectation; the controller must still supply a matching value. Prefer the real view-model shape over assigning an SFCC API type to a different object

isset, loops, and status variables

The engine models variables introduced by <isset> and <isloop> so they can be used in later expressions. Type precision follows the expression that produces each value

<isset name="heading" value="${Resource.msg('product.title', 'product', null)}" scope="page" />
<isprint value="${heading}" />

heading becomes a known variable. Find references to follow its recognized uses, see Template variable navigation. Included templates see it too, see Variables across templates

<isloop items="${pdict.tiles}" var="tile" status="loopState">
    <div class="${loopState.first ? 'first' : ''}">
        <isprint value="${tile.displayName}" />
    </div>
</isloop>

The items expression is analyzed like any other expression, and tile and loopState are declared for the expressions inside the loop. loopState. completes count, index, first, last, odd and even. The loop variable has no type, though. It does not take the element type of the collection, even when items is typed, so tile. offers no members and member access on it is not checked

To work with typed members inside the loop, read the item into an annotated variable in a script block:

<isloop items="${pdict.tiles}" var="tile">
    <isscript>
        /** @type {{ id: string, displayName: string }} */
        var current = tile;
    </isscript>
    <isprint value="${current.displayName}" />
</isloop>

Template variable navigation

A variable declared in a template leads to its declaration and to its other uses, in the same template and in the templates it reaches

Template variables navigate within the template: Cmd-click a variable to reach its isscript or isset declaration, and Go to References lists every use in the same file

Cmd+Click a variable in a ${ … } expression or a script block (Ctrl+Click on Windows and Linux), or put the caret on it and press F12 (Go to Definition), to move to its declaration:

Declared withThe cursor lands on
var in an <isscript> blockThe name after var
<isset name="…">The name value
<isloop var="…" status="…">The var or status value

Go to References (Shift+F12) or Find All References (Shift+Option+F12 on macOS, Shift+Alt+F12 on Windows and Linux) on a variable in an expression or a script block lists its declaration and every use in <isscript> blocks and ${ … } expressions

  • A variable that no other template sets or reads is searched in its own template only. There, uses are matched by name, so a property with the same name, such as pdict.applyUrl, is listed with them. Names inside strings and comments are skipped
  • A variable with the same name in an unrelated template is not listed
  • Rename Symbol (F2) on a variable edits the places Find References lists, see Rename a template variable

Variables across templates

An <isset> variable, and the var and status of an <isloop>, reach the templates rendered in place while they are set: <isinclude template="…">, a custom tag and <isdecorate>. What a rendered template sets comes back to the template that renders it, after the tag

pidRating.isml is included by productDetails.isml, so it sees the variables set before the isinclude. Completion names the template that sets each one, and Go to Definition opens its isset
cartridge/templates/default/search/productGrid.isml
<isset name="badge" value="${Resource.msg('tile.badge', 'product', null)}" scope="page" />
<isloop items="${pdict.products}" var="product" status="loopState">
    <isinclude template="product/productTile" />
</isloop>
cartridge/templates/default/product/productTile.isml
<div class="tile" data-index="${loopState.index}">
    <isprint value="${badge}" />
</div>

In productTile.isml, badge hovers as string, the type of its isset value in the grid

FeatureIn the rendered template
Completionbadge, product and loopState come after the template's own names. The label details show the declaring template, such as search/productGrid.isml, and the detail starts with isset (page) in or isloop in
Hover and typesThe type of the declared value, as if the variable were declared in this template. A loop var stays untyped
Go to DefinitionOpens the declaring template on the isset name, or on the isloop var or status. pdict.x also finds an isset of x with scope="request" or scope="pdict"
Find ReferencesThe declaration and every read in the templates connected to it. pdict.x reads are not listed
DiagnosticsNo "Cannot find name" or "used before being assigned" error for a name a connected template sets. An isset that another template reads is not reported as unused
  • A session variable, an isset with no scope or scope="session", is readable in every template. It is listed last in completion, and a global with the same name, such as request, wins
  • <isinclude url="…"> is a separate request, so no variables cross it
  • pdict, out, Resource, URLUtils and StringUtils belong to each template and never cross
  • A name that a connected template does not set at that point is still reported, for example a read before the <isinclude> that sets it

Imports and cartridge context

A require() in a script block uses the same cartridge resolution as a server script. Types from local metadata can participate when an expression resolves to an appropriate SFCC object

Press F12 on the path in require('*/cartridge/scripts/checkout/constants') to open the module. In the other direction, references to the module's exports list the template lines that read them, see Find template usages from scripts

Legacy template code can also use importScript:

<isscript>
    importScript('util/helpers.ds');
</isscript>
<isprint value="${formatMoney(pdict.total)}" />

When helpers.ds declares formatMoney, the imported symbol can complete, hover, and navigate back to its declaration. The analyzer models the import relationship; it does not execute the import or guarantee the runtime type of pdict.total. See Digital Script for import scope and shadowing checks

How positions stay connected

The server builds a JavaScript analysis view of the template and maps locations back to the original ISML. You invoke completion, navigate, and inspect diagnostics in .isml, not in a generated JavaScript file

Complex or dynamically assembled expressions can limit the information available. A missing diagnostic on an untyped value is not evidence that its runtime property exists

The scripting pages cover the shared engine in more depth, including ISML expression completion, inlay hints, which also appear in templates, references to ISML variables, and template and resource strings

On this page