A tiny, type-safe Markdown frontmatter parser for modern TypeScript projects. Built for workflows where good typing, predictable APIs, and small utilities go a long way. This package focuses on the essentials: clean ergonomics, minimal overhead, and a developer experience that integrates naturally into content-focused applications and tooling.
import { mattr } from "@stinobe/mattr";
try {
const parsed = mattr(fileContents);
} catch (error) {
// ...
}import { mattr } from "@stinobe/mattr";
type Schema = {
title: string;
description?: string;
};
try {
const parsed = mattr<Schema>(fileContents);
} catch (error) {
// ...
}Important
This only provides type safety during compiling, not during runtime
import { mattr } from "@stinobe/mattr";
import { z } from "zod";
const Schema = z.object({
title: z.string(),
description: z.optional(z.string()),
});
try {
// You can specify the type as well
// but that's not really necessary
// since it will be inferred from the schema option
const parsed = mattr(fileContents, { schema: Schema });
} catch (error) {
// ...
}Visit the website for more information about Zod
Note
Zod is not required to use this library
No mattr which option you're choosing, the output of the mattr function is always the same.
| Property | Description |
|---|---|
data |
The frontmatter as JSON ouput with given type (default: Record<string, unknown>) |
content |
The markdown content itself without the frontmatter |
excerpt |
The excerpt according to the settings as string, or null if no excerpt was found |
raw |
The file contents as they were passed to the mattr function |
Type: boolean | ExcerptFunction
Default: true
Enables and configures excerpt generation.
false- disabled (ignoringexcerptLengthandexcerptSeparator)true- uses default extration strategyExcerptFunction- custom extraction logic More info
When enabled (true), the excerpt is resovled using following rules
- If
excerptSeparatoris defined, takes content until seperator, otherwise passes entire content to next step - If
excerptLengthis defined, limits the number of charactors to the length of the excerpt - If none of the above is defined, take first paragraph
Type: string
Default: undefined
When defined with a non-empty string, takes content from input file after frontmatter untill separator
Type: number
Default: undefined
Maximum character length of the excerpt. When less than or equal to 0 this will throw an InvalidExcerptError.
Type: ZodType
Default: undefined
Takes a Zod-schema to validate frontmatter output against
I've added and exported a type in case you want to create a custom excerpt function. This contains some data passed on from options by the mattr function.
ExcerptFn param |
Description |
|---|---|
ctx.raw |
file contents |
ctx.content |
file content without frontmatter |
options.length |
excerptLength passed to mattr |
options.separator |
excerptSeparator passed to mattr |
import type { MattrExcerptFn } from "@stinobe/mattr";
const myCustomExcerptFunction: MattrExcerptFn = (ctx, excerptOptions) => {
// Do some magic
return "a string";
};You can also create a wrapper function where one of your parameters is an options object. For example if you would have a function traversing over files
import type {
MattrOptions,
MattrFile,
MattrAllowedTypes,
} from "@stinobe/mattr";
const myGlobFunction = async <T extends MattrAllowedTypes>(
globPath: string,
options: MattrOptions<T>,
): MattrFile<T>[] => {
const posts: MattrFile<T>[] = [];
for await (const post of glob(globPath)) {
try {
const parsed = mattr<T>(post, options);
posts.push(parsed);
} catch {
// Handle errors thrown
}
}
return posts;
};In some situations an erorr will be thrown so don't forget to do the handling correct
Thrown when:
- A Yaml block in a markdownfile is not closed
- The
parsefunction from theyamlpackage has thrown an error.
The error thrown by theparsefunction is passed as cause.
Thrown when:
- unsuccesful parse of the Zod schema, issues form
safeParseare passed to the error.
Thrown when:
excerptLengthis smaller than or equal to0