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.
# 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