Thank you for your interest in contributing to the OpenZeppelin Docs! We welcome contributions from everyone.
Please follow this guide if you have something to contribute.
If you find a bug or have a suggestion for improvement:
- Check if the issue already exists in GitHub issues
- If not, create a new issue with a clear description
- Wait for a team member to comment before moving forward with a pull request
- Fork the repository
- Create a new branch for your change
- Follow the local development guide in the README or with the steps below
- Use conventional commits when making changes
- Make sure your changes follow the OpenZeppelin Docs Standards
- Submit a pull request
- Keep pull requests focused on a single feature or fix
- Write clear commit messages
- Spelling fixes should be grouped as much as possible (i.e. fixing multiple errors instead of just one)
- Make sure pnpm is installed
pnpm --version- Clone the repo and install dependencies
git clone https://github.com/OpenZeppelin/docs
cd docs
pnpm install- Run the
devserver to see a live preview and have your changes reflected athttp://localhost:3000
pnpm dev- After making changes run the lint command to make formatting rules are applied
pnpm run checkUnderstanding the project structure will help you navigate the codebase and make effective contributions.
The documentation content is organized in the content/ directory with the following structure:
content/
├── community-contracts/ # Community-contributed contracts
├── confidential-contracts/ # Confidential/privacy-focused contracts
├── contracts/ # Core OpenZeppelin Contracts documentation
├── contracts-cairo/ # Cairo contracts for StarkNet
├── contracts-compact/ # Compact contract implementations
├── contracts-stylus/ # Stylus contracts for Arbitrum
├── ui-builder/ # UI Builder documentation
├── defender/ # Defender platform documentation
├── monitor/ # Monitoring tools documentation
├── relayer/ # Relayer service documentation
├── stellar-contracts/ # Stellar blockchain contracts
├── substrate-runtimes/ # Substrate runtime documentation
├── uniswap-hooks/ # Uniswap v4 hooks
├── upgrades-plugins/ # Upgrade plugins documentation
├── upgrades.mdx # General upgrades guide
└── wizard.mdx # Contract wizard documentation
Each product directory typically contains:
index.mdx- Main documentation entry pointchangelog.mdx- Version history and changes- Subdirectories for specific features/modules
- API reference files (often auto-generated)
The application follows Next.js 13+ app directory structure:
| Path | Description |
|---|---|
src/app/(docs)/ |
Documentation pages route group |
src/app/(docs)/layout.tsx |
Docs layout wrapper |
src/app/(docs)/[...slug]/page.tsx |
Dynamic documentation pages (catch-all route) |
src/app/page.tsx |
Homepage |
src/app/layout.tsx |
Root application layout |
src/app/layout.config.tsx |
Shared layout configuration and top navigation |
src/components/ |
Reusable React components |
src/components/layout/ |
Layout-specific components |
src/components/icons/ |
Custom SVG icons for products |
src/components/ui/ |
UI component library |
src/lib/source.ts |
Content source adapter with Fumadocs loader |
src/navigation/ |
Navigation tree configurations (JSON files) |
src/hooks/ |
Custom React hooks |
source.config.ts- Fumadocs MDX configuration with math, mermaid, and code highlighting supportnext.config.mjs- Next.js configurationpostcss.config.mjs- PostCSS configuration for stylingtsconfig.json- TypeScript configuration
The navigation system is one of the most complex parts of the application. Understanding how it works is essential for adding new products or modifying navigation structure.
The documentation supports multiple blockchain ecosystems, each with its own navigation tree:
- Ethereum & EVM (
ethereum-evm.json) - Arbitrum Stylus (
arbitrum-stylus.json) - Starknet (
starknet/directory with versioned files) - Stellar (
stellar.json) - Midnight (
midnight.json) - Polkadot (
polkadot.json) - Uniswap Hooks (
uniswap.json) - Zama FHEVM (
zama.json)
Navigation trees are located in src/navigation/ and are defined as JSON files.
The navigation system uses the useNavigationTree hook (src/hooks/use-navigation-tree.ts) to dynamically determine which navigation tree to display based on the current URL path.
Key logic:
- The hook checks the
pathnameto determine which ecosystem the user is viewing - For most paths, there's a direct mapping (e.g.,
/contracts-cairo→ Starknet tree) - Some paths like
/monitor,/relayer, and/ui-builderare shared across multiple ecosystems
Some tools work across multiple blockchain ecosystems:
/monitor- Available in Ethereum, Stellar, Polkadot, and Arbitrum Stylus/relayer- Available in Ethereum, Stellar, Polkadot, and Arbitrum Stylus/ui-builder- Available in Ethereum and Stellar
How it works:
- When a user navigates to an ecosystem-specific page (e.g.,
/stellar-contracts), the ecosystem is stored insessionStoragewith keylastEcosystem - When the user then navigates to a shared path like
/monitor, the system checkssessionStorageto determine which navigation tree to show - The sidebar tabs (displayed in
DocsLayoutClient) dynamically include or exclude shared paths based on the last ecosystem visited
Example flow:
User visits /stellar-contracts
→ sessionStorage.setItem('lastEcosystem', 'stellar')
→ Stellar navigation tree is shown
User navigates to /monitor
→ Check sessionStorage.getItem('lastEcosystem') → 'stellar'
→ Display Stellar navigation tree (which includes /monitor)
→ Stellar tab remains active
This approach ensures that shared tools appear in the correct ecosystem context while maintaining a consistent user experience.
The sidebar displays ecosystem tabs defined in src/components/layout/docs-layout-client.tsx. Each tab has:
title- Display name (e.g., "Ethereum & EVM")url- Primary URL for the ecosystemicon- Custom icon componenturls- Set of URLs that belong to this ecosystem (including shared paths when appropriate)
The active tab is determined by checking if the current pathname matches any URL in the tab's urls set.
Navigation trees are defined using a specific structure with the following node types:
Page Node:
{
"type": "page",
"name": "Getting Started",
"url": "/contracts"
}Folder Node:
{
"type": "folder",
"name": "Tokens",
"index": {
"type": "page",
"name": "Overview",
"url": "/contracts/5.x/tokens"
},
"children": [...]
}Separator Node:
{
"type": "separator",
"name": "Open Source Tools"
}To add or modify navigation:
- Locate the appropriate JSON file in
src/navigation/ - Edit the JSON structure following the node types above
- Ensure URLs match the actual MDX file locations in
content/ - If adding a new ecosystem, create a new JSON file and register it in
src/navigation/index.ts - Update
useNavigationTreehook if needed - Update ecosystem tabs in
DocsLayoutClientif adding a new ecosystem
DocsLayoutClient (src/components/layout/docs-layout-client.tsx)
- Main client-side layout wrapper for documentation pages
- Manages ecosystem tabs and determines which should be displayed
- Handles the logic for showing shared paths in the correct ecosystem context
- Receives the navigation tree from
useNavigationTreehook
DocsLayout (src/components/layout/docs.tsx)
- Base documentation layout component from Fumadocs
- Renders the sidebar, navigation, and page content
- Configured with navigation tree and tab information
Card & SmallCard - Content cards for homepage and feature highlights
TOC (Table of Contents) - Auto-generated from page headings with scrollspy functionality
Search - Full-text search interface with custom result formatting
ThemeToggle - Light/dark mode switching
VersionBanner - Version-specific messaging and warnings
Product-specific icons located in src/components/icons/:
- Blockchain Icons: Ethereum, Arbitrum, Starknet, Stellar, Polkadot, Midnight, Zama
- Product Icons: Contracts, Defender, Monitor, Relayer
- Tool Icons: Wizard, Ethernaut, and others
Icons are React components that accept className props for styling.
This project is built on Fumadocs, a modern documentation framework that provides:
- MDX Support: Write documentation in MDX with React components
- Built-in Search: Full-text search powered by Algolia
- OpenAPI Integration: Automatic API reference generation from OpenAPI specs
- Type-safe Content: TypeScript integration for content and navigation
- Theme Support: Built-in light/dark mode with customizable themes
Fumadocs handles the core infrastructure, allowing us to focus on content and customization. For detailed framework documentation, visit fumadocs.dev.
The repository includes several automation scripts in the scripts/ directory to help maintain documentation quality and automate repetitive tasks.
Script: scripts/generate-api-docs.js
This script automates the generation of Solidity API reference documentation from OpenZeppelin contract repositories using Solidity Docgen.
How it works:
- Clones a specified contracts repository (with branch selection)
- Installs dependencies and runs the
npm run prepare-docscommand - Copies generated markdown files from the
docs/modules/api/pagesdirectory - Copies example files from
docs/modules/api/examplesif available - Places the generated documentation in the specified output directory
- Cleans up temporary files
Usage:
node scripts/generate-api-docs.js \
--repo https://github.com/OpenZeppelin/openzeppelin-contracts.git \
--branch master \
--api-output content/contracts/5.x/api \
--examples-output examplesOptions:
--repo, -r- Contracts repository URL--branch, -b- Branch to clone (default: master)--temp-dir, -t- Temporary directory for cloning (default: temp-contracts)--api-output, -a- API documentation output directory--examples-output, -e- Examples output directory--help, -h- Show help message
Requirements:
The target repository must have:
- A
docs/config-md.jsfile (Solidity Docgen configuration) - A
docs/templates-md/directory (markdown templates) - An
npm run prepare-docsscript that generates documentation
See the README for instructions on setting up a repository for API generation.
File: src/lib/openapi.ts
This file configures OpenAPI specification integration using Fumadocs OpenAPI server utilities.
How it works:
The openapi.ts file creates an OpenAPI instance that:
- Fetches OpenAPI spec files from remote URLs (e.g., GitHub raw URLs)
- Parses the specifications
- Makes them available to Fumadocs for automatic API documentation generation
Current configuration:
export const openapi = createOpenAPI({
input: [
"https://github.com/ghraw/OpenZeppelin/openzeppelin-relayer/refs/heads/main/openapi.json",
],
});Adding new OpenAPI specs:
To add documentation for a new API, add the URL to the input array:
export const openapi = createOpenAPI({
input: [
"https://github.com/ghraw/OpenZeppelin/openzeppelin-relayer/refs/heads/main/openapi.json",
"https://github.com/ghraw/YourOrg/your-api/main/openapi.json",
],
});The OpenAPI documentation is automatically rendered in MDX files using Fumadocs transformers configured in source.config.ts.
Script: scripts/generate-changelog.js
This script automatically generates changelog documentation from GitHub releases using the changelog-from-release tool.
How it works:
- Uses the
changelog-from-releaseCLI tool to fetch GitHub releases - Formats the release notes as markdown
- Adds MDX frontmatter with title metadata
- Removes the tool's generated tag
- Writes the formatted changelog to
changelog.mdx
Usage:
node scripts/generate-changelog.js <repo_url> <output_directory>Example:
node scripts/generate-changelog.js \
OpenZeppelin/openzeppelin-relayer \
content/relayer/1.2.xThis generates content/relayer/1.2.x/changelog.mdx with all releases from the repository.
Requirements:
Install changelog-from-release:
# macOS
brew install changelog-from-release
# Or download from: https://github.com/rhysd/changelog-from-releaseOutput format:
---
title: Changelog
---
# [Version] - Date
Release notes content...Script: scripts/link-validation.ts
A comprehensive link validation tool that checks all internal links in documentation files and navigation structures.
How it works:
- Scans all MDX files in the
content/directory - Extracts URLs from markdown links and custom components (like
Cardcomponents) - Validates navigation trees to ensure all URLs in JSON navigation files exist
- Checks fragments (hash links) to ensure heading IDs exist on target pages
- Reports broken links with file locations and line numbers
Usage:
# Check all links and print to console
npx tsx scripts/link-validation.ts
# Check specific scope (e.g., only contracts documentation)
npx tsx scripts/link-validation.ts --scope "/contracts/*"
# Output results to a file
npx tsx scripts/link-validation.ts --output link-errors.txt
# Disable fragment checking
npx tsx scripts/link-validation.ts --no-ignore-fragmentsOptions:
--scope <pattern>- Validate only files matching the pattern (supports wildcards)--output <file>- Write results to a file instead of console--no-ignore-fragments- Include fragment validation (hash links)
What it validates:
- File links: All markdown links in
.mdxfiles - Component links:
hrefattributes in custom components likeCard - Navigation URLs: All URLs in navigation JSON files
- Fragment links: Heading anchors (
#heading-id) on pages - Relative paths: Relative file paths resolved as URLs
Example output:
Invalid URLs in content/contracts/5.x/tokens.mdx:
/contracts/invalid-page: URL not found in site pages at line 45 column 12
/contracts/5.x/erc20#nonexistent-heading: Fragment '#nonexistent-heading' not found on page at line 78 column 5
------
Invalid URLs in Navigation Trees:
Ethereum & EVM: /deprecated/old-page - URL not found in site pages
------
Summary: 3 errors found in 2 files out of 150 total files
When to run:
- Before submitting a pull request
- After adding or reorganizing documentation
- After updating navigation structures
- As part of CI/CD pipeline (recommended)
Script: scripts/sync-search-content.ts
Workflow: .github/workflows/sync-search.yml
This script synchronizes documentation content with Algolia search indexes, enabling full-text search across the documentation.
How it works:
- Creates a temporary route (
src/app/static.json/route.ts) that exports search indexes - Runs a Next.js build to generate static search data at
.next/server/app/static.json.body - Reads the generated search records containing page metadata and content
- Syncs to Algolia using the Fumadocs Algolia integration
- Cleans up temporary files after completion
Search index structure:
The script uses src/lib/export-search-indexes.ts to generate search records:
{
_id: "/contracts/5.x/erc20", // Page URL (unique ID)
url: "/contracts/5.x/erc20", // Page URL
title: "ERC-20 Tokens", // Page title
description: "Guide to ERC-20...", // Page description
structured: { ... }, // Structured content for search
extra_data: { ... } // Full page content
}Version filtering:
The script automatically excludes old documentation versions from search to keep results focused on current content:
const excludedVersions = ["/3.x/", "/4.x/", "/1.0.0/", "/0.1.0/", ...];Manual usage:
# Sync search content to Algolia
bun run scripts/sync-search-content.tsRequired environment variables:
NEXT_PUBLIC_ALGOLIA_ID=your-algolia-app-id # Set it both GitHub settings and Netlify Settings
ALGOLIA_PRIVATE_KEY=your-algolia-admin-api-key # Only set locally and GitHub settingsAutomated workflow:
The .github/workflows/sync-search.yml workflow automatically runs on every push to the main branch:
on:
push:
branches: [main]How Algolia search works:
- Indexing: The sync script sends document records to Algolia's
documentindex - Search UI: Fumadocs provides a built-in search component that queries Algolia
- Real-time updates: Search results are updated automatically when content changes
- Ranked results: Algolia handles relevance ranking, typo tolerance, and result highlighting
Configuration:
Search behavior is configured through Fumadocs in src/lib/source.ts and the search component in the layout. Algolia handles:
- Instant search with type-ahead
- Fuzzy matching for typos
- Result highlighting
- Search analytics
Troubleshooting:
If search results are outdated:
- Check if the GitHub workflow ran successfully
- Verify environment variables are set in GitHub secrets
- Manually run the sync script locally
- Check Algolia dashboard for index status
The site uses Fumadocs with the following MDX enhancements:
- Math Support: LaTeX math rendering with KaTeX
- Mermaid Diagrams: Flowcharts and diagrams using Mermaid syntax
- Code Highlighting: Multi-theme syntax highlighting with line numbers
- OpenAPI Integration: Automatic API documentation generation from OpenAPI specs
OpenZeppelin Wizard - Embedded contract generation tool for creating Solidity smart contracts
Code Examples - Copy-to-clipboard functionality for code blocks powered by Fumadocs
Version Switching - Multi-version documentation support with version-specific content
Responsive Design - Mobile-optimized navigation and content rendering
For more information on Fumadocs features, see the official documentation.
- Create a new
.mdxfile in the appropriatecontent/subdirectory - Add frontmatter with metadata:
--- title: Page Title description: Page description for SEO ---
- Write your content using MDX syntax
- Add the page to the navigation JSON file in
src/navigation/ - Test locally with
pnpm dev
- Create a new directory in
content/(e.g.,content/new-product/) - Create an
index.mdxfile as the entry point - Create a navigation JSON file in
src/navigation/(e.g.,new-product.json) - Register the navigation tree in
src/navigation/index.ts:import newProductData from "./new-product.json"; export const newProductTree: NavigationTree = { name: "New Product", children: newProductData as NavigationNode[], };
- Update
useNavigationTreehook insrc/hooks/use-navigation-tree.tsto include the new path - Add a new tab in
DocsLayoutClient(src/components/layout/docs-layout-client.tsx) with appropriate icon - Create or import a custom icon for the product in
src/components/icons/
For version-specific documentation:
- Create a versioned subdirectory (e.g.,
content/contracts/5.x/) - Maintain older versions in parallel directories (e.g.,
4.x/,3.x/) - Update navigation to include version-specific paths
- Use the "Previous Versions" folder pattern in navigation JSON for clarity
Please be respectful and constructive in all interactions. We strive to maintain a welcoming and inclusive environment for all contributors.
If you have questions about contributing, feel free to open an issue or reach out to the maintainers.