Fern is powerful for API-first teams with SDK generation. Bengal focuses purely on documentation—giving you full control over your content without proprietary syntax. If you're migrating away from Fern's docs platform, this guide shows you how.
Quick Wins (5 Minutes)
Understanding the Difference
| Aspect | Fern | Bengal |
|---|---|---|
| Primary focus | API + SDK + Docs | Documentation only |
| Configuration | fern.config.json+docs.yml |
bengal.toml |
| Content format | MDX (proprietary extensions) | Standard Markdown |
| Hosting | Fern-hosted | Self-hosted anywhere |
| SDK generation | Built-in | External tools |
What Transfers Directly
| Fern | Bengal | Status |
|---|---|---|
| MDX content | Markdown | ✅ Convert syntax |
| OpenAPI specs | OpenAPI support | ✅ Works directly |
| Navigation structure | Directory-based nav | ✅ Simpler |
| Code examples | Code blocks | ✅ Identical |
Component → Directive Translation
Callouts
<Callout intent="info">
This is an informational callout.
</Callout>
<Callout intent="warning">
This is a warning callout.
</Callout>
<Callout intent="success">
Operation completed successfully.
</Callout>
<Callout intent="danger">
Critical warning—proceed with caution.
</Callout>
:::{info}
This is an informational callout.
:::
:::{warning}
This is a warning callout.
:::
:::{tip}
Operation completed successfully.
:::
:::{danger}
Critical warning—proceed with caution.
:::
Tabs / Code Examples
<CodeBlocks>
<CodeBlock title="Python">
```python
import fern
client = fern.Client(api_key="...")
```
</CodeBlock>
<CodeBlock title="TypeScript">
```typescript
import { FernClient } from "fern";
const client = new FernClient({ apiKey: "..." });
```
</CodeBlock>
<CodeBlock title="Go">
```go
client := fern.NewClient(fern.WithAPIKey("..."))
```
</CodeBlock>
</CodeBlocks>
:::{tab-set}
:::{tab} Python
```python
import myapi
client = myapi.Client(api_key="...")
```
:::{/tab}
:::{tab} TypeScript
```typescript
import { MyAPIClient } from "myapi";
const client = new MyAPIClient({ apiKey: "..." });
```
:::{/tab}
:::{tab} Go
```go
client := myapi.NewClient(myapi.WithAPIKey("..."))
```
:::{/tab}
:::{/tab-set}
Cards
<Cards>
<Card
title="Getting Started"
icon="rocket"
href="/docs/getting-started"
>
Set up your first integration in minutes.
</Card>
<Card
title="API Reference"
icon="book"
href="/docs/api-reference"
>
Complete reference for all endpoints.
</Card>
</Cards>
:::{cards}
:columns: 2
:::{card} Getting Started
:icon: rocket
:link: /docs/getting-started/
Set up your first integration in minutes.
:::{/card}
:::{card} API Reference
:icon: book
:link: /docs/api-reference/
Complete reference for all endpoints.
:::{/card}
:::{/cards}
Accordions
<Accordion title="How do I authenticate?">
Use the `Authorization` header with your API key:
Authorization: Bearer YOUR_API_KEY
</Accordion>
<Accordion title="What are the rate limits?">
We allow 1000 requests per minute per API key.
</Accordion>
:::{dropdown} How do I authenticate?
:icon: key
Use the `Authorization` header with your API key:
```
Authorization: Bearer YOUR_API_KEY
```
:::
:::{dropdown} What are the rate limits?
:icon: clock
We allow 1000 requests per minute per API key.
:::
Steps / Guides
<Steps>
<Step title="Install the SDK">
```bash
npm install @your-company/sdk
```
</Step>
<Step title="Initialize">
```typescript
import { Client } from "@your-company/sdk";
const client = new Client({ apiKey: "..." });
```
</Step>
<Step title="Make a request">
```typescript
const users = await client.users.list();
```
</Step>
</Steps>
:::{steps}
:::{step} Install the SDK
```bash
npm install @your-company/sdk
```
:::{/step}
:::{step} Initialize
```typescript
import { Client } from "@your-company/sdk";
const client = new Client({ apiKey: "..." });
```
:::{/step}
:::{step} Make a request
```typescript
const users = await client.users.list();
```
:::{/step}
:::{/steps}
API Endpoint Documentation
# Fern generates from your API definition
# Custom docs in MDX reference generated content
<EndpointRequestSnippet endpoint="GET /users" />
<EndpointResponseSnippet endpoint="GET /users" />
## List Users
`GET /users`
### Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `limit` | integer | No | Maximum results (default: 20) |
| `offset` | integer | No | Pagination offset |
### Response
| Field | Type | Description |
|-------|------|-------------|
| `users` | array | List of user objects |
| `total` | integer | Total count |
### Example
```bash
curl -X GET "https://api.example.com/users?limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
**Note**: Bengal generates API reference pages automatically from your OpenAPI spec via autodoc. Configure in `bengal.toml`:
```toml
[autodoc.openapi]
enabled = true
spec_file = "openapi.yaml"
```
This generates dedicated pages for each endpoint at `/api-reference/endpoints/...`
Configuration Comparison
Basic Config
// fern.config.json
{
"organization": "your-org",
"version": "0.x.x"
}
# docs.yml
instances:
- url: https://docs.your-company.com
title: Your Company Docs
navigation:
- section: Getting Started
contents:
- page: Introduction
path: ./pages/introduction.mdx
- page: Quickstart
path: ./pages/quickstart.mdx
- section: API Reference
contents:
- api: API Reference
colors:
accentPrimary: "#0D9373"
logo:
light: ./assets/logo-light.svg
dark: ./assets/logo-dark.svg
[site]
title = "Your Company Docs"
baseurl = "https://docs.your-company.com"
theme = "bengal"
[site.logo]
light = "assets/logo-light.svg"
dark = "assets/logo-dark.svg"
# Navigation auto-generated from directory structure
# Use weight frontmatter for ordering
# OpenAPI integration
[autodoc.openapi]
enabled = true
spec = "openapi.yaml"
Navigation
navigation:
- section: Getting Started
contents:
- page: Introduction
path: ./pages/introduction.mdx
- page: Quickstart
path: ./pages/quickstart.mdx
- page: Authentication
path: ./pages/authentication.mdx
- section: Guides
contents:
- page: Webhooks
path: ./pages/guides/webhooks.mdx
- page: Pagination
path: ./pages/guides/pagination.mdx
- section: API Reference
contents:
- api: API Reference
# Navigation from directory structure + weight frontmatter:
content/
├── docs/
│ ├── getting-started/
│ │ ├── _index.md (weight: 10)
│ │ ├── introduction.md (weight: 10)
│ │ ├── quickstart.md (weight: 20)
│ │ └── authentication.md (weight: 30)
│ ├── guides/
│ │ ├── _index.md (weight: 20)
│ │ ├── webhooks.md (weight: 10)
│ │ └── pagination.md (weight: 20)
│ └── api-reference/
│ ├── _index.md (weight: 30)
│ └── ... (generated from OpenAPI)
Tip
No manualdocs.ymlupdates when you add pages. Bengal's directory structure is the navigation.
What You Don't Need Anymore
| Fern Requires | Bengal |
|---|---|
fern.config.json |
Not needed |
docs.ymlnavigation |
Auto-generated |
Fern CLI (fern generate) |
bengal build |
| Fern hosting | Any static host |
| Proprietary MDX syntax | Standard markdown |
| Fern organization account | Self-hosted |
Feature Comparison
What Bengal Has
| Feature | Fern | Bengal |
|---|---|---|
| Callouts | <Callout> |
:::{note},:::{warning}✅ |
| Tabs | <CodeBlocks> |
:::{tab-set}✅ |
| Cards | <Cards> |
:::{cards}✅ |
| Steps | <Steps> |
:::{steps}✅ |
| Accordions | <Accordion> |
:::{dropdown}✅ |
| Code blocks | Built-in | Built-in ✅ |
| OpenAPI | Built-in | Autodoc generates pages from spec ✅ |
| Search | Built-in | Built-in ✅ |
| Dark mode | Built-in | Built-in ✅ |
| Custom domains | Paid feature | Any host |
What's Different (Trade-offs)
| Feature | Fern | Bengal | Notes |
|---|---|---|---|
| SDK generation | Built-in | External | Use OpenAPI Generator, etc. |
| API playground | Interactive | Static | External tools for testing |
| Type-safe API definitions | Fern Definition | OpenAPI | Standard spec works |
| Hosting | Managed | Self-hosted | More control |
| AI-powered search | Built-in | External | Integrate your own |
What Bengal Adds
| Feature | Description |
|---|---|
| Full source control | Modify anything |
| No vendor lock-in | Standard formats |
| Offline development | Complete local workflow |
| Build-time validation | bengal healthchecks |
| Content versioning | _versions/folders |
| Custom themes | Full theming control |
SDK Generation Alternative
Fern's SDK generation is separate from documentation. Without Fern, you can use:
# OpenAPI Generator (most languages)
openapi-generator generate -i openapi.yaml -g python -o ./sdk/python
# For TypeScript specifically
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-fetch \
-o ./sdk/typescript
# Or use language-specific tools:
# Python: openapi-python-client
# Go: oapi-codegen
# Rust: openapi-generator with rust templates
Your OpenAPI spec works with any generator. Documentation is independent of SDK generation.
Directory Structure Comparison
| Fern | Bengal | Notes |
|---|---|---|
fern/ |
Project root | Simpler structure |
fern/docs/pages/ |
content/docs/ |
Content location |
fern/docs/assets/ |
assets/ |
Static files |
fern.config.json |
Not needed | No central config |
docs.yml |
bengal.toml+ directories |
Simpler |
fern/openapi/ |
Project root orapi/ |
Flexible location |
What Bengal Adds
---
title: API Reference
api_version: "2.0"
base_url: "https://api.example.com/v2"
---
# {{ page.title }}
Current version: **{{ page.metadata.api_version }}**
Base URL: `{{ page.metadata.base_url }}`
Use template variables directly in markdown.
<!-- _snippets/auth-note.md -->
:::{note}
All API requests require authentication.
Include your API key in the `Authorization` header.
:::
<!-- In any page -->
:::{include} /_snippets/auth-note.md
:::
DRY principle for documentation.
# Check all links
bengal health linkcheck
# Full health check
bengal health
# Analyze site structure
bengal analyze
Catch problems before they reach users.
# Deploy anywhere
bengal build
# Output is static HTML
# Works with:
# - GitHub Pages
# - Netlify
# - Vercel
# - Cloudflare Pages
# - AWS S3 + CloudFront
# - Any web server
Migration Steps
- 1
Install Bengal
pip install bengal # or with uv uv add bengal - 2
Create New Site
bengal new site mysite cd mysite - 3
Copy Content
# Copy your Fern MDX files cp -r /path/to/fern/docs/pages/* content/docs/ # Copy assets cp -r /path/to/fern/docs/assets/* assets/ # Copy OpenAPI spec cp /path/to/fern/openapi/openapi.yaml . - 4
Convert MDX Syntax
Replace Fern components with directives:
Find Replace With <Callout intent="info">:::{info}<Callout intent="warning">:::{warning}<Callout intent="success">:::{tip}<Callout intent="danger">:::{danger}<CodeBlocks>:::{tab-set}<CodeBlock title="X">:::{tab} X<Cards>:::{cards}<Card>:::{card}<Steps>:::{steps}<Step>:::{step}<Accordion>:::{dropdown} - 5
Rename Files
# Rename .mdx to .md find content -name "*.mdx" -exec sh -c 'mv "$1" "${1%.mdx}.md"' _ {} \; # Create section index files # For each directory, create _index.md - 6
Add Frontmatter
Add ordering to pages:
--- title: Quickstart weight: 20 description: Get started in 5 minutes --- - 7
Configure OpenAPI (Optional)
If using OpenAPI for API reference:
# config/_default/autodoc.yaml autodoc: openapi: enabled: true specs: - path: "openapi.yaml" output_prefix: "api-reference" - 8
Test
bengal build bengal health linkcheck bengal serve
Migration Checklist
Before You Start
- Install Bengal:
pip install bengal - Export/download your Fern content
- Create new Bengal site:
bengal new site mysite
Content Migration
- Copy MDX files to
content/docs/ - Rename
.mdxto.md - Convert
<Callout>to:::{note}, etc. - Convert
<CodeBlocks>to:::{tab-set} - Convert
<Cards>to:::{cards} - Convert
<Steps>to:::{steps} - Remove Fern-specific imports
Configuration
- Create
bengal.toml - Add
weightfrontmatter for ordering - Create
_index.mdfor each section - Configure OpenAPI autodoc if used
Assets
- Copy images to
assets/ - Update image paths in content
- Copy OpenAPI specs
Verify
- Build:
bengal build - Check:
bengal health linkcheck - Preview:
bengal serve
Quick Reference Card
| Task | Fern | Bengal |
|---|---|---|
| Install | npm install fern-api |
pip install bengal |
| Initialize | fern init |
bengal new site |
| Generate | fern generate |
bengal build |
| Serve | Fern hosting | bengal serve |
| Deploy | Push to Fern | Any static host |
| Callout | <Callout> |
:::{note} |
| Tabs | <CodeBlocks> |
:::{tab-set} |
| Cards | <Cards> |
:::{cards} |
| Steps | <Steps> |
:::{steps} |
Common Questions
Why leave Fern?
Common reasons for migration:
- Vendor lock-in: Fern's proprietary syntax and hosting
- Cost: Self-hosted Bengal is free
- Control: Full customization of output
- Simplicity: Documentation without SDK complexity
- Flexibility: Deploy anywhere, modify anything
What about SDK generation?
SDK generation and documentation are separate concerns. For SDKs without Fern:
# OpenAPI Generator supports 50+ languages
openapi-generator generate -i openapi.yaml -g python
# Or use specialized tools:
# - openapi-python-client (Python)
# - openapi-typescript (TypeScript)
# - oapi-codegen (Go)
Your OpenAPI spec is the source of truth—it works everywhere.
Can I keep my OpenAPI workflow?
Absolutely. Bengal works great with OpenAPI via autodoc:
# bengal.toml
[autodoc.openapi]
enabled = true
spec_file = "openapi.yaml"
This automatically generates API reference pages from your OpenAPI spec. Each endpoint gets its own page with full documentation including parameters, request/response schemas, and examples.
For manual API documentation in markdown pages, you can write endpoint docs manually or link to the generated autodoc pages.
What about the API playground?
For interactive API testing, use external tools:
- Swagger UI (can embed)
- Stoplight Elements
- RapiDoc
- Postman (link to collections)
Static documentation with links to testing tools is often clearer than embedded playgrounds.
How do I handle versioning?
Bengal supports documentation versioning:
content/
├── docs/ # Current version
├── _versions/
│ ├── v1/
│ │ └── docs/ # v1 documentation
│ └── v2/
│ └── docs/ # v2 documentation
Version switcher appears automatically in navigation.
Next Steps
- Directives Reference - All available directives
- Writer Quickstart - Full markdown guide
- Configuration Reference - Config options
- OpenAPI Autodoc - API doc generation