Thank you for your interest in contributing to SuperDoc! Whether you're fixing a bug, improving documentation, or adding a feature, we appreciate your help.
- Ways to Contribute
- Architecture Overview
- Contribution Areas
- Your First PR
- Development Setup
- Pull Request Process
- Release Process
- Style Guidelines
- Community
Contributing isn't just about writing code. Here are several ways you can help:
Report bugs with reproduction files Open a .docx in SuperDoc and compare it with Microsoft Word. If something looks different, open an issue with the file attached. Good bug reports with reproduction files are incredibly valuable.
Improve documentation
Our docs live in apps/docs/ (docs.superdoc.dev) and are built with Mintlify. Fix typos, add code examples, improve explanations, or write guides. Run pnpm run dev:docs to preview locally. Documentation PRs are always welcome and a great way to get started.
Add examples and integrations
Create example projects showing SuperDoc with different frameworks (Next.js, Nuxt, Remix, etc.) in the examples/ directory.
Add test coverage Write unit tests or visual regression tests for existing features. Better test coverage helps everyone.
Fix bugs and implement features Check our good first issues for approachable tasks, or help wanted for meatier items.
Help the community Answer questions on Discord.
SuperDoc is a document editing and rendering library for the web. Understanding its architecture will help you find the right place to make changes.
SuperDoc uses its own rendering pipeline -- ProseMirror is NOT used for visual output:
DOCX File
β super-converter (parse OOXML into ProseMirror document)
β pm-adapter (convert PM nodes into FlowBlocks)
β layout-engine (paginate FlowBlocks into Layouts)
β DomPainter (render Layouts to DOM)
A hidden ProseMirror Editor instance manages document state and editing commands, but its DOM is never shown to the user. All visual rendering goes through DomPainter.
packages/
superdoc/ Main entry point (npm: superdoc)
react/ React wrapper (@superdoc-dev/react)
super-editor/ ProseMirror editor core
src/editors/v1/
core/
super-converter/ DOCX import/export (OOXML β ProseMirror)
extensions/ Editing behaviors (bold, lists, tables, etc.)
layout-engine/ Layout & pagination pipeline
pm-adapter/ ProseMirror β Layout bridge
layout-engine/ Pagination algorithms
painters/dom/ DOM rendering (DomPainter)
style-engine/ OOXML style resolution & cascade
contracts/ Shared type definitions
ai/ AI integration
collaboration-yjs/ Collaboration server
shared/ Internal utilities
examples/ Framework integration examples
tests/visual/ Visual regression tests (Playwright)
| What you want to change | Where to look |
|---|---|
| How something looks (visual rendering) | layout-engine/painters/dom/ |
| Style resolution (fonts, colors, borders) | layout-engine/style-engine/ |
| Data flowing from editor to renderer | layout-engine/pm-adapter/ |
| Editing behavior (keyboard, commands) | super-editor/src/editors/v1/extensions/ |
| DOCX import/export | super-editor/src/editors/v1/core/super-converter/ |
| React integration | packages/react/ |
| Main entry point (Vue) | packages/superdoc/ |
| Visual regression tests | tests/visual/ |
The importer stores raw OOXML properties. The style-engine resolves them at render time.
The converter (super-converter/) parses and stores only what is explicitly in the XML. The style-engine (layout-engine/style-engine/) handles all cascade logic (defaults -> table style -> conditional formatting -> inline overrides). Don't resolve styles during import -- it bakes them into node attributes and loses the original document intent on export.
These are areas where community contributions are especially welcome. Check issues labeled good first issue for specific tasks.
| Area | Difficulty | Where to Look | What to Do |
|---|---|---|---|
| Documentation | Easy | docs.superdoc.dev | Fix gaps, add code examples, improve explanations |
| Examples | Easy | examples/ |
Create framework integration examples |
| Test coverage | Easy-Medium | tests/visual/ |
Add tests for existing features |
| Rendering parity | Medium | layout-engine/painters/dom/ |
Open a .docx in Word and SuperDoc, fix visual differences |
| Browser compatibility | Medium | super-editor/, layout-engine/ |
Fix Firefox/Safari-specific bugs |
| Copy/paste | Medium | super-editor/src/editors/v1/extensions/ |
Fix formatting loss when pasting from Word, Google Docs, browsers |
| DOCX import coverage | Medium-Hard | super-editor/src/editors/v1/core/super-converter/ |
Support additional OOXML tags and elements |
Here's a step-by-step walkthrough to make your first contribution:
- Browse good first issues
- Or pick from the contribution areas above
- Comment on the issue to let others know you're working on it
# Fork the repo on GitHub, then:
git clone https://github.com/<your-username>/superdoc.git
cd superdoc
# Install dependencies (pnpm 9+ required)
pnpm install
# Start the dev server
pnpm devThe dev server gives you a live editor to test changes.
git checkout -b fix/your-change-description
# or: feat/your-change-description
# or: docs/your-change-descriptionUse the architecture overview and the "Where to Make Changes" table to locate the right files. When in doubt, search for keywords:
# Find files by name
find packages/ -name "*.js" | xargs grep -l "your-keyword"
# Search file contents
grep -r "your-keyword" packages/ --include="*.js" -l- Follow existing code patterns in the file you're editing
- Keep changes focused -- one fix or feature per PR
- Add or update tests for your changes
# Run the full test suite
pnpm test
# Run tests for a specific package
pnpm run test:editor # super-editor tests
pnpm run test:superdoc # superdoc package tests
# Check formatting and linting
pnpm run format:check
pnpm run lintgit add <files>
git commit -m "fix: describe your change"
git push origin fix/your-change-descriptionFollow Conventional Commits for your commit message (see Commit Messages below).
Open a PR against the main branch. In the description:
- Describe what you changed and why
- Link to the related issue (e.g.,
Closes #123) - Include screenshots for visual changes
- Add a test plan if applicable
CI will run automatically. A maintainer will review your PR and provide feedback.
git clone https://github.com/<your-username>/superdoc.git
cd superdoc
pnpm install
pnpm devpnpm dev # Start dev server (from examples/)
pnpm build # Build all packages
pnpm test # Run all tests
pnpm run lint # Run ESLint
pnpm run format # Run PrettierAny change that grows the public surface ships with explicit assertions for the shape consumers will see. The gates below catch missing surface but do not catch wrong-but-explicit types β a misannotated signature can compile, pass the consumer matrix, and still break consumer TypeScript builds. Be deliberate about what you assert.
Three kinds of public surface change, and what each requires:
A. New public method on SuperDoc / DocumentApi / a UI handle. Add a consumer fixture under tests/consumer-typecheck/src/ that asserts BOTH the parameter shape and the return shape. The pattern (see search-match.ts for a real example):
declare const sd: SuperDoc;
const _paramOk: AssertEqual<Parameters<SuperDoc['myMethod']>[0], MyParam> = true;
const _returnOk: AssertEqual<ReturnType<SuperDoc['myMethod']>, MyReturn> = true;
sd.myMethod(realRuntimeValue); // exercises the call siteWhy both: a migration narrowed SuperDoc.search from string | RegExp to string and slipped past every existing gate because the return-type fixture was there but the parameter-type fixture was not.
B. New event in SuperDocEventMap or new Config.on* callback. Add a consumer fixture that asserts the payload type:
sd.on('my-event', (payload) => {
const _payloadOk: AssertEqual<typeof payload, MyEventPayload> = true;
});
// @ts-expect-error: unknown event names must be rejected
sd.on('my-evnt', () => {});When the event fires from SuperDoc itself (not bridged from upstream), also add a runtime test in SuperDoc.test.js that registers a handler and asserts the emitted payload matches. Types prove consumer usability; runtime tests prove the value the runtime actually emits. They are not the same gate.
C. New public export from superdoc (a new entry in packages/superdoc/src/public/index.ts, a new value re-exported from a public subpath, or a new subpath in package.json exports). The facade source IS the contract (SD-3175 path-as-contract, finalized in SD-3212 PR C). Gates enforce consistency on every PR:
verify-public-facade-emit.cjs-- parses eachsrc/public/**facade source file directly and asserts the emitted.d.tsmatches. NoexpectedNamesallowlist to update; adding a named export to the facade source is the only action needed.snapshot.mjs --all --check-- locks the three snapshot families.rootcovers the fourpackage.json#exportssources (types.import,types.require,import,require);legacycoverssuperdoc/*subpath resolved exports;super-editor-packagecovers@superdoc/super-editor'spackage.json#exportskeys. Drift fails CI; runsnapshot.mjs --family <name> --writeto regenerate one family after an intentional change.check-root-classification-closure.mjs-- nosupported-rootorlegacy-rootexport may reference aninternal-candidatetype in its declared public type. New exports require an entry intests/consumer-typecheck/snapshots/superdoc-root-classification.json.typecheck-matrix.mjs-- every typed public subpath has at least one matrix scenario. New subpath = new fixture undertests/consumer-typecheck/src/+ corresponding matrix entry + inventory line indocs/architecture/package-boundaries.md.check-all-public-types-fixture.mjs-- derives the expected type-only root export list fromsuperdoc-root-classification.jsonand fails ifsrc/all-public-types.tsis missing assertions or has stale ones.src/all-public-types.ts-- exercised by the SD-2842 matrix scenarios to catch any-collapses on customer-facing types. New type-only root export = addimport { X } from 'superdoc';plusconst _real_X: AssertNotAny<X> = true;.
The point of these gates is to keep customer TypeScript builds working. A new public surface that ships without an explicit fixture can collapse to any, narrow silently, or fail to resolve for consumers without the team noticing until upgrade.
feature/descriptionfor new featuresfix/descriptionfor bug fixesdocs/descriptionfor documentation changesperf/descriptionfor performance improvements
Follow Conventional Commits:
feat: add real-time cursor sharing
- Implement cursor position tracking
- Add websocket connection for updates
Closes #123
Your commit type determines the version bump on release:
| Commit Type | Version Bump | Example |
|---|---|---|
fix: |
Patch (0.0.X) | fix: resolve cursor positioning bug |
feat: |
Minor (0.X.0) | feat: add PDF export functionality |
feat!: or BREAKING CHANGE: |
Major (X.0.0) | feat!: redesign document API |
chore:, docs:, refactor:, test: |
No version change | docs: update README |
When you open a PR, the following checks run automatically:
- Commit message validation
- Code formatting (Prettier)
- Linting (ESLint)
- Unit tests
- Visual regression tests (if UI changes)
- Changes are focused (one fix/feature per PR)
- Tests added or updated
- Test suite passes locally (
pnpm test) - Code is formatted (
pnpm run format:check) - Commit messages follow conventional commits
- PR description links to related issue
- If you added a public API: appropriate fixture from Adding a public API β public method (Parameters + ReturnType), public event/callback (payload type + runtime assertion where practical), or public export (the gate lists).
pnpm check:publicpasses.
SuperDoc uses automated CI/CD with semantic-release. No manual version bumps are needed.
mainbranch -> Pre-release versions (@nexttag on npm)stablebranch -> Stable versions (@latesttag on npm)
Every merge to main publishes a pre-release automatically. Stable releases are promoted from main via a GitHub Actions workflow.
- Use JavaScript with JSDoc type annotations for all new code
- Follow the existing code style (enforced by ESLint and Prettier)
- Use ES6+ features
- Document public APIs using JSDoc
- Keep lines under 100 characters
# Check formatting
pnpm run format:check
# Auto-fix formatting
pnpm run format
# Run linting
pnpm run lint
# Fix linting issues
pnpm run lint:fixThis project and everyone participating in it are governed by our Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to support@superdoc.dev.
We value every contribution. Community contributors are featured in our README and recognized on Discord.
Questions? Join our Discord.