Digital Script (.ds)
Develop legacy Digital Script (.ds) with JavaScript-level intelligence, importScript/importPackage/importClass scope analysis, and zero configuration.
Digital Script (.ds) is the original Salesforce Commerce Cloud server-side scripting format, still found across pipeline-based and SiteGenesis projects. Most editors treat .ds as plain text. Intellij SFCC treats it as a first-class language.
A Real Language, Not Plain Text
The extension registers .ds as the digitalscript language with its own icon, syntax configuration, and TextMate grammar. The moment you open a .ds file you get:
- Syntax highlighting tuned for Digital Script.
- The same TypeScript-grade engine used for
sfcc-javascript- completion, hover, signature help, go-to-definition, find references, and diagnostics. - Zero configuration - exactly like
.jsfiles, there is nothing to set up.
Internally the engine analyzes .ds files with the same JavaScript/CommonJS pipeline as script files, so everything in Types & the dw API and Module Resolution applies - dw.* types, cartridge-aware require(), and generated metadata types.
// order.ds
var OrderMgr = require('dw/order/OrderMgr');
var Status = require('dw/system/Status');
function execute(args) {
var order = OrderMgr.getOrder(args.OrderNo);
order. // ← full dw.order.Order completion
return new Status(Status.OK);
}importScript and Scope
importScript is the Digital Script import mechanism, and it behaves fundamentally differently from require(). This distinction is the single most important thing to understand about .ds files - and the extension models it precisely so completion, hover, navigation, and shadowing warnings are all accurate.
require() returns a module object you assign to a variable. importScript does not return anything - it injects the target script's top-level declarations directly into the global scope, where you reference them by their bare names.
What gets injected
When you importScript('cartridge/scripts/util/helpers.ds'), every top-level declaration in helpers.ds - its vars, functions, and so on - becomes available as a global symbol in the importing script:
// util/helpers.ds (the imported script)
var TAX_RATE = 0.2;
function formatMoney(amount) {
return amount.toFixed(2);
}// checkout.ds (the importing script)
importScript('util/helpers.ds');
// TAX_RATE and formatMoney are now in scope by their bare names -
// no variable was assigned, nothing was destructured.
var tax = subtotal * TAX_RATE; // ← completes, hovers, navigates
var label = formatMoney(tax); // ← jumps to helpers.dsThe extension resolves these injected symbols so they autocomplete, show hover docs, and navigate to their real definition in the source script - instead of appearing undefined.
Location does not matter
This is the part that surprises people coming from require(). No matter where the importScript call appears - top of the file, inside a function, inside a block - the imported symbols land in the global scope, not a local one:
function init() {
importScript('util/helpers.ds'); // called inside a function …
}
// … yet formatMoney is global, usable anywhere in the file:
function checkout() {
return formatMoney(total); // ← still resolves
}The language server mirrors SFCC's actual runtime behavior here: it hoists the imported declarations to global scope regardless of the call site, so its analysis matches what really happens at execution.
Transitive imports
If an imported script itself calls importScript, those further symbols are pulled in too. The extension follows the chain, so a symbol three scripts deep still resolves at the call site.
Local declarations shadow imports
If you declare a symbol locally that an importScript also provides, your local declaration wins - the imported one is shadowed and effectively does nothing. This is a classic, silent Digital Script bug, so the server raises a shadowing diagnostic that tells you which sources collide.
importScript('util/helpers.ds'); // provides formatMoney
function formatMoney(amount) { // ← local re-declaration
return '$' + amount; // shadows the imported formatMoney
}
// ⚠ 'formatMoney' shadows a symbol imported via importScriptSee the shadowing diagnostic for how this surfaces in the Problems panel.
importPackage and importClass
Two related mechanisms bring dw.* classes into global scope by short name - common in older .ds code that predates require('dw/...'):
importPackage imports every class of a package into global scope, referenced by its short (unqualified) name:
importPackage(dw.system);
var status = new Status(Status.OK); // ← Status, no dw.system. prefix
var logger = Logger.getLogger('cat'); // ← Logger, also from dw.systemimportClass imports a single class into global scope by its short name:
importClass(dw.util.ArrayList);
var items = new ArrayList(); // ← ArrayList, no dw.util. prefixBoth are resolved against the dw.* type model, so the imported short names complete, hover with full API docs, and navigate - and they participate in the same shadowing analysis as importScript.
For new code, prefer require('dw/...') - it's explicit about what each name is and matches modern SFRA style. importScript / importPackage / importClass remain fully supported for the legacy .ds code that uses them. See Module Resolution and Imports & the Rhino Engine.
Same Scripting in ISML
The exact same scripting engine - and the same importScript scope behavior - powers ISML templates. ${ … } expressions and <isscript> blocks are full script, so importing helpers into an ISML template injects their symbols just as it does in a .ds file. That's why ISML has complete scripting support, not just tag completion.
<isscript>
importScript('util/helpers.ds'); // formatMoney now in scope
</isscript>
<isprint value="${formatMoney(pdict.total)}" />ISML scripting, the injected template prelude (pdict, Resource, URLUtils, loop status, …), tags, and snippets are covered in the ISML LSP section.
Legacy Syntax, Handled Gracefully
Older Digital Script relies on idioms that predate modern JavaScript. The engine recognizes them so they work as completions and navigation targets - and so they do not produce a wall of false-positive errors.
Mixed Projects
Many real codebases mix .ds, .js, and .isml. Because one language server handles all of them with a shared cartridge model, navigation crosses file types seamlessly - a require() in a .js controller can jump straight into a .ds module, and references span both.