SFMC Language Service for VS CodeA Visual Studio Code extension providing comprehensive language support for Salesforce Marketing Cloud Engagement and Marketing Cloud Next — AMPscript, SSJS (Server-Side JavaScript), GTL (Guide Template Language), and MCN Handlebars. HTML files containing SFMC content are auto-detected and switched to the combined SFMC (AMPscript / SSJS) language, and Feature Overview
Formatting runs a bundled copy of Prettier and Variable resolution means the language service infers the concrete type (and where possible, the value) held in a variable. Hovering over a local variable reveals its resolved type rather than a generic AMPscriptSyntax Highlighting
Auto-Completion
Hover / IntelliSense
Signature Help
Diagnostics
Marketing Cloud Next compatibility (
|
| Setting value | Behaviour |
|---|---|
"engagement" (default) |
Existing diagnostics only — no MCN-specific checks |
"next" |
Adds errors for MCN-unsupported functions and SSJS blocks; adds information hints for functions with behavioral differences |
In "next" mode:
- Error for any AMPscript function not available in Marketing Cloud Next (41 of 155 functions are supported). Example:
InsertDE,AttachFile,ContentArea→"InsertDE is not supported in Marketing Cloud Next." - Information for supported functions with behavioral differences (
FormatDate,Lookup,StringToDate) — the note is shown in the Problems panel at the call site so you can act on it before deploying - Error on any
<script runat="server">block — SSJS is not supported in Marketing Cloud Next - MCN Handlebars support activates: validation, completions, hover, signature help, and code actions for
{{...}}mustaches and{!$...}built-in bindings. Binding hovers link to the Salesforce Developers documentation.
Hover cards always show the MCN support line regardless of the setting: "Supported in Marketing Cloud Next (API v67.0+)" or "Not supported in Marketing Cloud Next", so you can check at a glance without switching modes.
Variable Resolution
Variable resolution — inferring the type or value held by a @variable at any point in the file — is planned for a future release. The extension does not yet provide type-aware hover or diagnostics for AMPscript variables.
Snippets
36 built-in snippets:
| Prefix | Description |
|---|---|
ampblock |
AMPscript block delimiters |
ampinline |
Inline output expression |
vout |
Variable output (%%=v(@var)=%%) |
ampvar / ampset / ampvarset |
Variable declaration and assignment |
ampif / ampifelse / ampifelseif |
Conditional blocks |
ampfor / ampforrows |
FOR loops (counting and row-set iteration) |
amplookup / amplookuprows / amplookuporderedrows |
Data Extension lookups |
amphttpget / amphttppost |
HTTP requests with error handling |
ampcloudpagesurl |
CloudPages URL builder |
ampcontentblock |
Content Builder block inclusion |
ampattrval |
Safe attribute retrieval |
ampempty |
Empty check with default |
ampupsertdata / ampinsertdata |
Data Extension DML |
ampcreateobject |
SOAP API object creation |
ampscripttag |
Script tag block |
amptemplate |
Full email template boilerplate |
ampssjs |
AMPscript-in-SSJS bridge pattern |
amptreatascontent |
TreatAsContent from SSJS |
ampredirectto |
Redirect to CloudPage |
ampdateformat |
Common date format patterns |
amprowsetloop |
RowSet loop with error check |
SSJS (Server-Side JavaScript)
Syntax Highlighting
- Standard JavaScript syntax, extended with SFMC-specific patterns
<script runat="server">block recognition (withoutlanguage="ampscript")
Auto-Completion
- 223 SFMC-specific completions sourced from
ssjs-data: 50Platform.Function.*methods, 27 bare SSJS globals (Stringify,Now,Write,GUID, and more), 132 Core Library object methods across 41 objects, and 14 WSProxy operations — plus 127 ES3/ES5 built-in completions (Array, String, Number, Object, Math, Date, RegExp, etc.) Platform.Function.*,Platform.Variable.*,Platform.Response.*,Platform.Request.*methods- Core library objects with full method listings:
DataExtension,Subscriber,TriggeredSend,HTTP,Guard, and more — available afterPlatform.Load("Core", "1.1.5") - WSProxy method completions for
new Script.Util.WSProxy() - Bare-name globals (
Stringify(),Now(),GUID(), etc.) alongside theirPlatform.Function.*equivalents - Context-aware member completions: after a
.only the relevant members are shown — the full global list is not injected into member-access positions
Hover / IntelliSense
- TypeScript-powered: completions, hover, and diagnostics are backed by an embedded TypeScript language service with full SFMC type declarations — accurate, type-aware suggestions for all SFMC globals
- Function signature with parameter names and types
ssjs.guidereference links embedded in hover cards@deprecatednotices for functions that should not be used in new code@remarks requiresCoreLoadhints wherePlatform.Load("Core", "1.1.5")is required
Signature Help
- Parameter hints while typing function arguments
- Active parameter highlighted as you type commas
ssjs.guidelinks shown alongside the active signature
Go-to Definition
- Navigate to SFMC function and object declarations in the bundled type definition file
Diagnostics
- Missing
Platform.Load: flags calls to Core Library objects (DataExtension,HTTP.Get, etc.) and bare globals (Stringify(),Now(),GUID(), etc.) whenPlatform.Load("Core", "1.1.5")has not been called before that line — order-aware - ES6+ syntax errors:
let/const, arrow functions,for...of, generator functions, spread..., destructuring are flagged as errors (SSJS runs in ES3/ES5 only) - TypeScript type diagnostics: type-aware errors powered by the embedded TypeScript service
- Non-functional Core methods: Error on Core Library methods that resolve and are callable but never take effect on a live business unit — covers static calls such as
FilterDefinition.Update(...)as well as instance calls on objects created viaInit(...) - Deprecated Core classes: Warning on methods of deprecated Core Library classes (
ContentAreaObj,Email,Portfolio,Template,Send,Send.Definition) — covers static calls such asSend.Definition.Add(...)as well as instance calls on objects created viaInit(...) - Core-version-bound members: Error when a member that only exists up to
Platform.Load("Core", "1")— such asErrorUtilandErrorUtil.ThrowWSProxyError— is used in a file that loads a newer Core version, because the member is undefined at runtime there. WithPlatform.Load("Core", "1")the usual deprecation Warning is shown instead - MCN incompatibility (when
sfmcLanguageServer.targetPlatformis"next"): Error on any<script runat="server">block — SSJS is not supported in Marketing Cloud Next
Suppressing "Cannot find name" for cross-file variables
SFMC CloudPages and emails often rely on variables that are defined in another asset — such as a global DEBUG flag set in a parent page, a subscriber key passed via AMPscript, or a configuration variable set in an included script. The embedded TypeScript service does not see those other files and will report them as unknown names.
Use an ESLint-style file-level /* global */ comment to tell the extension which names are supplied by the surrounding context. The comment must appear at the top of the .ssjs file (or inside a <script runat="server"> block for SFMC HTML files):
/* global DEBUG, deKey */
if (DEBUG) {
Write(deKey);
}
Multiple names are separated by commas. The :readonly / :writable qualifiers used by ESLint are accepted for compatibility but have no effect on TypeScript diagnostics — all declared names are treated as any:
/* global DEBUG:readonly, deKey:writable */
You can also use /* globals */ (with a trailing s) — both spellings work identically. The declarations are scoped to the current document and are removed when the file is closed, so they cannot leak into other open files.
Variable Resolution
The embedded TypeScript service infers the type — and where possible the concrete value — held by a variable at each point in the file. Hovering over a local variable shows its resolved type (string, number, a specific object type, etc.) rather than a generic any. This works for variables whose initializer has a known type and for variables reassigned within the same scope.
Snippets
18 built-in snippets:
| Prefix | Description |
|---|---|
ssjsblock |
<script runat="server"> wrapper |
ssjsplatformload |
Platform.Load("core", "1") |
ssjslookup / ssjslookuprows |
Platform.Function lookups |
ssjsinsertdata / ssjsupsertdata |
Data Extension DML |
ssjshttpget / ssjshttpgetsimple / ssjshttppost |
HTTP requests |
ssjswsproxy / ssjswsproxycreate |
WSProxy operations |
ssjstrycatch |
Try/catch error handling |
ssjsde |
DataExtension Init/Retrieve |
ssjsvarbridge |
AMPscript variable bridge |
ssjsrequestparam / ssjsformdata |
Request parameters |
ssjsredirect / ssjscloudpagesurl |
Navigation |
ssjstemplate |
Full CloudPage template |
How .ssjs files are interpreted (sfmcLanguageServer.ssjsFileMode)
By default a .ssjs file is treated as plain Server-Side JavaScript — the whole file is linted as SSJS. Some tooling (notably SSJS Manager) instead stores .ssjs files as HTML that wraps the code in <script runat="server">…</script> (sometimes with AMPscript/HTML around it). Feeding such a file to the SSJS linter whole would flag the wrapper as broken JavaScript.
Use sfmcLanguageServer.ssjsFileMode (resource-scoped) to choose how .ssjs files are read:
| Value | Behaviour |
|---|---|
"javascript" (default) |
Every .ssjs file is plain SSJS — today's behaviour, no content scan. |
"auto" |
Per-file detection: a .ssjs that wraps its code in <script runat="server"> (or contains AMPscript) is treated as SFMC content so the embedded SSJS is linted inside the tag; a plain-JS .ssjs stays SSJS. Enables interop with SSJS Manager. Costs a small per-edit content scan. |
"sfmc" |
Every .ssjs file is forced to SFMC content — choose this only when all your .ssjs files are script-wrapped (a plain-JS .ssjs would then be treated as HTML, not linted as SSJS). No content scan. |
The default preserves existing behaviour. SSJS Manager can set "auto" per-workspace automatically.
HTML Files — SFMC Language
Any .html file that contains SFMC content is automatically switched to the SFMC (AMPscript / SSJS) language (sfmc) — no manual language selection needed.
Detection triggers (checked on file open and on every content change, so pasting code into a blank file works immediately):
- AMPscript block:
%%[ ... ]%% - AMPscript inline:
%%= ... =%% - AMPscript script tag:
<script language="ampscript"> - SSJS script tag:
<script runat="server">(withoutlanguage="ampscript")
What you get after detection:
- Language shown in the VS Code status bar as SFMC (AMPscript / SSJS) with the blue/yellow icon
- Full AMPscript IntelliSense (completions, hover, signature help, diagnostics) inside
%%[ ]%%and%%= =%%regions - Full SSJS IntelliSense (completions, hover, signature help, go-to definition, TypeScript diagnostics) inside
<script runat="server">blocks — with correct line/column offsets back to the HTML document - Syntax highlighting for both AMPscript and SSJS content within the same file
Plain HTML files (no SFMC content) are never affected and remain as html.
GTL (Guide Template Language)
GTL uses {{ }} delimiters and is a thin wrapper around AMPscript. The extension provides:
- Context-aware completions inside
{{ }}— AMPscript functions, variables, and personalization strings - 8 built-in snippets:
| Prefix | Description |
|---|---|
gtlexpr |
GTL expression {{ }} |
gtlvar |
Variable output {{ v(@var) }} |
gtllookup |
Lookup via GTL |
gtlif |
Inline conditional {{ IIf(...) }} |
gtlcontent |
ContentBlockByKey via GTL |
gtlpersonalization |
Personalization string |
gtlattrval |
AttributeValue via GTL |
gtlformatdate |
Date formatting via GTL |
Handlebars (.hbs) — Marketing Cloud Next
.hbs files use VS Code's built-in Handlebars language (HTML plus Handlebars), so syntax highlighting works out of the box — no custom language is registered by this extension. On top of that, the extension attaches its language server to Handlebars documents and provides MCN Handlebars intelligence for the Salesforce Marketing Cloud Next template engine.
Because Handlebars is a Marketing Cloud Next-only feature, .hbs files are always treated as targetPlatform: "next" regardless of the sfmcLanguageServer.targetPlatform setting. You get the same MCN Handlebars support that Engagement HTML files only receive when the setting is switched to "next".
What you get in .hbs files:
- Completions for MCN Handlebars helpers inside
{{ ... }}mustaches and for built-in{!$...}data bindings - Hover documentation for helpers and bindings, with links to the Salesforce Developers documentation
- Signature help for helper arguments — MCN helper arguments are whitespace-separated (e.g.
{{substring value 0 3}}), so hints appear as you type spaces - Diagnostics for unsupported Handlebars constructs (partials, decorators, and built-in helpers absent from MCN's locked-down engine)
- Code actions / quick fixes for MCN Handlebars diagnostics
Status Bar
A compact entry appears in the VS Code status bar (bottom-right) as soon as the extension activates. Its label reflects the active sfmcLanguageServer.targetPlatform: sfmc-e for Engagement and sfmc-next for Marketing Cloud Next.
- Spinner while the language server is starting up.
- Check mark once the server is running and ready.
- Error icon if the server stops or fails.
- Click to open the language server output channel.
- Hover for a tooltip with a Show Output link, live server status, active trace level (if enabled), a quick Settings link, and an MCE Mode / MCNext Mode line that opens Settings filtered to the
targetPlatformoption. The label and tooltip update automatically when the setting changes.
Formatting
The extension ships a built-in document formatter powered by a bundled copy of Prettier and prettier-plugin-sfmc — no separate install or .prettierrc is required. It runs entirely in-process, so Format Document and Format on Save work out of the box.
Supported languages
| Language | Trigger |
|---|---|
| AMPscript | *.ampscript, *.amp |
| SSJS | *.ssjs |
| SFMC HTML (mixed content) | *.html auto-switched to sfmc on markers |
| MCN Handlebars | *.hbs |
| SQL | *.sql |
Mixed-content SFMC HTML is formatted as one unit: the surrounding HTML, embedded AMPscript (%%[ … ]%%, %%= … =%%), embedded SSJS (<script runat="server">), and MCN Handlebars ({{ … }}) are all handled together.
Plain HTML (files without SFMC markers) is intentionally not claimed — it keeps the html language id and is left to VS Code's HTML formatter or your own.
Choosing a formatter (coexistence with the Prettier extension)
The built-in formatter is always registered, but VS Code decides which formatter runs via editor.defaultFormatter. The extension is designed to just work out of the box — no manual Prettier or prettier-plugin-sfmc install required:
- For every SFMC language that has no formatter set in your workspace/folder settings, the extension quietly claims it (writes itself to your workspace
.vscode/settings.json). A formatter you set only in your User (global) settings does not block this per-workspace takeover. - It only asks when there is a genuine conflict — i.e. one or more SFMC languages already have a different
editor.defaultFormatter(the Prettier extensionesbenp.prettier-vscode, or any other formatter) in your workspace/folder settings. In that case the free languages are still claimed silently, and a prominent modal dialog appears once per workspace offering to switch the conflicting languages to the SFMC formatter too. Your answer is remembered internally per workspace — it is not written to your settings file. - If nothing conflicts, you are never prompted.
Whenever the extension claims one or more languages silently, it shows a brief informational notification (which auto-dismisses) listing exactly which languages it now formats. After the conflict prompt, it also confirms whether the conflicting languages were switched to the SFMC formatter or kept with your existing one.
Re-showing the prompt. To force the conflict prompt to appear again (e.g. to reset your earlier choice), add "sfmcLanguageServer.formatterPromptDismissed": false to your workspace .vscode/settings.json. The extension removes that entry automatically once you answer, so it never lingers in a git-tracked settings file.
Team lead / admin opt-out. To pin a formatter choice for a whole repository and stop the extension from ever prompting or silently claiming languages, commit "sfmcLanguageServer.formatterPromptDismissed": true to the project's .vscode/settings.json (alongside your desired editor.defaultFormatter per-language blocks). With true set, the extension leaves your editor.defaultFormatter values completely untouched and never shows the modal — and, unlike the transient false reset value, it never removes an explicit true. This is the recommended way to standardise the formatter (SFMC formatter, Prettier extension, or a mix) across a team without each member being asked. (sfmcLanguageServer.enableFormatter: false also silences everything, but it additionally turns the built-in formatter off — use formatterPromptDismissed: true when you still want the built-in formatter available where you point editor.defaultFormatter at it.)
Language IDs, not aliases. Per-language override blocks must use the lowercase language IDs —
[ampscript],[ssjs],[sfmc],[handlebars],[sql]. The capitalised display names (e.g.[AMPscript]) are aliases and are ignored by VS Code's language-scoped settings.
Auditing the decision. On every activation the extension writes a single status line to the SFMC Prettier Formatter Output channel (Output panel → "SFMC Prettier Formatter") describing the current state — whether the prompt was already answered for this workspace, whether the admin opt-out is active, which languages conflict, and which (if any) were newly claimed. Use it to confirm why (or why not) the prompt appeared.
To switch formatters at any time, set editor.defaultFormatter per language in .vscode/settings.json, for example:
{
// Use the built-in SFMC formatter for AMPscript…
"[ampscript]": { "editor.defaultFormatter": "joernberkefeld.sfmc-language" },
// …but hand SSJS to the Prettier extension:
"[ssjs]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
// …and keep the remaining three on the built-in SFMC formatter:
"[sfmc]": { "editor.defaultFormatter": "joernberkefeld.sfmc-language" },
"[handlebars]": { "editor.defaultFormatter": "joernberkefeld.sfmc-language" },
"[sql]": { "editor.defaultFormatter": "joernberkefeld.sfmc-language" },
}
All five SFMC language IDs ([ampscript], [ssjs], [sfmc], [handlebars], [sql]) support this per-language override. Set sfmcLanguageServer.enableFormatter to false to turn the built-in formatter off entirely.
Config override and its limits
A workspace .prettierrc*, package.json "prettier" field, or .editorconfig overrides the standard defaults (e.g. tabWidth, printWidth, singleQuote), and a workspace .prettierignore is respected — ignored files are skipped.
Two things are always enforced, regardless of your config:
- The bundled
prettier-plugin-sfmcis used. A"plugins"entry in your config file is ignored (loading a second Prettier from disk would break the mixed-content parser). - The bundled Prettier version is used.
If you need full control over the Prettier or plugin version, or a custom plugins list, use the Prettier extension (esbenp.prettier-vscode) with a project-local prettier / prettier-plugin-sfmc install instead, and point the relevant editor.defaultFormatter at it.
Model Context Protocol (MCP) for AI Assistants
This extension registers the mcp-server-sfmc MCP server with VS Code so GitHub Copilot agent mode (and other MCP-aware chat flows) can discover SFMC tooling automatically — validation and lookups for AMPscript and SSJS, diff-aware review, fix suggestions, catalogs as resources, and guided prompts. You do not need a separate npm install or a manual .vscode/mcp.json entry for that discovery; the server is still loaded via npx when the tool runs.
Finding this extension vs. the MCP Server Gallery in VS Code
- In the Extensions view,
@contribute:mcpfilters extensions that contribute MCP server definitions. This extension appears there because it declares that contribution and registersmcp-server-sfmc. - The
@mcpfilter opens the MCP Server Gallery, which is backed by the curated GitHub MCP Registry (not the full Marketplace catalog). Publishing the VS Code extension does not add the server to that gallery. The npm packagemcp-server-sfmcis registered asio.github.JoernBerkefeld/mcp-server-sfmc(see mcp-server-sfmcmcpName/server.json); after a release, metadata is published to the MCP Registry viamcp-publisher(locally or from CI). Quickstart: Publish an MCP Server to the MCP Registry. Ensurechat.mcp.gallery.enabledis on if the gallery does not appear.
Requirements: VS Code 1.101 or newer (see engines in this extension's package.json). Older versions can add the server manually as described in the mcp-server-sfmc README (also covers Cursor, Claude Desktop, Windsurf, and global or local installs).
File Types
| File pattern | Language | Notes |
|---|---|---|
*.ampscript, *.amp |
AMPscript | Always |
*.ssjs |
SSJS | Always |
*.html containing SFMC content |
SFMC (AMPscript / SSJS) | Auto-detected on open and on paste |
*.html without SFMC content |
html (unchanged) | Extension does not touch plain HTML |
*.hbs |
Handlebars (built-in) | MCN Handlebars intelligence, always in next mode |
Installation
Option 1: Install from VSIX
Build and package:
npm install npm run compile npm run packageIn VS Code: Extensions >
...> Install from VSIX... > select the generated.vsixfile
Option 2: Run in Extension Development Host
- Open the
vscode-sfmc-languagefolder in VS Code - Run
npm installandnpm run compile - Press
F5and choose Launch Client - Open a
.ampscript,.amp,.ssjs, or.hbsfile in the Extension Development Host
Configuration
| Setting | Default | Description |
|---|---|---|
sfmcLanguageServer.maxNumberOfProblems |
100 |
Maximum number of diagnostics reported per file |
sfmcLanguageServer.trace.server |
off |
Traces LSP communication (off, messages, verbose) |
sfmcLanguageServer.ssjsFileMode |
javascript |
How .ssjs files are interpreted: javascript (plain SSJS, default), auto (detect script-wrapped vs plain per file — SSJS Manager interop), or sfmc (force all .ssjs to SFMC) |
sfmcLanguageServer.enableFormatter |
true |
Enable the built-in Prettier-based formatter (AMPscript, SSJS, SFMC HTML, Handlebars, SQL) |
sfmcLanguageServer.formatterPromptDismissed |
false |
Set to false to force the coexistence prompt to reappear (auto-removed once answered; state persisted internally per workspace). Set to true as an admin opt-out — the extension then never prompts and never writes editor.defaultFormatter, and an explicit true is left untouched |
Architecture
vscode-sfmc-language/
├── client/ Language client (VS Code extension host)
│ ├── src/extension.ts Starts the LSP server, auto-detects SFMC HTML files
│ └── src/test/ Integration tests (completion, diagnostics, hover)
├── server/ Language server (Node.js LSP)
│ ├── src/server.ts LSP adapter — completions, hover, signature help, diagnostics
│ └── src/tsService.ts Embedded TypeScript language service for SSJS
├── syntaxes/ TextMate grammars for syntax highlighting
│ ├── ampscript.tmLanguage.json AMPscript + GTL grammar (includes SSJS embed rules)
│ ├── ssjs.tmLanguage.json SSJS grammar (extends JavaScript)
│ └── sfmc.tmLanguage.json SFMC HTML grammar (delegates to ampscript grammar)
├── snippets/ VS Code snippet definitions
│ ├── ampscript.snippets.json
│ ├── ssjs.snippets.json
│ └── gtl.snippets.json
├── resources/icons/ Language icons (amp, ssjs, sfmc)
├── language-configuration.json AMPscript bracket/comment/folding config
├── ssjs-language-configuration.json SSJS bracket/comment/folding config
└── package.json Extension manifest
Language intelligence is delegated to two shared packages:
sfmc-language-lsp— AMPscript and SSJS function catalog, validators, hover, completions, code actionsssjs-data— SSJS function/object metadata and TypeScript declarations (sfmc-globals.d.ts)
Development
npm install # Install root + client + server dependencies
npm run compile # TypeScript compilation (type-checked)
npm run watch # Watch mode for development
npm test # Run integration tests in VS Code instance
npm run lint # ESLint
npm run package # Package as .vsix
The extension uses esbuild for production bundling (vscode:prepublish), reducing load time and VSIX size.
Telemetry
This extension collects a small amount of anonymous usage telemetry to understand adoption and which features are used, so development can be prioritised. It is sent to a PostHog project hosted in the EU and contains no personally identifiable information, file contents, credentials, or business-unit / tenant identifiers.
What is collected: extension activation (with the configured target platform, .ssjs file mode, and booleans indicating whether a small allowlist of related SFMC extensions are co-installed), which SFMC language(s) a session edits, the formatter-coexistence outcome (and, on failed, a closed-enum stage plus sanitized error name/code — never the exception message, stack, or file paths), and whether a known conflicting AMPscript extension is active. Events are keyed by VS Code's anonymous machine id only. The full catalogue ships as telemetry.json in the extension root and is visible via the VS Code CLI --telemetry dump.
Opt out: telemetry is gated solely by VS Code's global telemetry setting. Set telemetry.telemetryLevel to off and nothing is sent — the extension respects isTelemetryEnabled and stops immediately when you change it. There is no separate extension setting to configure.
Notes
- AMPscript function metadata is sourced from the
ampscript-datashared package. - SSJS function metadata and TypeScript declarations are sourced from the
ssjs-datashared package. - This extension provides editing support for SFMC languages — it does not execute AMPscript or SSJS code.