Contributing
If you encounter issues with Nitro, want to fix a bug, or reproduce a bug in the example app, you'd need to clone the repo and get it running first.
This page covers environment setup and reproduction - how to run Nitro locally. For the contribution flow itself (what PRs we accept, the required test-per-fix rule, the nitrogen workflow, and the PR checklist), see CONTRIBUTING.md in the repo root. Read it before opening a PR.
The Nitro repository is a Bun monorepo:
packages/nitro-core/: Shared native runtime and JS/TS API.packages/nitrogen/: The CLI that generates native bindings.packages/react-native-nitro-modules/: RN entry point and native build integration.tests/standalone/: Core runtime tests without React Native.tests/react-native/apps/: Example and benchmark apps.tests/react-native/packages/: Benchmark objects and View fixtures.packages/template/: The RN library template.
Run Nitro Example
1. Set up your development environment
You need:
- Bun
- Xcode with Swift 6.4 or higher for iOS builds
- Android Studio
2. Clone the repo
Clone margelo/nitro and navigate into the nitro folder. Target v1 for Nitro V1 contributions.
3. Install dependencies
Prepare core dependencies, install packages, and build the workspace:
bun bootstrap
All packages resolve from the local workspace. The only submodule is the Hermes engine used by standalone native tests.
3.1. (Optional) Install iOS dependencies
For iOS, set up React Native's SwiftPM dependencies from the repository root:
bun example spm
The example and benchmark apps use React Native 0.88 and SwiftPM. The remaining Podfiles and podspecs provide discovery metadata for React Native CLI; CocoaPods is not needed to build these apps.
4. Run the app
After installing all dependencies, you can run the React Native app in tests/react-native/apps/example/:
- iOS
- Android
- Open
tests/react-native/apps/example/ios/NitroExample.xcodeprojin Xcode - Select your target (iPhone Simulator)
- Click Run
- Open
tests/react-native/apps/example/androidin Android Studio - Click Gradle Sync
- Click Run
Edit shared implementations in packages/nitro-core. bun sync-core stages those sources into the RN package, and bun specs regenerates
the Nitrogen bindings for every test fixture with the local Nitrogen build.
Reproduce an issue
Add a reproduction to the repository with your issue report. Portable runtime regressions belong in tests/standalone/; React Native
integration and View regressions belong in tests/react-native/apps/example/. To submit a reproduction:
- Fork the repository
- Change the code to reproduce the issue
- Create a PR to the nitro repository which demonstrates the issue
A PR that only reproduces the bug - and makes CI go red - is a completely valid and very welcome contribution. If you can add a minimal failing test (compile error or runtime error caught by the standalone or Harness tests) and open a PR with just that, stop there. You don't have to attempt a fix. A clean, 100% deterministic repro pinned in CI is often more valuable than a guessed patch; the actual fix can be taken from there.
See You don't need to ship a fix in CONTRIBUTING.md
for details and the test-writing rules (reuse existing types, keep it small, don't remove existing tests).
Reproduce a build error
If you encounter a build error, compare your setup to the setup in tests/react-native/apps/example/. For example, reproduce changes to
your Xcode project or SwiftPM dependencies in tests/react-native/apps/example/ and check whether it still builds. Submit a PR with the
change required to make it fail, and see if the CI fails to build. That alone is enough - you don't need to also fix it.
Reproduce a nitrogen bug
Add HybridObject specs to
tests/standalone/nitro-test/specs/TestObject.nitro.ts.
Use the separate nitro-test-external package for cross-library types. View specs belong in
tests/react-native/packages/react-native-nitro-test/src/specs/. After changing a spec, run Nitrogen from the repo root:
bun specs
Commit the generated files. If that change causes a compile error downstream (e.g. Swift refuses to build the generated code), that's a valid repro on its own - open the PR with the red CI and leave the fix to a maintainer if you don't have one.
When adding a reproduction, follow the rules in CONTRIBUTING.md: reuse existing types where possible, don't remove existing test cases, and keep the addition small. Dumping a full user spec into the test module is not the right approach - distill the bug to the minimal type or call that reproduces it.
Reproduce a runtime error
Add portable runtime assertions to
tests/standalone/test-runner/js/. Run bun test:ios or
bun test:android from the repository root. These suites run through Swift Testing and JUnit, including sanitizer configurations in CI.
For React Native integration or View regressions, add an assertion to the Harness files in
tests/react-native/apps/example/__tests__/. Run
bun test:react-native:ios or bun test:react-native:android. Harness keeps nine View tests and three integration checks; portable cases
run standalone.
A PR that reproduces the failure is enough; you don't have to include a fix.
Performance tests
Performance CI is opt-in. A maintainer with write, maintain, or admin repository access can post
@nitro-modules-bot please test performance on an open PR. The bot acknowledges it with 👍, updates the PR's Nitro Performance status,
and posts the results or a failure link. Contributors without write access can ask a maintainer to run it, including for PRs from forks.
See Performance tests in CONTRIBUTING.md for repeat runs and measurement-only retries.
Run Nitro Docs
The Nitro docs (nitro.margelo.com) live in the docs/ folder of
margelo/nitro and are built with Docusaurus.
To run the Nitro docs, follow these steps:
1. Install dependencies
Clone Nitro and install dependencies from its repository root:
git clone https://github.com/margelo/nitro.git
cd nitro
bun install --frozen-lockfile --ignore-scripts
2. Run docs (dev)
Run the docs development server from the Nitro repository root:
bun docs start
Before submitting docs changes, run bun docs typecheck and bun docs build from the same directory.
Formatting and linting
Use 140 columns and two-space indentation across the repository. JavaScript and TypeScript use single quotes and no semicolons. The formatters may keep an indivisible token, URL, or string longer than 140 columns.
Install dependencies, then prepare the pinned native formatters once:
bun install --frozen-lockfile
bun format:setup
Setup requires Python 3 with venv, Java 17 or newer, Git, and Swift 6.4 or newer. On macOS, select Xcode before running setup, or set
DEVELOPER_DIR to its Contents/Developer directory. The first setup builds swift-format from its pinned source and dependency lockfile.
Tools are cached under node_modules/.cache/nitro-format; setup does not install global packages.
Format the repository or check its current formatting without modifying files:
bun format
bun format:check
Both commands include tracked files and new files that Git does not ignore. They cover source code, standalone tests, scripts, package
manifests, documentation, and configuration files. Generated bindings, vendored code, build output, and dependency lockfiles retain their
producer's formatting; the exclusions are in config/format-ignore. Nitrogen-generated sections inside Swift package manifests have
formatter ignore markers so regenerating bindings does not undo formatting.
To run one formatter, append --language=prettier, --language=cpp, --language=swift, or --language=kotlin. The existing
bun lint-cpp, bun lint-swift, and bun lint-kotlin commands are aliases for the corresponding formatting pass. bun lint still applies
ESLint's code-quality fixes, and bun lint-all runs formatting followed by ESLint.
Prettier reads its settings from the root package.json. C++ uses config/.clang-format, and native tool versions are pinned in
config/format-tools.json. Swift and Kotlin use the root .swift-format and .editorconfig files because their editor integrations
discover configuration by walking parent directories. Keep the template package's Prettier options in sync with the root package so newly
created modules start with the same style.
VS Code recommends the formatter extensions and selects the appropriate formatter for each language. It respects your personal
format-on-save preference. Run bun format:setup before using the C++ extension's pinned formatter. The Kotlin extension uses the same
pinned ktlint version; Swift's official extension uses the formatter in your selected Swift toolchain and reads the repository's
.swift-format settings. bun format:check is the definitive check against the pinned CLI versions.
CI runs the same formatting checks on pull requests and pushes to main and v1.