ejs

Embedded JavaScript templates for rendering HTML with plain JS control flow

Library
npm
v7.0.2
8,127 stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
86 /100 Excellent
Development Activity 88
Maintenance 76
Community 80
Maturity 60
Momentum 40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
78 /100 Good
Architecture 75
Code Quality 78
Innovation 72
Learning Curve 85

ejs (Embedded JavaScript templates) is a templating engine that lets you generate HTML markup using plain JavaScript inside familiar <% %> tags rather than a bespoke templating syntax. It compiles templates into JavaScript functions ahead of render time, supports escaped and raw output, includes, custom delimiters, and both synchronous and async rendering, and runs identically on the server (Node.js, CommonJS or ESM) and in the browser.

Because its control-flow tags execute real JavaScript, there’s no separate mini-language to learn for loops, conditionals, or expressions — if you know JS, you already know ejs. It ships a CLI for one-off rendering from the command line, complies with the Express view-engine contract via ejs.__express, and as of v6 removes the CJS/ESM interop shim that previously broke under ESM-aware bundlers like Vite, esbuild, and Bun.

What You Get

  • Four core rendering entry points — ejs.compile(), ejs.render(), ejs.renderFile(), and the Template class — covering pre-compilation, one-shot rendering, and file-based rendering with a Node-style (err, str) callback
  • Full tag set: <% (scriptlet), <%= (escaped output), <%- (raw output), <%# (comment), <%_/_%> (whitespace slurping), and -%> (newline trim), plus configurable custom delimiters
  • Runtime include() support that resolves relative or absolute paths, an array of views roots, or a custom includer callback, with per-include caching via options.cache
  • Drop-in Express view-engine compatibility through ejs.__express (aliased to renderFile), so app.set('view engine', 'ejs') works without extra glue code
  • A bundled CLI (bin/cli.js) for rendering templates from files or piped stdin with JSON data input, output-file redirection, and custom delimiter flags
  • Dual CJS/ESM publish targets (require('ejs') and import ejs from 'ejs') plus a prebuilt browser/UMD bundle (ejs.min.js) for client-side rendering with no bundler required
  • A v6 prototype-pollution mitigation (unsafePrototypeLocals opt-in) that changed how locals resolve identifiers through the prototype chain by default

Common Use Cases

  • Server-rendered HTML views in Express apps, using ejs.__express as the configured view engine for .ejs templates
  • Generating one-off HTML fragments or emails from a data object via ejs.render(str, data) without touching the filesystem
  • Static site or build-time HTML generation, precompiling templates with ejs.compile() and caching the resulting function for repeated renders
  • Client-side templating in the browser by loading ejs.min.js and rendering markup directly from JSON data without a server round-trip
  • Composing multi-file page layouts (headers/footers/partials) with <%- include('partial', {data}) %> resolved relative to the including template

Under The Hood

Architecture: ejs is a single-pass template compiler. Template (lib/esm/ejs.js) parses the source string against _REGEX_STRING — the regex that recognizes all tag delimiters — splitting it into literal text and JS code segments, then assembles those segments into the body of a new Function(...) that, when called with a locals object, returns the rendered string. ejs.compile() wraps this in the public API and returns the compiled function directly; ejs.render() and ejs.renderFile() layer caching (handleCache/tryHandleCache) and Express-specific options normalization (pulling views/cache out of data.settings for Express 2-4 compatibility) on top of the same Template machinery. Includes are resolved at template-compile time via getIncludePath/resolvePaths, walking the configured root/views array or an includer callback, and are themselves compiled and optionally cached as nested Template instances.

Tech Stack: Pure JavaScript with zero runtime dependencies — package.json lists only devDependencies (jake as the build/test runner, mocha for tests, eslint, typescript for type declarations, uglify-js/browserify for the client bundle). The published package exposes dual entry points (lib/cjs/ejs.js for require, lib/esm/ejs.js for import) generated from a single ESM source via a jakefile-driven CJS compile step, plus a browser UMD build (ejs.min.js) referenced by the browser/jsdelivr/unpkg package.json fields. engines targets Node >=0.12.18, reflecting the project’s long-running commitment to backward compatibility.

Code Quality: The test suite (test/ejs.js, 1223 lines, TDD-style suite/test blocks run via Mocha with --check-leaks) covers roughly 125 cases spanning tag parsing, includes, caching, async rendering, Express option-passing quirks, and error-stack rewriting (rethrow). Public functions carry JSDoc annotations (@param, @return, @public/@private) throughout lib/esm/ejs.js, and error handling favors explicit, descriptive throws (e.g. rethrow rewrites the generated function’s stack trace back to template line numbers) over silent failures. A dedicated SECURITY.md documents the trust boundary explicitly: ejs executes arbitrary JavaScript by design, so passing unsanitized user input as template source (not data) is documented as inherently unsafe rather than treated as a bug.

API Design: The four-function surface (compile, render, renderFile, Template) maps directly onto the three ways templates get used (precompiled reuse, one-shot string render, file-based render), keeping the common case — ejs.render(str, data) — a single call with sensible defaults. The options object is consistently named and documented (filename, cache, rmWhitespace, strict, async, etc.) across all three functions rather than diverging per entry point, and the Express-compatibility shimming in renderFile is isolated so it doesn’t leak into the plain render path. The tradeoff is that some behavior (like with()-based local-variable scoping vs. strict/destructuredLocals) requires reading the options table to understand, since JS’s own scoping rules don’t map 1:1 onto template locals.

Used by 14 apps in this directory

JavaScript
64%
AGPL 3.0

overleaf

Collaboration · Productivity

18,266

Open-source, real-time collaborative LaTeX editor with sandboxed compilation and full TeXLive support for self-hosted academic and research teams.

View details
84
Repo Health
80
Technical
59
Dependency
Built with
JavaScript 64%
TypeScript 30%
Updated 1 months ago
TypeScript
96%
MIT

Payload CMS

Blogging · CMS · Developer Tools

45,458

The open-source, Next.js-native headless CMS that lives inside your /app folder and gives you a full TypeScript backend instantly.

View details
92
Repo Health
88
Technical
65
Dependency
Built with
TypeScript 96%
Updated 6 days ago
JavaScript
50%
AGPL 3.0

QRev

AI Agents · CRM

364

Open source AI-first sales platform that replaces Salesforce with autonomous agents handling prospecting, outreach, and lead management at scale.

View details
39
Repo Health
68
Technical
64
Dependency
Built with
JavaScript 50%
Python 28%
TypeScript 14%
Updated 8 months ago
TypeScript
72%
Apache 2.0

Supabase

Authentication · Databases · Developer Tools

113,827

The open-source Postgres development platform that replaces Firebase with authentication, real-time APIs, edge functions, storage, and vector embeddings — all built on PostgreSQL.

View details
90
Repo Health
91
Technical
62
Dependency
Built with
TypeScript 72%
MDX 26%
Updated 6 days ago
TypeScript
80%
AGPL 3.0

VoidAuth

Authentication · Security

2,889

Self-hosted SSO with OIDC, LDAP, passkeys, and proxy auth for your entire self-hosted stack

View details
83
Repo Health
77
Technical
72
Dependency
Built with
TypeScript 80%
HTML 14%
Updated 1 weeks ago

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers