---
title: "Entry points"
description: "The functions that parse, compile, and evaluate Markdown and MDX, plus their result shape."
section: "reference"
order: 3
---
Sätteri's entry points parse a source string and return a result object. They run synchronously unless a plugin has an async visitor, in which case they return a `Promise` of the same result (see [Async plugins](/docs/plugin-api/#async-plugins)). Each one accepts an options argument — see [Options](/docs/options/) for the full reference.
## markdownToHtml
```ts
markdownToHtml(source: string, options?: CompileOptions): MarkdownToHtmlResult;
```
Parse Markdown and render HTML.
```js
import { markdownToHtml } from "satteri";
const { html, frontmatter, data } = markdownToHtml("# Hello, *world*");
// html === "<h1>Hello, <em>world</em></h1>"
```
## mdxToJs
MDX is a programming language: `mdxToJs` compiles it to JavaScript and `evaluate` runs that JavaScript. Treat MDX like code you execute — never compile or evaluate MDX from authors you don't trust.
```ts
mdxToJs(source: string, options?: MdxCompileOptions): MdxToJsResult;
```
Parse MDX and compile it to JavaScript module source. The compiled code is on `code` (not `html`).
```js
import { mdxToJs } from "satteri";
const { code } = mdxToJs("# Hello\n\n<MyComponent />");
```
## markdownToJs
```ts
markdownToJs(source: string, options?: MarkdownToJsOptions): MarkdownToJsResult;
```
Like `mdxToJs`, but the source is plain Markdown: `{...}` expressions, JSX tags, and `import`/`export` lines are ordinary Markdown text instead of MDX syntax. The options and result mirror `mdxToJs` — the MDX-specific fields all concern the compiled JS/JSX output, so they apply here too.
```js
import { markdownToJs } from "satteri";
const { code } = markdownToJs("# Hello\n\n{not an expression}");
```
HTML in the source has no JSX representation, so by default it is dropped: `Press <kbd>Ctrl</kbd>` compiles to `Press Ctrl`. Enable [`features: { rawHtml: true }`](#reparsing-raw-html-rawhtml) to parse the HTML into real elements and keep it.
Dropping happens last, when the JSX is generated, so a HAST plugin still sees the `raw` nodes and can replace them with something that does compile. Only what a plugin leaves behind disappears.
This is the one place `markdownToJs` and `markdownToHtml` differ on the same input: HTML output can re-emit raw HTML verbatim, JSX output cannot — except under [`optimizeStatic`](/docs/options/#optimizestatic), where a collapsed subtree is serialized back to an HTML string and any raw HTML in it rides along.
## Result shape
These functions return an object, never a bare string:
```ts
interface MarkdownToHtmlResult {
html: string; // rendered HTML
frontmatter: Frontmatter | null;
data: Data; // the document data bag
}
interface MdxToJsResult {
code: string; // compiled JS module source
frontmatter: Frontmatter | null;
data: Data;
}
```
`MarkdownToJsResult` has the same shape as `MdxToJsResult`.
`frontmatter` is the parsed block at the top of the document, or `null` if there is none — see [Frontmatter](/docs/features/#frontmatter) for its shape. `data` is the [document data bag](/docs/options/#data).
## evaluate
Compile and run MDX in one step, returning the module's exports (including `default`, the component). Pass a JSX runtime:
```js
import { evaluate } from "satteri";
import * as runtime from "react/jsx-runtime";
const { default: Content } = evaluate("# Hello\n\n<Sparkle />", { ...runtime });
```
## Trees without compiling
To get a plain JavaScript AST without running plugins or rendering, use the tree functions. Each parses the source and returns a materialized tree directly (not a result object), and accepts a `TreeOptions` object with a `features` and a `position` option.
```ts
interface TreeOptions {
features?: Features;
position?: boolean;
}
markdownToMdast(source: string, options?: TreeOptions): MdastNode;
mdxToMdast(source: string, options?: TreeOptions): MdastNode;
markdownToHast(source: string, options?: TreeOptions): HastNode;
mdxToHast(source: string, options?: TreeOptions): HastNode;
htmlToHast(html: string): HastNode;
```
```js
import { markdownToMdast } from "satteri";
const tree = markdownToMdast("# Hello");
tree.children[0].type; // "heading"
tree.children[0].depth; // 1
```
Pass `position: false` to skip recording `node.position`. Disabling positions can greatly increase performance and lower the memory usage, so it is worth passing whenever nothing downstream reads positions.
```js
const tree = markdownToMdast(source, { position: false });
```
This is useful when you want Sätteri's fast native parsing but another pipeline (e.g. remark plugins and `remark-stringify`) for the rest. The returned tree is plain objects, yours to keep — see [Node lifetime](/docs/plugin-api/#node-lifetime) for why that matters.
`htmlToHast` is the exception: it parses an HTML string (not Markdown or MDX) into HAST, with the same spec-compliant parsing a browser does — malformed markup is recovered, implied elements are filled in. The result is a `root` wrapping the implied `<html>` subtree. Use it to bring existing HTML into a HAST plugin pipeline.
```js
import { htmlToHast } from "satteri";
const tree = htmlToHast("<p>hi</p>");
tree.type; // "root"
```
Attributes are normalised into typed hast properties (`class` → `className: ["…"]`, `disabled` → `true`, `tabindex` → a number, `data-foo-bar` → `dataFooBar`). One deliberate divergence from standard hast: `<template>` content is emitted as the element's `children` rather than a separate `content` root, so external serializers that only read `content` will not re-serialize it.
## Reparsing raw HTML (`rawHtml`)
By default, inline and block HTML in Markdown is preserved as opaque `raw` nodes and re-emitted verbatim. Pass `features: { rawHtml: true }` to reparse it into structured HAST:
```js
import { markdownToHast } from "satteri";
const tree = markdownToHast(`<div class="note">\n\n**hi**\n\n</div>`, {
features: { rawHtml: true },
});
// <div> is now a real element wrapping the parsed <p><strong>hi</strong></p>
```
The whole tree is reparsed through the HTML parser, so a tag opened in one raw block and closed in another is resolved against the surrounding Markdown. Positions are not preserved through the reparse.
Sätteri's entry points parse a source string and return a result object. They run synchronously unless a plugin has an async visitor, in which case they return a Promise of the same result (see Async plugins). Each one accepts an options argument — see Options for the full reference.
import { markdownToHtml } from "satteri";
const { html, frontmatter, data } = markdownToHtml("# Hello, *world*");
// html === "<h1>Hello, <em>world</em></h1>"
mdxToJs
MDX is a programming language: mdxToJs compiles it to JavaScript and evaluate runs that JavaScript. Treat MDX like code you execute — never compile or evaluate MDX from authors you don't trust.
Like mdxToJs, but the source is plain Markdown: {...} expressions, JSX tags, and import/export lines are ordinary Markdown text instead of MDX syntax. The options and result mirror mdxToJs — the MDX-specific fields all concern the compiled JS/JSX output, so they apply here too.
import { markdownToJs } from "satteri";
const { code } = markdownToJs("# Hello\n\n{not an expression}");
HTML in the source has no JSX representation, so by default it is dropped: Press <kbd>Ctrl</kbd> compiles to Press Ctrl. Enable features: { rawHtml: true } to parse the HTML into real elements and keep it.
Dropping happens last, when the JSX is generated, so a HAST plugin still sees the raw nodes and can replace them with something that does compile. Only what a plugin leaves behind disappears.
This is the one place markdownToJs and markdownToHtml differ on the same input: HTML output can re-emit raw HTML verbatim, JSX output cannot — except under optimizeStatic, where a collapsed subtree is serialized back to an HTML string and any raw HTML in it rides along.
Result shape
These functions return an object, never a bare string:
interface MarkdownToHtmlResult {
html: string; // rendered HTML
frontmatter: Frontmatter | null;
data: Data; // the document data bag
}
interface MdxToJsResult {
code: string; // compiled JS module source
frontmatter: Frontmatter | null;
data: Data;
}
MarkdownToJsResult has the same shape as MdxToJsResult.
frontmatter is the parsed block at the top of the document, or null if there is none — see Frontmatter for its shape. data is the document data bag.
evaluate
Compile and run MDX in one step, returning the module's exports (including default, the component). Pass a JSX runtime:
import { evaluate } from "satteri";
import * as runtime from "react/jsx-runtime";
const { default: Content } = evaluate("# Hello\n\n<Sparkle />", { ...runtime });
Trees without compiling
To get a plain JavaScript AST without running plugins or rendering, use the tree functions. Each parses the source and returns a materialized tree directly (not a result object), and accepts a TreeOptions object with a features and a position option.
import { markdownToMdast } from "satteri";
const tree = markdownToMdast("# Hello");
tree.children[0].type; // "heading"
tree.children[0].depth; // 1
Pass position: false to skip recording node.position. Disabling positions can greatly increase performance and lower the memory usage, so it is worth passing whenever nothing downstream reads positions.
const tree = markdownToMdast(source, { position: false });
This is useful when you want Sätteri's fast native parsing but another pipeline (e.g. remark plugins and remark-stringify) for the rest. The returned tree is plain objects, yours to keep — see Node lifetime for why that matters.
htmlToHast is the exception: it parses an HTML string (not Markdown or MDX) into HAST, with the same spec-compliant parsing a browser does — malformed markup is recovered, implied elements are filled in. The result is a root wrapping the implied <html> subtree. Use it to bring existing HTML into a HAST plugin pipeline.
import { htmlToHast } from "satteri";
const tree = htmlToHast("<p>hi</p>");
tree.type; // "root"
Attributes are normalised into typed hast properties (class → className: ["…"], disabled → true, tabindex → a number, data-foo-bar → dataFooBar). One deliberate divergence from standard hast: <template> content is emitted as the element's children rather than a separate content root, so external serializers that only read content will not re-serialize it.
Reparsing raw HTML (rawHtml)
By default, inline and block HTML in Markdown is preserved as opaque raw nodes and re-emitted verbatim. Pass features: { rawHtml: true } to reparse it into structured HAST:
import { markdownToHast } from "satteri";
const tree = markdownToHast(`<div class="note">\n\n**hi**\n\n</div>`, {
features: { rawHtml: true },
});
// <div> is now a real element wrapping the parsed <p><strong>hi</strong></p>
The whole tree is reparsed through the HTML parser, so a tag opened in one raw block and closed in another is resolved against the surrounding Markdown. Positions are not preserved through the reparse.