npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mdx-tsc

v0.3.0

Published

A tsc-style CLI (and language server) that type-checks MDX

Readme

mdx-tsc

A tsc-style CLI that type-checks your MDX.

MDX lets you use imports, JSX components, and {expressions} inside Markdown — but until now there was no way to type-check it in CI. Editors could show errors (via the official mdx-analyzer), yet nothing failed your build when an MDX file passed the wrong prop to a component or referenced a field that doesn't exist.

mdx-tsc is that missing piece. Its mdx-tsc command is a drop-in tsc: it type-checks .mdx alongside your .ts/.tsx, reports errors at the exact spot in the MDX source, and exits non-zero when something is wrong.

$ mdx-tsc --project tsconfig.json
posts/intro.mdx(8,9): error TS2322: Type 'number' is not assignable to type 'string'.
posts/intro.mdx(10,21): error TS2339: Property 'toUpperCase' does not exist on type 'number'.

It is built on the same Volar-based engine as the official MDX editor tooling (@mdx-js/language-service) driven through Volar's runTsc, so its results match what you see in your editor.

Install

npm install --save-dev mdx-tsc typescript

typescript is a peer dependency — mdx-tsc uses whichever version your project already has.

Usage

mdx-tsc forwards every argument to tsc, so anything tsc accepts works:

mdx-tsc --project tsconfig.json     # check once (CI)
mdx-tsc -p tsconfig.json --watch    # re-check on change

Add it to your scripts:

{
  "scripts": {
    "typecheck": "mdx-tsc --project tsconfig.json"
  }
}

A tsconfig that checks MDX

Point include at your .mdx files and turn on MDX checking:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react",
    "module": "preserve",
    "moduleResolution": "bundler",
    "allowJs": true,
    "checkJs": true,
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true,
  },
  // `mdx-tsc` always type-checks. `checkMdx` governs only the *official* MDX
  // editor extension — leave it `true` if that's your only editor tooling, or
  // set it `false` when using the mdx-tsc extension (see "Editor support").
  "mdx": { "checkMdx": true },
  "include": ["**/*.mdx", "**/*.ts", "**/*.tsx"],
}

Frontmatter typing

mdx-tsc can type each document's frontmatter against a schema you declare, matched by glob, in the same "mdx" section of your tsconfig that holds checkMdx:

{
  "mdx": {
    "checkMdx": true,
    "frontmatter": {
      "content/blog/**/*.mdx": "./src/content.ts#BlogFrontmatter",
      "content/docs/**/*.mdx": "./src/content.ts#DocFrontmatter",
    },
  },
}

Each value is ./module#ExportedType. The referenced type can be a plain TypeScript type or a Zod inferred type — mdx-tsc only reads the static type, it never runs your schema:

// src/content.ts
export interface BlogFrontmatter {
  title: string;
  date: string;
  tags: string[];
}

import { z } from "zod";
export const docSchema = z.object({ title: z.string(), order: z.number() });
export type DocFrontmatter = z.infer<typeof docSchema>;

Both the frontmatter values and body usages of frontmatter are then checked against the schema:

---
title: Hello
date: 20260706 # error: number is not assignable to string
tags: [intro]
author: Jane # error: 'author' does not exist in type 'BlogFrontmatter'
---

# {frontmatter.title}

Posted {frontmatter.publishedAt}. {/* error: publishedAt is not on BlogFrontmatter */}

Wrong value types, unknown keys, and missing required fields are reported on the offending line of the YAML. Files that match no glob keep an untyped (any) frontmatter, so this is opt-in per content collection.

Provided components (MDXProvider / mdx-components.tsx)

Components you inject through MDXProvider or Next.js's mdx-components.tsx are used in MDX without an import. Tell the type system about them once by augmenting the global MDXProvidedComponents interface, and their props get checked everywhere:

// mdx-env.d.ts
import type { Chart } from "./components.js";

declare global {
  interface MDXProvidedComponents {
    Chart: typeof Chart;
  }
}
export {};
<Chart data={42} /> {/* error: number is not assignable to number[] */}

Make sure the .d.ts is covered by your tsconfig include.

In CI (GitHub Actions)

- uses: actions/setup-node@v4
  with:
    node-version: 22
- run: npm ci
- run: npx mdx-tsc --project tsconfig.json

Editor support (squiggles)

The frontmatter and type checks are available live in your editor through the mdx-tsc language server (mdx-tsc-language-server). It is additive: it publishes only type and frontmatter diagnostics and advertises no other features, so it runs alongside the official MDX extension without stepping on it.

  • Keep the official MDX extension for syntax highlighting, hover, completion, and markdown features.
  • Add mdx-tsc for type + frontmatter squiggles.
  • Set "mdx": { "checkMdx": false } in your tsconfig so the official extension stops emitting type diagnostics — mdx-tsc now owns those, so you don't see each error twice. (mdx-tsc and the mdx-tsc server always type-check regardless of this flag.)

Setups:

  • VS Code: the extension in editors/vscode launches the server for .mdx files.
  • Neovim / Zed / Helix / any LSP editor: point the editor at the mdx-tsc-language-server binary (stdio), passing initializationOptions.typescript.tsdk (your TypeScript lib directory).

What it checks

  • ESM import/export resolution in MDX
  • {expression} types
  • JSX component props (imported and provider-injected components)
  • Frontmatter, against a per-glob schema you declare
  • MDX parse errors — a document that can't be parsed is reported at the offending location (with the reason), so broken MDX fails the check instead of slipping through

Limitations

  • MDX must be valid JavaScript + JSDoc. Like all MDX, the ESM in a document is JavaScript — TypeScript-only syntax such as export const x: number = … is not valid. Use JSDoc (/** @type {number} */) for annotations.
  • Remark/rehype transformers aren't applied (an upstream @mdx-js/language-service constraint), so syntax added by transformer plugins is not reflected in types.

How it works

mdx-tsc runs the real TypeScript compiler through Volar's runTsc, which swaps in a program that understands .mdx. Each document is projected to a virtual JSX module (via @mdx-js/language-service) with source maps back to the MDX, so diagnostics land on the original file. mdx-tsc adds frontmatter typing on top of that projection and reads its configuration from your tsconfig.

License

MIT