@bhouston/markdown-content
v1.0.4
Published
Markdown parsing and schemas for structured marketing and blog content
Downloads
681
Readme
@bhouston/markdown-content
Pure TypeScript markdown parsing with YAML front matter and Zod schema validation. Zero Node.js dependencies — runs in any JavaScript runtime.
Install
npm install @bhouston/markdown-contentQuick Start
Use createMarkdownParser to build a typed parser from Zod schemas, then call parseMarkdownDocument on raw markdown strings.
Marketing pages
import { createMarkdownParser, builtInPageParserSchemas } from '@bhouston/markdown-content';
const parser = createMarkdownParser(builtInPageParserSchemas);
const page = parser.parseMarkdownDocument(rawMarkdown);
// page.title, page.description, page.keywords[], page.sections[]Blog posts
import { createMarkdownParser, builtInBlogParserSchemas } from '@bhouston/markdown-content';
const parser = createMarkdownParser(builtInBlogParserSchemas);
const post = parser.parseMarkdownDocument(rawMarkdown);
// post.title, post.date, post.author, post.tags[], post.sections[]Custom parser
import { createMarkdownParser } from '@bhouston/markdown-content';
import * as z from 'zod';
const parser = createMarkdownParser({
pageFrontMatterSchema: z.object({ title: z.string(), description: z.string(), keywords: z.array(z.string()) }),
sectionFrontMatterSchema: z.object({ layout: z.string() }),
behavior: {
partSeparator: '|||', // default
implicitSectionFrontMatter: { layout: 'single' }, // default
},
});Document Format
Every file must open with a YAML front matter block. Additional --- blocks after the first define explicit sections. If no explicit section front matter follows, the remainder of the document becomes one implicit single section.
---
title: My Page
description: A page about things.
keywords: foo, bar
---
## Section One (implicit single section)
Regular markdown body.With explicit sections:
---
title: My Page
description: A page about things.
keywords: foo, bar
---
---
layout: intro
title: Welcome
subTitle: A great introduction
---
Hero text here.
---
layout: single
title: Details
---
Body content.Use ||| to split a section body into multiple parts (e.g. columns, grid cards, steps). ||| inside fenced code blocks is ignored.
---
layout: two-column
---
Left column content.
|||
Right column content.Section Layouts
All sections share common optional fields: title, subTitle, description, bgColor (default | muted | dark). Strict validation rejects unknown keys.
intro
For hero or prominent opening sections. Supports only layout, title, subTitle, and bgColor — does not accept description.
Required: title, subTitle
---
layout: intro
title: The Programmable Asset Backend
subTitle: Stop bloating Git.
bgColor: default
---
<cta-button text="Get Started →" href="/signup"></cta-button>single
A section with one markdown body.
---
layout: single
title: What Is This?
bgColor: muted
---
Body content here.two-column
Split the body with one ||| into exactly 2 columns.
Optional fields: columnGap (positive number), verticalAlign (top | center, default top), horizontalAlign (left | center, default left)
---
layout: two-column
title: Load It Your Way
verticalAlign: center
---
### Direct URL
Load assets directly from the media endpoint.
|||
### SDK
Use the typed SDK for richer integrations.three-column
Split the body with two ||| separators into exactly 3 columns.
Optional fields: columnGap (positive number), verticalAlign (top | center, default top), horizontalAlign (left | center, default left)
---
layout: three-column
title: Three Ways to Integrate
---
### Option A
First column.
|||
### Option B
Second column.
|||
### Option C
Third column.feature-grid
Split the body with ||| into 2 or more markdown cards.
Optional fields: columns (2 | 3 | 4, default 3)
---
layout: feature-grid
title: Built for Developer Workflows
columns: 4
---
### Open Asset Library
Discover public assets.
|||
### Not Just Storage
Upload, preview, and convert.steps
Split the body with ||| into 2 or more ordered steps.
Optional fields: variant (default | compact, default default)
---
layout: steps
title: Get Started
variant: default
---
### Create an Account
Sign up for free.
|||
### Upload Assets
Use the dashboard or CLI.Built-in Page Front Matter
Required fields: title, description
Optional fields: keywords (comma-separated string or array, defaults to [])
---
title: My Page
description: A short description.
keywords: foo, bar, baz
---Built-in Blog Front Matter
Required fields: title, description, date, author, and at least one tags entry.
Optional fields: shortTitle, subTitle, updatedDate, excerpt, imageUrl, audioUrl, keywords, tags, hide, canonical
Notes:
tagsshould be authored as a YAML array. Comma-separated strings are still accepted for compatibility; values are normalized to lowercase with spaces replaced by-.keywordsshould be authored as a YAML array. Comma-separated strings are still accepted for compatibility.hideaccepts boolean or boolean-string ("true"/"false"), defaults tofalse.- Dates are coerced via
new Date(value).
---
title: My Blog Post
description: What this post is about.
date: 2026-03-01
author: Jane Smith
tags:
- release-notes
- infrastructure
imageUrl: /images/hero.png
---
Post body here.API Reference
createMarkdownParser(options)
Returns a parser object with two methods:
| Method | Description |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| parseMarkdownDocument(raw) | Parses the full document. Requires leading page front matter. Returns page front matter merged with sections[]. |
| parseMarkdownSections(raw) | Parses only sections from the body (no page front matter). |
CreateMarkdownParserOptions
| Field | Type | Description |
| ------------------------------------- | --------- | ----------------------------------------------------------------------- |
| pageFrontMatterSchema | ZodType | Schema for the page-level front matter block. |
| sectionFrontMatterSchema | ZodType | Schema for each section front matter block. |
| labels.frontMatterLabel | string? | Label used in error messages. |
| labels.missingFrontMatterMessage | string? | Message when page front matter is absent. |
| behavior.partSeparator | string? | Part separator string (default \|\|\|). |
| behavior.implicitSectionFrontMatter | object? | Front matter for the implicit section (default { layout: 'single' }). |
Error Handling
import { MarkdownContentError, zodIssuesToMarkdownContentIssues } from '@bhouston/markdown-content';
try {
parser.parseMarkdownDocument(raw);
} catch (err) {
if (err instanceof MarkdownContentError) {
console.error(err.code); // e.g. 'INVALID_PAGE_FRONT_MATTER'
console.error(err.issues); // MarkdownContentIssue[]
}
}Error codes
| Code | When thrown |
| ------------------------------ | --------------------------------------------------------- |
| MISSING_PAGE_FRONT_MATTER | File does not start with --- |
| INVALID_FRONT_MATTER_BLOCK | YAML parse failure or malformed delimiters |
| INVALID_PAGE_FRONT_MATTER | Page front matter fails schema validation |
| INVALID_SECTION_FRONT_MATTER | A section front matter block fails schema validation |
| INVALID_IMPLICIT_SECTION | The implicit section front matter fails schema validation |
Utility Functions (blog)
| Function | Description |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------- |
| joinMarkdownSections(sections) | Joins all section markdown parts into a single string. |
| getBlogDisplayTitle(page) | Returns shortTitle ?? title. |
| getBlogDisplaySubtitle(page) | Returns subtitle for display, accounting for shortTitle / subTitle interplay. |
| calculateReadingTimeInMinutes(markdown) | Estimates reading time (default 225 wpm, 5.7 chars/word). |
| withPageMetadata(page, metadata) | Shallow-merges PageMetadata into a page object. |
| withBlogPostMetadata(page, metadata) | Merges id, directory, and computed readTime into a blog post, returning HydratedBlogPost. |
Exports
| Module | Key exports |
| --------------- | ---------------------------------------------------------------------------------------------------------------------- |
| parser | createMarkdownParser, CreateMarkdownParserOptions, ParseMarkdownDocumentOptions |
| frontmatter | parseFrontMatterBlock, ParsedFrontMatterBlock |
| errors | MarkdownContentError, MarkdownContentErrorCode, MarkdownContentIssue, zodIssuesToMarkdownContentIssues |
| core/schemas | All section schemas/types, MarkdownImageMetadataSchema, PageMetadataSchema, layout enums |
| builtins/page | builtInPageParserSchemas, PageFrontMatterSchema, PageSchema, Page, PageFrontMatter |
| builtins/blog | builtInBlogParserSchemas, BlogPostFrontMatterSchema, BlogPostPageSchema, HydratedBlogPost, BlogPost, helpers |
Requirements
- TypeScript 5.x with
moduleResolution: bundlerornode16 - No Node.js APIs; runs in browsers, edge runtimes, and servers
License
MIT
