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

order.ds opens in the DigitalScript language with its own icon and highlighting; after order. the Order members from the dw types complete exactly as in a .js file

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 .js files, 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.ds

The 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.system

importClass imports a single class into global scope by its short name:

importClass(dw.util.ArrayList);

var items = new ArrayList();          // ← ArrayList, no dw.util. prefix

Both 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

A legacy pipeline script with importScript and for each: basket. completes inside the loop header, lineItem hovers as a ProductLineItem and Problems reports nothing

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

On this page