Zero-config CLI for MDX development with built-in .do platform integration.
- 🔐 Authentication - WorkOS OAuth (Google, Microsoft, Okta)
- 🤖 AI Capabilities - LLM, embeddings, models via
sdk.ai - 💼 Business Runtime - Data operations via
sdk.$ - 🌐 API Infrastructure - REST and RPC clients
- ⚡ Real-time Features - Events, scheduling, messaging
- 🎨 Next.js + React - Modern web development
- 📝 MDX First - Content as code with components
# Install mdxe
npm install mdxe
# Start dev server (browser auto-opens with login)
npx mdxe dev
# That's it! No configuration needed.npm install mdxe
# or
pnpm add mdxe
# or
yarn add mdxe# Authenticate (comes with mdxe)
npx do login
# Run development server
npx mdxe dev
# Build for production
npx mdxe build
# Run tests
npx mdxe testAuthentication works automatically via WorkOS OAuth:
- Sign in with Google, Microsoft, Okta, etc.
- No WorkOS account setup needed (uses .do platform credentials)
- Browser-based OAuth flow
- Tokens shared across all .do packages
Access AI and business capabilities directly in your MDX:
'use client'
import { useAI, use$ } from '../components/sdk-provider'
export function MyComponent() {
const ai = useAI() // Get AI capabilities
const $ = use$() // Get Business runtime
async function generate() {
// AI text generation
const result = await ai.llm.post('/generate', {
prompt: 'Write a haiku'
})
// Business operations (uses CapnWeb - queues until awaited)
$.db.insert('haikus', { text: result.text })
$.send('notifications', { message: 'New haiku!' })
// Only await when you need the result
const saved = await $.db.query('SELECT * FROM haikus ORDER BY id DESC LIMIT 1')
return saved
}
return <button onClick={generate}>Generate</button>
}SDK Structure:
sdk.ai- AI capabilities (LLM, embeddings, models)sdk.$- Business runtime containing:$.api- API client infrastructure$.db- Database operations$.every- Event scheduling$.on- Event handlers$.send- Messaging
Convenience hooks:
useSDK()- Returns{ ai, $ }use$()- Returns$(business runtime)useAI()- Returnsai(AI capabilities)
IMPORTANT: The business runtime ($) uses CapnWeb which queues operations automatically:
// ❌ Don't do this (unnecessary awaits)
await $.db.insert('users', user)
await $.db.insert('logs', log)
await $.send('notifications', notification)
// ✅ Do this (queues as single RPC batch)
$.db.insert('users', user)
$.db.insert('logs', log)
$.send('notifications', notification)
// Only await when you need the result
const user = await $.db.query('SELECT * FROM users WHERE id = ?', [id])Key Benefits:
- 🚀 Zero latency - Operations queue automatically
- 📦 Automatic batching - Multiple calls = single RPC
- 🔄 Streaming results - Get data as soon as available
- ⚡ Pipeline efficiency - No round-trip overhead
This is why $ operations feel instant - they're queued locally and batched over the network!
Just create .mdx files and run mdxe dev. That's it!
# Create your first MDX file
echo "# Hello World" > index.mdx
# Start development
npx mdxe devmdxe includes Payload CMS for content management:
- Visual editor for MDX files
- Media management
- User permissions
- Custom collections
- Next.js - Full-featured web applications
- Hono - Lightweight serverless workers
- Static - Pre-rendered HTML
- React - Component libraries
Make your documentation self-verifying with executable code blocks and automatic assertions.
Add assert or doc meta to TypeScript code blocks:
# My Document
\`\`\`ts assert
const sum = 10 + 20
expect(sum).toBe(30)
\`\`\`Run tests:
pnpm test:doc path/to/file.mdx --verboseLiterate testing turns documentation into living tests:
- Code blocks execute and verify behavior
- Assertions inject results as inline comments
- Documentation stays accurate automatically
- Examples become test cases
ts assert - Enable assertions and output capture:
\`\`\`ts assert
const user = { name: 'Alice', age: 30 }
expect(user.name).toBe('Alice')
expect(user.age).toBeGreaterThan(18)
\`\`\`ts doc - Output capture only (no assertions):
\`\`\`ts doc
const sum = 5 + 10
console.log('Sum:', sum)
\`\`\`Full Vitest-compatible expect() API:
// Equality
expect(value).toBe(expected) // Strict equality (===)
expect(value).toEqual(expected) // Deep equality
expect(value).toBeDefined() // Not undefined
expect(value).toBeNull() // Exactly null
expect(value).toBeTruthy() // Truthy value
expect(value).toBeFalsy() // Falsy value
// Comparisons
expect(value).toBeGreaterThan(n) // value > n
expect(value).toBeLessThan(n) // value < n
expect(value).toBeGreaterThanOrEqual(n) // value >= n
expect(value).toBeLessThanOrEqual(n) // value <= n
// Strings
expect(str).toContain(substring) // String includes
expect(str).toMatch(/pattern/) // Regex match
// Arrays
expect(arr).toContain(item) // Array includes
expect(arr).toHaveLength(n) // Array length
// Objects
expect(obj).toHaveProperty('key') // Has property
expect(obj).toMatchObject(partial) // Contains propertiesInject assertion results as inline comments:
# Run with --update flag
pnpm test:doc file.mdx --update --verboseBefore:
const sum = 10 + 20
expect(sum).toBe(30)After:
const sum = 10 + 20
expect(sum).toBe(30)
// ✅ Expected 30 to be 30Passing tests:
📊 Document Test Results
📄 my-document.mdx
Blocks: 3/3 passed (100%)
Assertions: 6/6 passed (100%)
✅ All tests passed!
Failing tests:
📊 Document Test Results
📄 my-document.mdx
Blocks: 2/3 passed (67%)
Assertions: 5/6 passed (83%)
❌ 1 test failed:
Block 2:
❌ Expected 30 to be 25
Expected: 25
Actual: 30
AI Generation Tests:
\`\`\`ts assert
import { generate } from 'mdxai'
const result = await generate('Write a haiku')
const content = await result.text()
expect(content).toBeDefined()
expect(content.length).toBeGreaterThan(10)
\`\`\`Database Tests:
\`\`\`ts assert
import { MdxDbFs } from '@mdxdb/fs'
const db = new MdxDbFs()
await db.build()
const posts = db.list('posts')
expect(Array.isArray(posts)).toBe(true)
expect(posts.length).toBeGreaterThan(0)
\`\`\`Multiple assertions:
const user = {
name: 'Alice',
age: 30,
email: 'alice@example.com'
}
expect(user).toBeDefined()
expect(user.name).toBe('Alice')
expect(user.age).toBeGreaterThan(18)
expect(user.email).toContain('@')Async operations:
const data = await fetch('/api/users')
const users = await data.json()
expect(users).toBeDefined()
expect(Array.isArray(users)).toBe(true)
expect(users[0]).toHaveProperty('id')Error handling:
try {
const result = riskyOperation()
expect(result).toBeDefined()
} catch (error) {
expect(error.message).toContain('expected error')
}Run tests directly in browser editor:
- Press Cmd+Shift+T (Mac) or Ctrl+Shift+T (Windows)
- Click Run Tests button
- See results inline with ✅/❌ indicators
See @mdxui/browser for Monaco integration details.
# Run document tests
pnpm test:doc path/to/file.mdx
# With verbose output
pnpm test:doc path/to/file.mdx --verbose
# Auto-inject assertion results
pnpm test:doc path/to/file.mdx --update
# Skip authentication
pnpm test:doc path/to/file.mdx --skip-auth
# Test multiple files
pnpm test:doc tests/**/*.test.mdxRun with: pnpm test:doc
# Start development server
mdxe dev
# Specify port
mdxe dev --port 3000
# Open browser automatically
mdxe dev --open# Build for production
mdxe build
# Build for specific platform
mdxe build --target next
mdxe build --target hono
mdxe build --target static# Run tests
mdxe test
# Watch mode
mdxe test --watch
# Coverage report
mdxe test --coverage# Lint MDX files
mdxe lint
# Type check
mdxe typecheck
# Send content to external service
mdxe send <destination># Authenticate once
npx do login
# mdxe automatically uses your token
npx mdxe devFor local development without authentication:
mdxe dev --skip-authimport { isAuthenticated, getCurrentUser } from 'mdxe/auth'
if (isAuthenticated()) {
const user = await getCurrentUser()
console.log(`Authenticated as ${user.email}`)
}mdxe works with zero configuration, but you can customize it with mdxe.config.ts:
import { defineConfig } from 'mdxe'
export default defineConfig({
// Content directory
contentDir: './content',
// Output directory
outDir: './dist',
// Target platform
target: 'next',
// Payload CMS configuration
cms: {
enabled: true,
collections: ['posts', 'pages']
},
// MDX plugins
mdxPlugins: [
remarkGfm,
remarkFrontmatter
]
})---
title: My Post
date: 2025-10-04
tags: [mdx, javascript]
---
# {frontmatter.title}
Published on {frontmatter.date}Import and use React components:
import { Button } from '@/components/Button'
# Welcome
<Button onClick={() => alert('Hello!')}>
Click me
</Button>Execute TypeScript code blocks:
\`\`\`typescript
const greeting = "Hello from MDX!"
console.log(greeting)
\`\`\`Add structured data with YAML-LD:
---
'@context': 'https://schema.org'
'@type': Article
headline: 'My Article'
author:
'@type': Person
name: 'Jane Doe'
---
# My Article
Content here...mdxe includes Payload CMS for content management:
# Start development server
npx mdxe dev
# Access CMS at:
http://localhost:3000/admin- Posts - Blog posts and articles
- Pages - Static pages
- Media - Images and files
// payload.config.ts
export default {
collections: [
{
slug: 'products',
fields: [
{ name: 'name', type: 'text' },
{ name: 'price', type: 'number' },
{ name: 'description', type: 'richText' }
]
}
]
}import {
isAuthenticated,
getCurrentUser,
ensureAuthenticated,
getAccessToken
} from 'mdxe/auth'
// Check if user is authenticated
const authenticated = isAuthenticated()
// Get current user
const user = await getCurrentUser()
// Ensure authenticated (prompts if not)
await ensureAuthenticated()
// Get access token for API calls
const token = await getAccessToken()import {
findMdxFiles,
parseMdxFile,
findIndexFile
} from 'mdxe'
// Find all MDX files in directory
const files = await findMdxFiles('./content')
// Parse MDX file
const { frontmatter, content } = await parseMdxFile('./post.mdx')
// Find index file (index.mdx, README.mdx, etc.)
const indexFile = await findIndexFile('./content')# Build for Cloudflare
mdxe build --target hono
# Deploy
wrangler pages deploy dist# Build for Next.js
mdxe build --target next
# Deploy
vercel deploy# Build static site
mdxe build --target static
# Deploy
netlify deploy --dir=distSee the examples directory for complete examples:
- Blog - Simple blog with posts
- Deck - Slide presentation
- Minimal - Bare minimum setup
- Payload Blog - Blog with CMS
- token.do - Shared authentication
- cli.do - Authentication CLI (
docommand) - @mdxdb/core - MDX database utilities
- mdxai - AI-powered content generation
If you see "Authentication required":
# Login with the do command
npx do login
# Or skip auth for local development
npx mdxe dev --skip-auth# Use a different port
npx mdxe dev --port 3001# Clean and rebuild
rm -rf dist .mdxe
npx mdxe build- Documentation: mdx.org.ai
- GitHub: dot-do/mdx
- Issues: GitHub Issues
MIT