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(). The extension models these imported declarations so they can participate in completion, hover, navigation, references, and shadowing checks
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
Imports inside functions and blocks
For analysis, the server models declarations from recognized importScript calls in the importing script's global scope, including calls inside functions and blocks:
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 models imported declarations in the importing script's global analysis scope, including imports inside functions or blocks. This is static analysis, not execution: the relevant import must still run before runtime code depends on its declarations
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 analyzer treats the imported declaration as shadowed. Runtime behavior still depends on the executed code and declaration order. 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' is already declared in this file. An importScript/importPackage/importClass
// call also provides this symbol, so the duplicate synthetic declaration is skipped and
// the local declaration takes precedence.See 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 editing aids 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
Pipeline input and output types
A pipeline script can declare its dictionary with @input and @output annotations. The server uses those declarations to type execute(args) without changing the .ds file. See Pipeline scripts & XML for a complete example and the recognized annotation syntax
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