Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>EJS ColorizerNew to Visual Studio Code? Get it now.
EJS Colorizer

EJS Colorizer

connorontheweb

|
133 installs
| (0) | Free
| Sponsor
Complete EJS language support for VS Code — semantic token highlighting, include navigation, folding, completions, hover, and diagnostics.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

EJS Colorizer

Complete EJS language support for Visual Studio Code - built on the Semantic Tokens API so every token is colored correctly regardless of how complex the template gets.

VS Marketplace License TypeScript

Latest Release

Why v2

Previous versions used TextMate grammars. TextMate grammars are stateless regex engines - when EJS tags appear inside HTML elements, the HTML grammar's internal state machine gets corrupted and downstream tokens (closing tags, attribute values, etc.) end up the wrong color. The only real fix is to step outside the TextMate system.

v2 replaces that with a full semantic token provider: a stateful HTML scanner runs over a "placeholder" version of the document (EJS blocks replaced with equal-length spaces so offsets are preserved), then a purpose-built JS lexer handles the content inside each EJS block. Every token is placed at its exact document offset. </p> is always the right color.

Features

Accurate syntax highlighting

  • All EJS tag types: <%, <%=, <%-, <%#, <%_, -%>, _%>
  • HTML — tag names, attribute names, attribute values, comments, DOCTYPE
  • JavaScript inside EJS blocks — keywords, strings, numbers, comments, operators
  • CSS inside <style> blocks — full CSS colorization, completions, and hover
  • Works correctly at every nesting level and with EJS inside attribute values

Outline / Document Symbols

  • The Outline panel and breadcrumbs show all EJS blocks as navigable symbols: control-flow blocks, include() calls, variable declarations, and output expressions

Emmet abbreviations

  • Emmet expansion works inside HTML regions of EJS files (div.container>ul>li*3 → full HTML structure)

Prettier formatting

  • Format Document (Shift+Alt+F) uses the Prettier CLI from your project's node_modules (or PATH)
  • Respects your .prettierrc; use @prettier/plugin-ejs for full EJS-aware formatting

Include navigation

  • File-path completions inside include() — lists .ejs/.html files relative to the current file, with directory traversal
  • Clickable document links on every include() path (Ctrl/Cmd+click)
  • Go to Definition (F12) jumps to the first line of the included file
  • Warning diagnostic when an include() path does not exist on disk
  • All four are scoped to EJS tags, so include('…') written in body text, a <script> block or an HTML comment is left alone. Inside a <%# %> comment, links and F12 still work but the missing-path warning is suppressed — a warning about an include you commented out is noise
  • Completions additionally count a tag you are still typing, since a tag with no %> yet is the only state completions are ever useful in. The other three stay quiet until the tag is closed

JS syntax diagnostics

  • Joins all scriptlet/output block contents into a single synthetic program (matching EJS runtime behaviour) before checking syntax — so split if/for/while constructs spanning multiple blocks are accepted correctly

Folding

  • Multi-line EJS blocks fold as a unit
  • Matching if/for/while open-blocks ({) fold to their corresponding } close-block

EJS comment toggle

  • Cmd+/ (Mac) / Ctrl+/ (Windows/Linux) comments each selected line in the style its position calls for, or uncomments if every selected line is already commented. Works with multiple cursors

    Where the line is What you get
    Markup or text <p>Hi</p> → <%# <p>Hi</p> %>
    One whole EJS tag <% if (user) { %> → <%#if (user) { %>
    Markup carrying a tag <p><%= x %></p> → <% if (false) { %><p><%= x %></p><% } %>
    Inside a scriptlet body const a = 1; → // const a = 1;
    Only a delimiter (<%, %>) left alone — select the whole tag instead
  • The styles mix freely in one selection, and every one of them toggles back to exactly what you started with

  • Selecting a whole <% … %> scriptlet comments its body and leaves the tag standing

  • Shift+Alt+A (Toggle Block Comment) is region-aware too, over any selection — including part of a line. A span of JavaScript inside a scriptlet gets /* … */, a selection of markup gets one wrap around the whole thing, and a selection carrying a tag gets one dead branch around the whole thing

  • Other routes to commenting — the Command Palette entries, and Vim keymaps where gc drives VS Code's built-in — fall back to static tokens that can't tell markup from JavaScript. Those are set to the dead branch, which compiles on any markup selection with or without tags in it, rather than to <%#, which does not

  • A warning diagnostic fires automatically on any <%# %> block that contains a nested EJS tag — the inner %> would close the comment early; see Known Limitations

Completions

  • EJS tag snippets triggered by < or <%
  • Includes if, if/else, for, forEach, and include block templates

Hover documentation

  • Hover over any EJS delimiter to see what it does

Tag Reference

Tag Purpose
<% %> Scriptlet — executes JS, no output
<%= %> Output, HTML-escaped
<%- %> Output, unescaped (trusted content only)
<%# %> Server-side comment — not sent to browser
<%_ %> Whitespace-slurp scriptlet
-%> Trims the trailing newline
_%> Trims all trailing whitespace
<%% Outputs a literal <% string
%%> Outputs a literal %> string

EJS Version Compatibility

Compatible with EJS v5 and v6. EJS v6.0 introduced no template syntax changes - all tags work identically.

One runtime behavior to be aware of when upgrading to EJS v6: locals are now shallow-copied into a null-prototype object before template execution as a security hardening measure (prototype-pollution protection). This means inherited properties from class instances or Object.create() results are no longer accessible at the top level inside with. If your templates use inherited properties from class instances passed as locals, add { unsafePrototypeLocals: true } to your EJS render options to restore the previous behavior.

Requirements

VS Code 1.116.0 or later. No other dependencies — vscode-html-languageservice is bundled.

Extension Settings

Semantic highlighting is enabled for EJS files automatically. Everything else works with no setup — the settings below are escape hatches, and each one defaults to the behaviour you get without touching it.

Setting Default What it does
ejsColorizer.diagnostics.jsSyntax.severity error Severity for JavaScript syntax errors in the joined program
ejsColorizer.diagnostics.missingInclude.severity warning Severity for include() paths that don't resolve on disk
ejsColorizer.diagnostics.brokenComment.severity warning Severity for <%# %> comments that terminate early

All three accept error, warning, information, hint, or off.

hint is usually what you want instead of off. It keeps the check running and leaves the underline in the editor, but adds nothing to the Problems panel — so a check that's occasionally wrong stops filling up your panel without you losing it entirely.

Settings are read per folder, so a multi-root workspace can turn a check off for one project in that folder's .vscode/settings.json and keep it everywhere else.

When you'd turn each one down:

  • jsSyntax — this check reconstructs the whole template as the single program EJS builds from it, which is what lets an if opened in one block close in another. If your templates legitimately don't form a standalone parse, you'll get an error that no correct EJS can clear.
  • missingInclude — include paths are resolved the way the EJS runtime resolves them, relative to the including file. Projects rendering with a custom root option, or assembling partials in a build step, have paths that work at runtime and can't be resolved from the editor. Document links and Go to Definition keep working regardless of this setting.
  • brokenComment — reports a real EJS parser limitation rather than a style preference, so this is the least likely of the three to want silencing.

Known Limitations

EJS comment tag (<%# %>) terminates at the first %>

This extension makes it easy to create EJS comments using the <%# %> tag, but be aware of a fundamental EJS parser limitation: the comment closes at the first %> encountered — there is no escape sequence and no nesting.

This means the following is broken at the EJS level, regardless of what this extension does:

<%# <a href="/<%= section %>">Link</a> %>

EJS sees the %> inside <%= section %> as the comment closer. Everything after it — ">Link</a> %> — is emitted as raw text output.

What to watch out for:

  • Any <%# %> comment you write by hand around an output expression (<%= %>), unescaped output (<%- %>), or any other EJS tag will terminate early
  • Under EJS 6 the symptom is a hard failure — Could not find matching close tag for "<%#" — and the page does not render at all. Under EJS 5 it was stray text and partial HTML in the output
  • Pure HTML lines with no EJS tags inside are safe to comment this way

Cmd+/ and Shift+Alt+A never produce this. A line that is entirely one tag gets a # marker on the tag itself, and a line mixing markup with a tag gets the dead-code branch below. The <%# %> wrap is used only where it is safe. Every result is verified by compiling it with the real EJS engine — see npm test.

Safe alternative for mixed HTML + EJS lines:

Wrap the block in a dead-code if branch — EJS compiles it but never executes it:

<% if (false) { %>
  <a href="/<%= section %>">Link</a>
<% } %>

This is the only EJS-native way to suppress a line that contains EJS tags without triggering parser errors. Cmd+/ applies it for you, on a single line, whenever the line carries a tag.

A line that is only a delimiter has no comment style of its own

In a tag split across lines, the <% and %> lines are not markup and not JS, and no single-line comment is correct on either. Cmd+/ leaves them untouched rather than damaging the tag. Select the whole scriptlet and its body comments out with //, tag intact.

Include completions can fire in output text if a tag above is unterminated

include(' in body text does not open a file picker — unless the document already contains a tag with no %> anywhere after it, in which case everything up to the next %> reads as that tag's contents. This is the same graceful degradation the include diagnostics have: an unterminated tag pairs with the next %> in the file. Closing the tag settles it.

<% // include('x') %> is still reported as a missing include

An include disabled with a JavaScript line comment sits inside a real EJS block. Excluding it would mean parsing JS comments to find out which code is live, which is the kind of guesswork this extension resolves with scope instead.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft