SomMark is a template language that compiles to multiple output formats — HTML, JSON, YAML, TOML, CSV, Markdown, XML, and more.
Write your content once with a single consistent block syntax. Add loops, compile-time logic, and file imports. Compile to whatever format your project needs.
v5 is the official stable release and the recommended version to start with. SomMark has reached its main goal — a single consistent block syntax that compiles to any output format. v5 is the last major version. Future releases will be minor updates or patches.
# CLI
npm install -g sommark
# Use it with your project
npm install sommarkteam.smark (HTML mapper)
[import = Card: "./components/Card.smark" !]
${
const team = [
{ name: "Adam", role: "Founder", avatar: "adam.jpg" },
{ name: "Hawa", role: "Engineer", avatar: "hawa.jpg" },
{ name: "Ilham", role: "Designer", avatar: "ilham.jpg" },
];
const year = new Date().getFullYear();
}$
[main = class: "team-page"]
[h1]Our Team[end:h1]
[section = class: "grid"]
[for-each = ${ team }$, as: "member"]
[Card = name: ${ member.name }$, role: ${ member.role }$]
[img = src: ${ member.avatar }$, alt: ${ member.name }$ !]
[end:Card]
[end:for-each]
[end:section]
[footer]© ${ year }$ SomMark[end:footer]
[end:main]components/Card.smark
[div = class: "card"]
[h2]v{name}[end:h2]
[p]v{role}[end:p]
[slot!]
[end:div]sommark --html team.smark<main class="team-page">
<h1>Our Team</h1>
<section class="grid">
<div class="card">
<h2>Adam</h2>
<p>Founder</p>
<img src="adam.jpg" alt="Adam">
</div>
<div class="card">
<h2>Hawa</h2>
<p>Engineer</p>
<img src="hawa.jpg" alt="Hawa">
</div>
<div class="card">
<h2>Ilham</h2>
<p>Designer</p>
<img src="ilham.jpg" alt="Ilham">
</div>
</section>
<footer>© 2026 SomMark</footer>
</main>| Format | CLI Flag |
|---|---|
| HTML | --html |
| Markdown | --markdown |
| MDX | --mdx |
| JSON | --json |
| JSONC | --jsonc |
| YAML | --yaml |
| TOML | --toml |
| CSV | --csv |
| XML | --xml |
| Text | --text |
Every tag is a block. Open with [name], close with [end:name]. A plain [end] also works, but [end:name] is recommended for readability.
[article = class: "post"]
[h1]Hello, SomMark[end:h1]
[p]Structured content, zero guesswork.[end:p]
[hr!]
[end:article]Self-closing blocks carry no body — append ! inside the tag:
[br!]
[img = src: "logo.png", alt: "Logo" !]
[input = type: "text", name: "email" !]${ }$ runs JavaScript at build time inside a sandboxed QuickJS VM. The static keyword is optional — ${ expr }$ and static ${ expr }$ are identical.
The example below targets the HTML mapper:
${
import pkg from "./package.json";
const built = new Date().toISOString();
}$
[footer]
${ pkg.name }$ v${ pkg.version }$ — built ${ built }$
[end:footer]Variables declared in one ${ }$ block are available in all blocks that follow in the same file.
[for-each] iterates over any array. The current item is accessed via the as: alias. ${ i }$ gives the zero-based index.
The example below targets the HTML mapper:
${
const links = ["Home", "Docs", "Blog", "Contact"];
}$
[nav]
[ul]
[for-each = ${ links }$, as: "link"]
[li][a = href: "#"]${ link }$[end:a][end:li]
[end:for-each]
[end:ul]
[end:nav]<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Docs</a></li>
<li><a href="#">Blog</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>When targeting JSON, YAML, or TOML, write key-value pairs using the tag name as the key. Type is inferred automatically — numbers stay numbers, true/false become booleans, everything else becomes a string.
The example below targets the JSON mapper:
[Object]
[username = "Adam" !]
[age = 25 !]
[score = 9.8 !]
[isAdmin = false !]
[deletedAt = null !]
[end:Object]{
"username": "Adam",
"age": 25,
"score": 9.8,
"isAdmin": false,
"deletedAt": null
}Body form also works — useful for longer values:
[Object]
[bio]Software developer based in Hargeisa.[end:bio]
[end:Object]Generate config from data with a loop. The example below targets the YAML mapper:
${
const services = [
{ name: "api", port: 8080, replicas: 3 },
{ name: "worker", port: 8081, replicas: 2 },
];
}$
[seq = "services"]
[for-each = ${ services }$, as: "s"]
[map-item]
[name = ${ s.name }$ !]
[port = ${ s.port }$ !]
[replicas = ${ s.replicas }$ !]
[end:map-item]
[end:for-each]
[end:seq]services:
- name: "api"
port: 8080
replicas: 3
- name: "worker"
port: 8081
replicas: 2Inject values at build time from your JavaScript config using p{}. Add a fallback with | in case a value is not set.
The example below targets the HTML mapper:
[div = class: "env-badge"]
Environment: p{NODE_ENV}
[end:div]
[footer]Built with p{engine | "SomMark"}[end:footer]new SomMark({
src,
format: "html",
placeholders: { NODE_ENV: "production" }
});Split templates into reusable .smark files. All [import] declarations must appear at the top of the file.
Static injection — insert a module with no props or body content:
[import = Nav: "./components/Nav.smark" !]
[import = Footer: "./components/Footer.smark" !]
[body]
[$use-module = Nav !]
[main]
[p]Page content.[end:p]
[end:main]
[$use-module = Footer !]
[end:body]Component block — pass props and body content. Inside the component, v{} reads the props and [slot] marks where the body goes:
[import = Card: "./components/Card.smark" !]
[Card = title: "Featured Post"]
This content fills the slot inside the card.
[end:Card]Card.smark:
[div = class: "card"]
[h2]v{title}[end:h2]
[div = class: "body"]
[slot!]
[end:div]
[end:div]Completely removed at build time — never appear in the output:
# single-line comment
###
multi-line comment
write as much as you need
###sommark -h, --help # show help message
sommark -v, --version # show version
sommark init # generate smark.config.js
sommark show config [file] # show resolved config
sommark show --path-config [file] # show path to active config file
sommark color on|off # help on enabling terminal colorssommark --html input.smark # compile to HTML
sommark --markdown input.smark # compile to Markdown
sommark --mdx input.smark # compile to MDX
sommark --xml input.smark # compile to XML
sommark --json input.smark # compile to JSON
sommark --jsonc input.smark # compile to JSONC
sommark --toml input.smark # compile to TOML
sommark --yaml input.smark # compile to YAML
sommark --csv input.smark # compile to CSV
sommark --text input.smark # compile to plain text
sommark --lex input.smark # print lexer token stream
sommark --parse input.smark # print parser ASTsommark --html -p input.smark # print output to console
sommark --html input.smark -o name ./dir/ # set output filename and directorysommark bundle ./dist/ # copy full bundle (JS + WASM)
sommark bundle ./dist/ --lite # lite bundle — no WASM
sommark bundle ./dist/ --only-lexer # lexer only
sommark bundle ./dist/ --only-parser # lexer + parser onlyNode.js
import SomMark from "sommark";
const sm = new SomMark({
src,
format: "html",
placeholders: { title: "My App" }
});
const output = await sm.transpile();Browser
import SomMark, { resolveBaseDir, renderCompiledHTML } from "sommark/browser";
const src = await fetch("./main.smark").then(r => r.text());
const sm = new SomMark({
src,
format: "html",
baseDir: resolveBaseDir("./templates/"),
});
renderCompiledHTML(document.getElementById("app"), await sm.transpile());Run sommark init to generate smark.config.js:
export default {
format: "html",
removeComments: true,
placeholders: {},
importAliases: { "@": "./" },
fallbackTarget: true,
outputValidator: null,
baseDir: null,
showSpinner: true,
security: {
allowRaw: true,
maxDepth: 5,
timeout: 5000,
allowFetch: true,
allowHttp: false,
allowedOrigins: [],
allowedExtensions: [],
sanitize: null,
},
outputDir: "./",
outputFile: "output",
};All ${ }$ expressions run inside a QuickJS sandbox — isolated from the host process:
| Setting | Default | Description |
|---|---|---|
allowRaw |
true |
Allow JavaScript in compile-time blocks |
maxDepth |
5 |
Maximum import nesting depth |
timeout |
5000 |
Max execution time per script (ms) |
allowFetch |
true |
Allow network requests from scripts |
allowHttp |
false |
Block plain HTTP — HTTPS only by default |
allowedOrigins |
[] |
Restrict fetch to specific domains |
allowedExtensions |
[] |
Restrict which file types can be imported |
sanitize |
null |
Custom function to sanitize HTML output |
Define your own tags and rendering logic with the Mapper API:
import SomMark, { Mapper } from "sommark";
const mapper = new Mapper();
mapper.register("alert", function({ content, props }) {
const type = props.type || "info";
return `<div class="alert alert-${type}">${content}</div>`;
});
const sm = new SomMark({
src: `[alert = type: "warning"]Check your config.[end:alert]`,
format: "html",
mapperFile: mapper,
});Full reference: docs/api/Mapper
| Topic | Location |
|---|---|
| Syntax Reference | docs/syntax/ |
| Output Formats | docs/languages/ |
| Core API | docs/api/Core/ |
| Browser API | docs/api/Browser/ |
| Mapper API | docs/api/Mapper/ |
| Sandbox API | docs/api/Sandbox/ |
| CLI Guide | docs/cli/ |
| Configuration | docs/cli/config.md |
MIT — Adam Elmi
