@ambicuity/matterforge
v1.0.0
Published
Parse front-matter from a string or file. Fast, reliable and easy to use. Parses YAML front matter by default, but also has support for YAML, JSON, TOML or Coffee Front-Matter, with options to set custom delimiters.
Maintainers
Readme
@ambicuity/matterforge
Parse front-matter from a string or file. Fast, reliable, and easy to use. Parses YAML front matter by default, with built-in support for JSON and JavaScript (eval-gated). Extensible to TOML or any custom format via pluggable engines.
matterforge is for developers building content-heavy sites, docs platforms, static-site generators, MDX pipelines, and Edge-rendered publishing tools. Ships with TypeScript types, dual ESM/CJS exports, browser + edge-runtime support, and built-in schema validation.
Install
Requires Node.js ≥20. For older Node versions, use @ambicuity/[email protected].
npm install @ambicuity/matterforgeUpgrading from v1? See MIGRATION.md — the YAML engine swap and a couple of internals changed. Most consumers need no code changes.
Quick Start
import matter from '@ambicuity/matterforge';
const result = matter(`---
title: Hello
slug: home
---
Content here`);
console.log(result.data.title); // Hello
console.log(result.content); // Content hereOverview
Converts a string with front-matter, like this:
---
title: Hello
slug: home
---
<h1>Hello world!</h1>Into an object like this:
{
content: '<h1>Hello world!</h1>',
data: {
title: 'Hello',
slug: 'home'
}
}Why matterforge?
| Feature | matterforge | gray-matter |
| --- | --- | --- |
| Built-in schema validation (Zod, Valibot, ArkType) | ✅ | ❌ |
| Error code frames & diagnostic CLI | ✅ | ❌ |
| Deterministic stringify (sortKeys) | ✅ | ❌ |
| ESM + CJS dual exports (via tshy) | ✅ | ❌ |
| Edge-runtime support (Cloudflare/Vercel/Deno) | ✅ | ❌ |
| Browser support (verified in headless Chromium) | ✅ | ❌ |
| YAML 1.2 (via yaml) | ✅ | ❌ (YAML 1.1 via js-yaml) |
| TypeScript generics (typed data field) | ✅ | ❌ |
| Async file reading | ✅ | ❌ |
| Property-based + fuzz tests | ✅ | ❌ |
| Regex-free parsing | ✅ | ✅ |
| Custom engines | ✅ | ✅ |
| Tree-shakeable (ESM, sideEffects: false) | ✅ | ❌ |
| Node ≥20 (modern toolchain) | ✅ | ❌ (Node ≥14) |
API Summary
| Method | Purpose | Runtime |
|---|---|---|
| matter(input, options?) | Parse front matter from a string | All |
| matter.stringify(file, data, options?) | Convert data back into front matter | All |
| matter.read(filepath, options?) | Read and parse a file synchronously | Node.js |
| matter.readAsync(filepath, options?) | Read and parse a file asynchronously | Node.js |
| matter.test(string, options?) | Check whether a string has front matter | All |
| matter.clearCache() | Clear internal parse cache | All |
| matter.language(string, options?) | Detect language after first delimiter | All |
| matter.YAMLException | Deprecated. Alias for YAMLParseError from yaml. Removal planned in v3.0.0. | All |
| matter.MatterforgeError | Enriched error class with code frames | All |
| matter.MatterforgeValidationError | Validation error class with issues array | All |
Runtime Compatibility
| API | Node.js | Browser | Edge Runtime |
|---|:---:|:---:|:---:|
| matter() | ✅ | ✅ | ✅ |
| matter.stringify() | ✅ | ✅ | ✅ |
| matter.test() | ✅ | ✅ | ✅ |
| matter.language() | ✅ | ✅ | ✅ |
| matter.clearCache() | ✅ | ✅ | ✅ |
| matter.read() | ✅ | ❌ | ❌ |
| matter.readAsync() | ✅ | ❌ | ❌ |
Note:
read()andreadAsync()require thefsmodule and are only available in Node.js.
Usage
CommonJS (Node.js)
const matter = require('@ambicuity/matterforge');ESM / ES Modules
import matter from '@ambicuity/matterforge';
// Named type + error class imports:
import { MatterforgeError, MatterforgeValidationError } from '@ambicuity/matterforge';
import type { MatterforgeFile, MatterforgeOptions, MatterEngine } from '@ambicuity/matterforge';
// All runtime methods are available on the default export:
matter.read('./file.md');
matter.stringify('content', { title: 'Hello' });
matter.test('---\ntitle: X\n---');TypeScript
import matter from '@ambicuity/matterforge';
import type { MatterFile, MatterOptions, MatterEngine } from '@ambicuity/matterforge';
// Generic data typing:
interface FrontMatter {
title: string;
slug: string;
tags?: string[];
}
const file = matter<FrontMatter>('---\ntitle: Hello\nslug: home\n---\nContent');
file.data.title; // string — fully typed!Framework Examples
Next.js Blog Post
import matter from '@ambicuity/matterforge';
import fs from 'fs';
interface PostMeta {
title: string;
date: string;
tags?: string[];
}
export async function getPost(slug: string) {
const raw = await fs.promises.readFile(`./posts/${slug}.md`, 'utf8');
const { data, content } = matter<PostMeta>(raw);
return { metadata: data, body: content };
}Astro Content Collection
import matter from '@ambicuity/matterforge';
export function parsePost(raw: string) {
const { data, content } = matter<{
title: string;
date: string;
tags?: string[];
}>(raw);
return { metadata: data, body: content };
}Cloudflare Worker (Edge Runtime)
import matter from '@ambicuity/matterforge';
export default {
async fetch(request) {
const markdown = await getMarkdownFromKV(request);
const { data, content } = matter(markdown);
return new Response(JSON.stringify({ data, content }), {
headers: { 'Content-Type': 'application/json' }
});
}
};Schema Validation (Zod, Valibot, etc.)
matterforge includes a universal schema validation adapter protocol. You can pass any object with a .parse() or .safeParse() method (like a Zod schema) directly to options.schema.
import { z } from 'zod';
import matter from '@ambicuity/matterforge';
const PostSchema = z.object({
title: z.string(),
slug: z.string(),
published: z.boolean().default(false),
date: z.string().datetime()
});
const file = matter(raw, {
schema: PostSchema,
onValidationError: 'throw' // or 'return' to attach to file.errors
});
// file.data is fully validated and typed according to PostSchema!CLI
matterforge ships with a built-in CLI to validate markdown content across your repository.
# Parse all markdown files, run validation (if a config exists), and report errors with code frames
npx matterforge check "content/**/*.md"
# Show aggregate statistics (languages used, common fields, error counts)
npx matterforge stats "content/**/*.md"Create a matterforge.config.cjs to enable CLI schema validation:
// matterforge.config.cjs — CJS because matterforge v2 declares "type": "module"
const { z } = require('zod');
module.exports = {
schema: z.object({
title: z.string(),
date: z.string().datetime()
}),
ignore: ['**/drafts/**'] // Optional ignore patterns
};If your project does not use
"type": "module", you can keep the.jsextension.
Returned Object
matterforge returns a file object with the following properties.
Enumerable
file.data{Object}: the object created by parsing front-matterfile.content{String}: the input string, withmatterstrippedfile.excerpt{String}: an excerpt, if defined on the optionsfile.empty{String}: when the front-matter is "empty" (either all whitespace, nothing at all, or just comments and no data), the original string is set on this property.file.isEmpty{Boolean}: true if front-matter is empty.file.errors{Array}: an array of validation errors, ifonValidationError: 'return'is used.
Non-enumerable
file.orig{Buffer|String}: the original input string (or buffer). String in Edge/browser runtimes.file.language{String}: the front-matter language that was parsed.yamlis the default.file.matter{String}: the raw, un-parsed front-matter string.file.stringify{Function}: stringify the file by convertingfile.datato a string in the given language, wrapping it in delimiters and prepending it tofile.content.
API
matter
Takes a string or object with content property, extracts and parses front-matter from the string, then returns an object with data, content and other useful properties.
Params
input{Object|String}: String, or object withcontentstringoptions{Object}returns{Object}
Example
import matter from '@ambicuity/matterforge';
console.log(matter('---\ntitle: Home\n---\nOther stuff'));
//=> { data: { title: 'Home'}, content: 'Other stuff' }.stringify
Stringify an object to YAML or the specified language, and append it to the given string. By default, only YAML and JSON can be stringified. See the engines section to learn how to stringify other languages.
Params
file{String|Object}: The content string to append to stringified front-matter, or a file object withfile.contentstring.data{Object}: Front matter to stringify.options{Object}: Options to pass to matterforge and the underlying YAML engine (eemeli/yaml).returns{String}: Returns a string created by wrapping stringified yaml with delimiters, and appending that to the given string.
Example
console.log(matter.stringify('foo bar baz', {title: 'Home'}, { sortKeys: true }));
// results in:
// ---
// title: Home
// ---
// foo bar bazDeterministic Stringify Options
When stringifying YAML, you can pass options to enforce deterministic output:
sortKeys{Boolean|Function}: Sort object keys alphabetically. Can also be a custom compare function.lineWidth{Number}: Max line width before wrapping (default: 80).quotingType{String}:"'"or'"'.forceQuotes{Boolean}: Force all strings to be quoted.noRefs{Boolean}: Do not emit YAML aliases/anchors.
.read
Synchronously read a file from the file system and parse front matter. Returns the same object as the main function. Node.js only.
Params
filepath{String}: file path of the file to read.options{Object}: Options to pass to matterforge.returns{Object}: Returns an object withdataandcontent
Example
const file = matter.read('./content/blog-post.md');.readAsync
Asynchronously read a file from the file system and parse front matter. Returns a Promise. Node.js only.
Params
filepath{String}: file path of the file to read.options{Object}: Options to pass to matterforge.returns{Promise<Object>}: Returns a Promise resolving to an object withdataandcontent
Example
const file = await matter.readAsync('./content/blog-post.md');.test
Returns true if the given string has front matter.
Params
string{String}options{Object}returns{Boolean}: True if front matter exists.
.clearCache
Clears the internal parse cache. Useful when the same input string may have been modified or when you need to free memory.
Example
matter.clearCache();.YAMLException (deprecated)
Alias for YAMLParseError from the yaml package, exposed for backward compatibility with v1.x. Will be removed in v3.0.0; new code should import YAMLParseError directly.
Example
try {
matter('---\ninvalid: yaml: [\n---\ncontent');
} catch (err) {
if (err instanceof matter.YAMLException) {
console.error('YAML parse error:', err.message);
}
}Or, for new code:
import { YAMLParseError } from 'yaml';
try {
matter('---\ninvalid: yaml: [\n---\ncontent');
} catch (err) {
if (err instanceof YAMLParseError) {
console.error('YAML parse error:', err.message);
}
}Options
options.excerpt
Type: Boolean|Function
Default: undefined
Extract an excerpt that directly follows front-matter, or is the first thing in the string if no front-matter exists.
If set to excerpt: true, it will look for the frontmatter delimiter, --- by default and grab everything leading up to it.
Example
const str = '---\nfoo: bar\n---\nThis is an excerpt.\n---\nThis is content';
const file = matter(str, { excerpt: true });Results in:
{
content: 'This is an excerpt.\n---\nThis is content',
data: { foo: 'bar' },
excerpt: 'This is an excerpt.\n'
}You can also set excerpt to a function. This function uses the file and options that were initially passed to matterforge as parameters. The function can either mutate file.excerpt directly, or return a string:
Example (mutation pattern)
const file = matter(str, {
excerpt: function(file, options) {
file.excerpt = file.content.split('\n').slice(0, 4).join(' ');
}
});Example (return pattern)
const file = matter(str, {
excerpt: function(file, options) {
return file.content.split('\n').slice(0, 4).join(' ');
}
});options.excerpt_separator
Type: String
Default: undefined
Define a custom separator to use for excerpts.
console.log(matter(string, { excerpt_separator: '<!-- end -->' }));Example
The following HTML string:
---
title: Blog
---
My awesome blog.
<!-- end -->
<h1>Hello world</h1>Results in:
{
data: { title: 'Blog'},
excerpt: 'My awesome blog.',
content: 'My awesome blog.\n<!-- end -->\n<h1>Hello world</h1>'
}options.engines
Define custom engines for parsing and/or stringifying front-matter.
Type: Object Object of engines
Default: JSON, YAML and JavaScript are built-in. TOML and CoffeeScript can be added via custom engines (see examples below).
Engine format
Engines may either be an object with parse and (optionally) stringify methods, or a function that will be used for parsing only.
Examples
const toml = require('toml');
// As a function:
const file = matter(str, {
engines: {
toml: toml.parse.bind(toml),
}
});
// As an object with parse and stringify:
const file = matter(str, {
engines: {
toml: {
parse: toml.parse.bind(toml),
stringify: function() {
throw new Error('cannot stringify to TOML');
}
}
}
});options.language
Type: String
Default: yaml
Define the engine to use for parsing front-matter.
console.log(matter(string, { language: 'toml' }));Dynamic language detection
Instead of defining the language on the options, matterforge will automatically detect the language defined after the first delimiter and select the correct engine to use for parsing.
---toml
title = "TOML"
description = "Front matter"
categories = "front matter toml"
---
This is contentoptions.delimiters
Type: String | [String, String]
Default: ---
Open and close delimiters can be passed as a string (same for open/close) or an array of two strings.
Example:
// Same delimiter for open and close:
matter.read('file.md', { delimiters: '~~~' });
// Different open/close delimiters:
matter.read('file.md', { delimiters: ['<<<', '>>>'] });Would parse:
~~~
title: Home
~~~
This is the {{title}} page.Custom Engines
matterforge ships with built-in engines for YAML, JSON, and JavaScript (eval-gated). Coffee, CSON, and TOML support are intentionally not bundled — register them as user-supplied engines so consumers who don't need them don't pay the dependency cost.
TOML
import matter from '@ambicuity/matterforge';
import toml from 'toml';
import * as tomlify from 'tomlify-j0.4';
matter.engines.toml = {
parse: toml.parse.bind(toml),
stringify: (data) => tomlify.toToml(data, { space: 2 })
};
const file = matter('+++\ntitle = "Hello"\n+++\ncontent', { delimiters: '+++' });Custom format
Any object with a parse(str, options?) => object and optional
stringify(data, options?) => string works:
matter.engines.myformat = {
parse: (str) => ({ /* your parsed result */ }),
stringify: (data) => '/* your serialized result */'
};Error Handling
Parsing and validation errors are thrown by default. matterforge enriches these errors with diagnostic code frames pointing to the exact line and column where the error occurred.
try {
matter('---\ninvalid: yaml: [\n---\ncontent');
} catch (err) {
console.error(err.message);
// Invalid front matter: missed comma between flow collection entries
//
// 1 | ---
// > 2 | invalid: yaml: [
// ^
// 3 | ---
// 4 | content
}matterforge preserves the original error's prototype chain, meaning err instanceof matter.YAMLException will still work correctly for YAML syntax errors.
Error scenarios:
| Scenario | Error Class |
|---|---|
| Invalid YAML syntax | YAMLException (with code frames) |
| Invalid JSON syntax | SyntaxError (with code frames) |
| Schema validation failure | MatterforgeValidationError (with .issues array) |
| Unknown engine | MatterforgeError |
| Engine missing parse method | TypeError |
| Non-string, non-object input | TypeError |
Deprecated Options
These continue to work but will be removed in a future major version:
| Deprecated | Use Instead |
|---|---|
| options.delims | options.delimiters |
| options.lang | options.language |
| options.parsers | options.engines |
TypeScript Types
matterforge exports convenience type aliases for framework and library authors:
import type {
MatterFile, // Alias for MatterforgeFile<string, D>
MatterOptions, // Alias for MatterforgeOption<string, any>
MatterEngine, // Engine function or { parse, stringify? } object
MatterStringifyOptions, // Stringify-specific options
MatterforgeError, // Enriched error class with code frames
MatterforgeValidationError, // Validation error class with issues array
SchemaAdapter // Duck-typed schema adapter protocol
} from '@ambicuity/matterforge';Security
matterforge parses YAML with eemeli/yaml — the modern YAML 1.2 reference implementation. Defaults are safe:
- No prototype pollution —
__proto__,constructor, andprototypekeys are stripped from parsed objects. - No code execution from YAML — unknown tags (
!!js/function, custom tags) degrade to plain strings instead of executing. - Safe integer handling — values above
Number.MAX_SAFE_INTEGERare surfaced asBigIntor strings rather than silently losing precision.
JavaScript front matter uses
eval()and is disabled by default. You must opt in withmatter(input, { eval: true }). Only enable for trusted content.
matterforge parses front matter safely, but does not sanitize HTML, validate metadata schemas without one, or prevent unsafe rendering in your application. Use a sanitizer (e.g. DOMPurify) when rendering untrusted HTML, and a schema validator (e.g. Zod, Valibot, ArkType) for type-safe data — pass any .parse()-shaped object as options.schema.
To report a security issue, see SECURITY.md.
Quality bar
matterforge is verified by a 5-layer test strategy (see docs/testing.md for the full breakdown):
| Layer | Coverage | Run with |
| --- | --- | --- |
| Unit + user-perspective (~210 tests) | Fixtures, real-world corpus, CLI E2E, dual-export, schema, large-input | npm test |
| Property-based (8 invariants × ~300 cases) | Parse/stringify roundtrip, delimiters, BOM/CRLF, cache, options stability | npm run test:property |
| Multi-runtime | Headless Chromium (@vitest/browser) + edge VM (@edge-runtime/vm) | npm run test:browser · npm run test:edge |
| Fuzz | 100k random inputs against the parser, with crash-corpus replay | npm run test:fuzz |
| Performance + smoke | Regression vs v2.0.0 baseline; npm pack + install + consumer smoke | npm run bench:regression · npm run test:published |
Covered edge cases include: front matter not at the beginning of a file; empty and comment-only front matter; custom open/close delimiters; nested --- inside content and YAML values; code blocks containing ---; BOM at the start of files; CRLF and mixed line endings; YAML/JSON/TOML front matter; invalid input error paths; large files (10 MB+); deeply nested YAML; special YAML types (null, boolean, arrays, nested objects, multiline strings); browser environments without Buffer; Vinyl file objects; cache correctness and bulk eviction.
Project
- Contributing: see CONTRIBUTING.md for dev setup, test layers, and PR conventions.
- Architecture overview: see docs/architecture.md for the module map and data flow.
- Testing strategy: see docs/testing.md for the layered test approach.
- Security disclosures: see SECURITY.md.
- Changelog: see CHANGELOG.md (and MIGRATION.md for breaking changes).
- Issues / feature requests: GitHub issues.
Author
Ritesh Rana — [email protected]
License
Copyright © 2026, Ritesh Rana. Released under the MIT License.
