ProvenPin Install

gray-matter front matter parsing validated with Zod: working versions (tested 2026-10-15)

Parse Markdown front matter with gray-matter, validate and coerce it with a Zod v4 schema, read excerpts and write posts back with matter.stringify.

passed4 passed · 0 failed2026-10-15 17:10 UTC
Status
passed
Last passed
2026-10-15 17:10 UTC
Tests
4 / 4
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
gray-matter@4.0.3zod@4.6.7@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38775053802

The code

Exactly the files that passed. Your agent can pull them with get_recipe("gray-matter-zod-frontmatter") (install), or see them on GitHub.

package.json
{
  "name": "gray-matter-zod-frontmatter",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "gray-matter": "4.0.3",
    "zod": "4.6.7"
  },
  "devDependencies": {
    "@types/node": "26.6.5",
    "tsx": "4.23.15"
  }
}
src/posts.ts
import matter from "gray-matter";
import { z } from "zod";

export const Frontmatter = z.object({
  title: z.string().min(1),
  date: z.coerce.date(),
  tags: z.array(z.string()).default([]),
  draft: z.boolean().default(false),
  slug: z.string().regex(/^[a-z0-9-]+$/).optional(),
});

export type Post = z.infer<typeof Frontmatter> & { slug: string; body: string; excerpt: string };

export class FrontmatterError extends Error {}

function slugify(title: string) {
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
}

export function parsePost(source: string): Post {
  // Passing options also disables gray-matter's string-keyed cache, so edits are always re-read.
  const file = matter(source, { excerpt: true, excerpt_separator: "<!-- more -->" });
  const fm = Frontmatter.safeParse(file.data);
  if (!fm.success) throw new FrontmatterError(z.prettifyError(fm.error));
  return {
    ...fm.data,
    slug: fm.data.slug ?? slugify(fm.data.title),
    body: file.content.trim(),
    excerpt: (file.excerpt ?? "").trim(),
  };
}

export function publishedSorted(sources: string[]): Post[] {
  return sources
    .map(parsePost)
    .filter((p) => !p.draft)
    .sort((a, b) => b.date.getTime() - a.date.getTime());
}

export function writePost(body: string, data: z.input<typeof Frontmatter>): string {
  return matter.stringify(body, Frontmatter.parse(data));
}
test/posts.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { parsePost, publishedSorted, writePost, FrontmatterError } from "../src/posts.ts";

const a = `---
title: Hello World
date: 2026-03-01
tags: [intro, news]
---
Short intro.
<!-- more -->
The rest.
`;
const b = `---
title: Draft Thing
date: 2026-04-01
draft: true
---
wip`;
const c = `---
title: Later Post
date: "2026-05-10T12:00:00Z"
slug: later
---
Body`;

test("parses, coerces dates, defaults and builds a slug", () => {
  const p = parsePost(a);
  assert.equal(p.title, "Hello World");
  assert.equal(p.slug, "hello-world");
  assert.ok(p.date instanceof Date);
  assert.equal(p.date.toISOString().slice(0, 10), "2026-03-01");
  assert.deepEqual(p.tags, ["intro", "news"]);
  assert.equal(p.draft, false);
  assert.equal(p.excerpt, "Short intro.");
});

test("filters drafts and sorts newest first", () => {
  assert.deepEqual(publishedSorted([a, b, c]).map((p) => p.slug), ["later", "hello-world"]);
});

test("invalid front matter throws a readable error", () => {
  assert.throws(() => parsePost("---\ndate: not-a-date\n---\nx"), (e: unknown) => {
    assert.ok(e instanceof FrontmatterError);
    assert.match((e as Error).message, /title/);
    assert.match((e as Error).message, /date/);
    return true;
  });
});

test("writePost round-trips through parsePost", () => {
  const src = writePost("Hi there", { title: "Round Trip", date: "2026-06-01", tags: ["x"] });
  assert.match(src, /^---\n/);
  const p = parsePost(src);
  assert.equal(p.title, "Round Trip");
  assert.equal(p.body, "Hi there");
  assert.deepEqual(p.tags, ["x"]);
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.