Scripting in ISML
Use full SFCC server-side scripting intelligence inside ISML expressions and isscript blocks, including dw.* types, pdict, loop variables, and importScript.
The headline capability of the ISML language server: templates are scripts too. Every ${ … } expression and every <isscript> block is analyzed by the same TypeScript-grade engine that powers .ds and .js files - so you get dw.* typing, completion, hover, and diagnostics inside your templates, not just tag highlighting.
How It Works
When the server analyzes a template it transpiles the ISML into an equivalent JavaScript view of the file: ${ … } expressions become expressions, <isset> and <isloop> become variable declarations, and <isscript> blocks are preserved verbatim. The offsets are kept aligned with the source, so completion and diagnostics land on the right characters in your actual .isml.
The transpiled view starts from a prelude that declares the variables SFCC makes ambient in every template, so they're always typed and in scope:
// Implicit prelude - present in every template's analysis
var Resource = require('dw/web/Resource');
var URLUtils = require('dw/web/URLUtils');
var StringUtils = require('dw/util/StringUtils');
var out = new (require('dw/io/PrintWriter'))();
var L = {}; // loop status (see <isloop> below)
var pdict = new (require('dw/system/PipelineDictionary'))();That means pdict, Resource, URLUtils, StringUtils, and out complete and type-check in any expression without you importing anything.
${ … } Expressions
Inside an expression you get full member completion, hover docs, and signature help - the same as in a script file.
<isprint value="${pdict.product.getName()}" />
<!-- ^ pdict typed, .product resolves, .getName() completes with dw.* docs -->
<a href="${URLUtils.url('Product-Show', 'pid', pdict.product.ID)}">
<!-- ^ URLUtils.url route + parameter completion --><isscript> Blocks
<isscript> blocks are preserved as real JavaScript and analyzed in place - write any cartridge logic and it behaves like a script file:
<isscript>
var ProductMgr = require('dw/catalog/ProductMgr');
var product = ProductMgr.getProduct(pdict.pid); // typed dw.catalog.Product
var price = product.getPriceModel().getPrice();
</isscript>
<isprint value="${price}" /><isset> Variables
A <isset name="…"> becomes a variable declaration, so the name you define is tracked and completes in later expressions:
<isset name="displayPrice" value="${pdict.product.getPriceModel().getPrice()}" scope="page" />
<isprint value="${displayPrice}" />
<!-- ^ displayPrice is in scope and typed --><isloop> Variables and Status
The loop's var becomes a per-iteration variable, and the status object (first/last/count/index, etc.) is tracked through the loop-status model:
<isloop items="${pdict.products}" var="product" status="loopState">
<div class="${loopState.first ? 'first' : ''}">
<isprint value="${product.name}" />
<!-- ^ product typed from the items collection -->
</div>
</isloop>importScript Inside ISML
Because <isscript> blocks are real script, importScript works in a template exactly as it does in a .ds file - it injects the imported script's top-level declarations into the global scope, so those symbols are then usable in the template's expressions.
<isscript>
importScript('util/helpers.ds'); // injects formatMoney, TAX_RATE, … globally
</isscript>
<isprint value="${formatMoney(pdict.total)}" />
<!-- ^ formatMoney resolves, hovers, and navigates to helpers.ds -->The injection and shadowing rules are identical to scripts - see Digital Script › importScript and Scope. Note that importScript lives inside an <isscript> block (it's a script statement), not in a bare ${ … } expression.
The Same dw API and Cartridge Model
Everything the script engine knows applies here too: the full dw.* API, your project's generated metadata types, and cartridge-aware require() resolution. See Types & the dw API and Module Resolution.