mrkeyoor.com_
Fri 07 Aug 22:57 UTC
npmCLI & Toolingupdated 07 Aug 2026

gray-matter

gray-matter splits a string into its front matter and its body. Give it a markdown file that starts with a fenced block of YAML and you get back an object with data (the parsed keys), content (everything after the block), plus extras such as excerpt, isEmpty and the raw matter string for debugging. YAML is the default, JSON and JavaScript engines ship with it, and you can register your own for TOML or anything else. It also goes the other way with stringify, which is what makes it useful for scripts that rewrite front matter rather than only reading it. This is the parser most static site generators reach for.

Verdict

The default front matter parser for good reasons: a small API, honest edge-case handling, and every static site tool already depends on it. Just treat it as frozen software, and keep it away from content you did not write.

API stability5/54.0.3 has been the published version since April 2021 and the last breaking change was v3; code written against it years ago still runs unchanged, which is the upside of a package nobody is changing
Docs3/5The README documents every option and returned property with runnable examples and a folder of them in the repo; what it never mentions is the module-level cache, its shared-reference behaviour, or that the javascript engine evaluates the front matter block
Maintenance2/5No npm release in over five years despite a repo push in June 2025, 54 open issues, and a hard dependency on js-yaml 3 through APIs that js-yaml 4 removed
Ecosystem5/58.1M weekly downloads and 4.5k stars, with the repository listing Gatsby, Astro, VitePress, Netlify, TinaCMS and Ant Design among the projects that use it; the pluggable engine option means TOML and CSON support is a few lines away

Use it if

  • You are building anything that reads markdown with YAML front matter, which is the case it handles well and has handled for a decade
  • You need to write front matter back out, for example a script that adds a field across a content directory, since stringify round-trips
  • Your content uses something other than YAML, or non-standard delimiters, both of which are configurable through engines and delimiters
  • You want an excerpt extracted in the same pass, either up to the next delimiter or through a separator such as an HTML comment
Skip it if

Setup reality

Install and call it, there is nothing to configure. The behaviour worth knowing before you ship is the module-level cache: results are stored in matter.cache keyed by the entire content string whenever you call it without options, so a build that parses ten thousand files keeps ten thousand file bodies in memory until you call matter.clearCache(). Worse, a cache hit returns a shallow copy, meaning file.data is the same object every time, so mutating it poisons every later parse of that string. Two smaller traps: matter.test only checks that the string starts with the delimiter, so a document that opens with a horizontal rule reads as having front matter, and matter.read is synchronous readFileSync, which will not thread through an async pipeline.

Patterns

Split front matter from contentparse-string

const matter = require('gray-matter');

const { data, content } = matter(
  '---\ntitle: Home\n---\nOther stuff'
);
// data    => { title: 'Home' }
// content => 'Other stuff'

A file with no front matter still returns successfully, with data set to an empty object rather than throwing.

Read and parse a file from diskread-file

const file = matter.read('./content/blog-post.md');
console.log(file.path, file.data.title);

This is readFileSync under the hood, so use fs.promises.readFile plus matter(str) when you need it async.

Import it from ESM or TypeScriptesm-typescript

import matter from 'gray-matter';

const file = matter(source);

The package is CommonJS only, so this depends on your bundler or Node's interop; the README's TypeScript form is import matter = require('gray-matter').

Check whether a string has front mattertest-for-matter

if (matter.test(source)) {
  const { data } = matter(source);
}

It only checks the opening delimiter, so a document starting with a --- horizontal rule returns true; use file.isEmpty on the parsed result to be sure.

Write front matter back outstringify

const updated = matter.stringify('foo bar baz', { title: 'Home' });
// ---
// title: Home
// ---
// foo bar baz

The YAML is regenerated by js-yaml, so key order, quoting and comments from the original block are not preserved.

Add a field to an existing fileupdate-in-place

const fs = require('fs');

const file = matter.read('./post.md');
file.data.updated = new Date().toISOString();
fs.writeFileSync('./post.md', matter.stringify(file.content, file.data));

Mutating file.data on a cached parse also mutates what later calls see, so clear the cache or pass an options object in bulk scripts.

Pull an excerpt out in the same passexcerpt

const file = matter(source, { excerpt: true });
console.log(file.excerpt);

With excerpt: true the excerpt runs up to the next delimiter, and it stays part of file.content rather than being removed from it.

Use a custom excerpt separatorexcerpt-separator

const file = matter(source, { excerpt_separator: '<!-- more -->' });

The separator convention most CMS content already uses, which saves rewriting your files to match the parser.

Parse non-standard delimiterscustom-delimiters

const file = matter.read('file.md', { delimiters: '~~~' });
// or an explicit open and close pair:
const other = matter(source, { delimiters: ['<!--meta', 'meta-->'] });

delims is the deprecated spelling of the same option and still works; delimiters is the documented name.

Register a TOML enginetoml-engine

const TOML = require('@iarna/toml');

const file = matter(source, {
  engines: {
    toml: {
      parse: TOML.parse.bind(TOML),
      stringify: TOML.stringify.bind(TOML),
    },
  },
  language: 'toml',
});

An engine can be a bare function when you only parse; supply stringify too or matter.stringify throws for that language.

Let the file declare its own languagelanguage-detection

const source = '---toml\ntitle = "TOML"\n---\nbody';
const file = matter(source, { engines: { toml: TOML.parse.bind(TOML) } });
console.log(file.language); // 'toml'

Detection reads whatever follows the opening delimiter, which is also how a hostile file reaches the eval-based javascript engine; pin options.language when the input is untrusted.

Stop the cache from growing without limitclear-cache

for (const filepath of files) {
  const file = matter.read(filepath);
  index.push({ path: filepath, ...file.data });
}

matter.clearCache();

Caching only happens when no options object is passed, so a long-running process that always passes options never accumulates; one that does not, does.

Alternatives

PackageRegistryPick it when
front-matternpmYou want a smaller YAML-only parser with no eval-capable engine and no internal cache
vfile-matternpmYou already use unified, remark or another vfile-based pipeline and want the matter attached to the file you are passing along
yamlnpmYou would rather split on the delimiters yourself and use a maintained YAML parser with source positions and comment preservation