robbert-vdh/nih-plug
Rust VST3 and CLAP plugin framework and plugins - because everything is better when you do it yourself
Healthy across all four use cases
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 8mo ago
- ✓12 active contributors
- ✓ISC licensed
Show all 7 evidence items →Show less
- ✓CI configured
- ✓Tests present
- ⚠Slowing — last commit 8mo ago
- ⚠Concentrated ownership — top contributor handles 76% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/robbert-vdh/nih-plug)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/robbert-vdh/nih-plug on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: robbert-vdh/nih-plug
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/robbert-vdh/nih-plug shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 8mo ago
- 12 active contributors
- ISC licensed
- CI configured
- Tests present
- ⚠ Slowing — last commit 8mo ago
- ⚠ Concentrated ownership — top contributor handles 76% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live robbert-vdh/nih-plug
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/robbert-vdh/nih-plug.
What it runs against: a local clone of robbert-vdh/nih-plug — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in robbert-vdh/nih-plug | Confirms the artifact applies here, not a fork |
| 2 | License is still ISC | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 272 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of robbert-vdh/nih-plug. If you don't
# have one yet, run these first:
#
# git clone https://github.com/robbert-vdh/nih-plug.git
# cd nih-plug
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of robbert-vdh/nih-plug and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "robbert-vdh/nih-plug(\\.git)?\\b" \\
&& ok "origin remote is robbert-vdh/nih-plug" \\
|| miss "origin remote is not robbert-vdh/nih-plug (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(ISC)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"ISC\"" package.json 2>/dev/null) \\
&& ok "license is ISC" \\
|| miss "license drift — was ISC at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "nih_plug_derive/src/lib.rs" \\
&& ok "nih_plug_derive/src/lib.rs" \\
|| miss "missing critical file: nih_plug_derive/src/lib.rs"
test -f "Cargo.toml" \\
&& ok "Cargo.toml" \\
|| miss "missing critical file: Cargo.toml"
test -f "nih_plug_egui/src/editor.rs" \\
&& ok "nih_plug_egui/src/editor.rs" \\
|| miss "missing critical file: nih_plug_egui/src/editor.rs"
test -f "nih_plug_vizia/src/editor.rs" \\
&& ok "nih_plug_vizia/src/editor.rs" \\
|| miss "missing critical file: nih_plug_vizia/src/editor.rs"
test -f "cargo_nih_plug/src/main.rs" \\
&& ok "cargo_nih_plug/src/main.rs" \\
|| miss "missing critical file: cargo_nih_plug/src/main.rs"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 272 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~242d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/robbert-vdh/nih-plug"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
NIH-plug is a Rust framework for building VST3 and CLAP audio plugins with a stateful, ceremony-light API that abstracts away format-specific boilerplate. It ships as a monorepo containing the core framework (nih_plug), three UI backend options (egui, iced, vizia), a custom derive macro system for parameter serialization, and 10+ production plugins (Soft Vacuum, Buffr Glitch, Crisp, Crossover, etc.) demonstrating real-world usage patterns. Workspace monorepo with: (1) core framework in root nih_plug crate, (2) three UI frameworks in nih_plug_{egui,iced,vizia}/, (3) proc-macro derive system in nih_plug_derive/ for parameter serialization, (4) plugins/{examples,soft_vacuum,buffr_glitch,...} as reference implementations, (5) cargo_nih_plug custom Cargo subcommand, (6) xtask for build automation. Parameter definitions live in derive macros; plugin state is serialized via generated code in nih_plug_derive/src/{params.rs,enums.rs}.
👥Who it's for
Audio plugin developers and DSP engineers (hobbyist and professional) who want to write plugins in Rust without manually handling VST3/CLAP SDK complexity. Also useful for electronic music producers evaluating plugins built with this framework. Plugin creators can use the cookiecutter template (nih-plug-template) as a starting point.
🌱Maturity & risk
Actively maintained and production-ready. The repo has substantial code (~1.8M lines of Rust), comprehensive CI/CD (build.yml, test.yml, docs.yml workflows), documented changelog, and a v0.0.0 version indicating intentional semantic versioning discipline. Multiple shipping plugins and an established documentation site (nih-plug.robbertvanderhelm.nl) signal maturity; however, the 0.x version string suggests the maintainer reserves the right to make breaking API changes.
Single maintainer (Robbert van der Helm) creates potential bottleneck for critical fixes. The workspace spans 20+ crates with external dependencies (baseview, cpal, jack, midir, rtrb for standalone; clap for VST3), increasing supply-chain risk. No visible open-issue or PR data in the provided metadata, so backlog status is unknown. The framework's tight coupling to Rust 1.80+ (rust-version in Cargo.toml) means MSRV bumps could affect downstream plugins.
Active areas of work
Active development visible through: comprehensive GitHub Actions workflows (build, test, docs generation on each commit), organized CHANGELOG.md tracking releases, and maintained plugin collection with diverse DSP applications (buffering, crossovers, spectral compression). The template repository (nih-plug-template) indicates ongoing developer experience focus. Exact recent commits/PRs not visible in provided data, but CI setup shows continuous integration discipline.
🚀Get running
git clone https://github.com/robbert-vdh/nih-plug.git
cd nih-plug
cargo build --release
# For a specific plugin:
cargo build -p buffr_glitch --release
# For examples:
cargo build -p gain_gui_egui --release
Refer to individual plugin READMEs in plugins/{plugin_name}/ for platform-specific build instructions (macOS Gatekeeper, VST3 SDK paths, etc.).
Daily commands:
No traditional 'server' — this is a plugin framework. To test a plugin: (1) build a plugin target: cargo build -p gain_gui_egui --release, (2) copy .vst3 or .clap from target/release/ to your DAW's plugin directory, (3) rescan plugins in your DAW (Ableton, Reaper, Bitwig, etc.). For standalone mode (if standalone feature enabled): the plugin exports a binary that runs without a DAW. Pre-built development binaries are available from GitHub Actions workflow artifacts.
🗺️Map of the codebase
nih_plug_derive/src/lib.rs— Core procedural macro implementation for parameter and enum derive macros—foundational for plugin parameter declaration across the framework.Cargo.toml— Workspace root defining all member crates and their interdependencies; essential for understanding the modular architecture.nih_plug_egui/src/editor.rs— Reference UI editor implementation using egui; demonstrates the primary pattern for integrating a GUI framework into the plugin API.nih_plug_vizia/src/editor.rs— Alternative editor implementation showing Vizia integration; critical for understanding multi-framework support strategy.cargo_nih_plug/src/main.rs— Custom cargo subcommand entry point for plugin building and bundling; key build infrastructure for end users.plugins/examples/gain/src/lib.rs— Minimal working plugin example demonstrating the API contract; essential reference for plugin authors.bundler.toml— Plugin bundling configuration template defining output structure and platform targets for distribution.
🛠️How to make changes
Create a New Simple Plugin
- Create a new plugin crate in plugins/examples/{plugin_name}/Cargo.toml with nih_plug as dependency (
plugins/examples/gain/Cargo.toml) - Define your plugin struct and implement the Plugin trait with parameter definitions (
plugins/examples/gain/src/lib.rs) - Add process() method to handle audio samples from the input/output buffers (
plugins/examples/gain/src/lib.rs) - Use bundler.toml to configure plugin metadata (name, vendor, category) and output targets (
bundler.toml) - Run 'cargo nih-plug bundle {plugin_name}' to build platform-specific plugin bundles (
cargo_nih_plug/src/main.rs)
Add a GUI Editor with egui
- Add nih_plug_egui dependency to plugin Cargo.toml (
plugins/examples/gain_gui_egui/Cargo.toml) - Import editor module and define editor() method returning Some(Box::new(...)) with editor instance (
plugins/examples/gain_gui_egui/src/lib.rs) - Implement update() and draw() methods using egui API; use widgets from nih_plug_egui/src/widgets/generic_ui.rs for parameters (
nih_plug_egui/src/editor.rs) - Use ParamSlider widget for numeric parameters; see nih_plug_egui/src/widgets/param_slider.rs for customization (
nih_plug_egui/src/widgets/param_slider.rs)
Define Reusable Plugin Parameters with Derive Macros
- Create a struct annotated with #[derive(Params)] and #[persist = "key"] for serialization (
plugins/crisp/src/lib.rs) - Add individual parameter fields with #[id = "id"] and #[nested = "prefix"] attributes (
nih_plug_derive/src/params.rs) - Use #[derive(Enum)] on custom enums to generate discrete parameter types automatically (
nih_plug_derive/src/enums.rs) - The macro generates proper serialization and UI hints automatically; access parameters through self.params in process() (
nih_plug_derive/tests/params.rs)
Switch UI Framework (e.g., egui → Vizia)
- Replace nih_plug_egui with nih_plug_vizia in Cargo.toml dependencies (
nih_plug_vizia/Cargo.toml) - Update editor() to return Vizia editor; import from nih_plug_vizia::ViziaEditor (
nih_plug_vizia/src/editor.rs) - Use CSS-based theming via theme.css and widgets.css in nih_plug_vizia/assets/ (
nih_plug_vizia/assets/theme.css) - Rebuild with 'cargo build' or 'cargo nih-plug bundle'; plugin binary updates automatically (
cargo_nih_plug/src/main.rs)
🔧Why these technologies
- Rust + Cargo workspace — Ensures memory safety without GC, strong type system for audio DSP correctness, and modular crate structure for optional UI/backend features
- Procedural macros (syn/quote) — Eliminates boilerplate for parameter declaration and serialization while maintaining type-safe ergonomics across plugin implementations
- Multiple GUI frameworks (egui/Vizia/Iced) — Allows plugin authors to choose UI paradigm (immediate-mode vs retained-mode) and target different platforms without framework lock-in
- VST3 + CLAP backends (in wrapper layer) — Provides API-agnostic plugin interface; plugins compile to multiple plugin standards from single codebase via wrapper abstraction
- Custom cargo subcommand (cargo_nih_plug) — Simplifies cross-platform plugin bundling, code generation, and symbol stripping for end users without shell scripts
⚖️Trade-offs already made
-
API-agnostic core vs. backend abstraction in wrappers
- Why: Decouples plugin logic from VST3/CLAP specifics, reducing vendor lock-in and enabling future backend additions
- Consequence: Requires a wrapper layer for each backend; plugin authors never see VST3/CLAP directly but lose fine-grained control
-
Multiple UI framework support instead of single framework
- Why: Different frameworks suit different workflows (immediate-mode vs. declarative) and platform targets; avoids forcing one paradigm
- Consequence: Each framework integration duplicates widget logic (e.g., param_slider exists in egui, Vizia, Iced); maintenance overhead increases
-
Procedural macros for parameter declaration
- Why: Reduces boilerplate and auto-generates serialization/UI hints without manual matching
- Consequence: Macro debugging is harder; IDE support may lag; plugin authors must learn derive syntax
-
Stateful plugin design vs. pure functional
- Why: Matches DAW expectations (plugins maintain state across process calls) and real-time DSP patterns
- Consequence: Requires careful thread-safety for real-time audio thread; no lazy initialization or dynamic state changes during processing
🚫Non-goals (don't propose these)
- Does not provide VST3 or CLAP format implementation—relies on external wrappers for plugin format compilation
- Does not handle host communication protocols (transport, synchronization) beyond parameter changes and audio I/O
- Does not include a built-in preset browser, file manager, or plugin discovery system
- Not a real-time audio
🪤Traps & gotchas
VST3 SDK Path: Windows/macOS builds require VST3_SDK env var or fallback via cargo_nih_plug; missing this causes silent CMake failures. MSRV 1.80: Rust version constraint is strict; older toolchains will fail without clear error messages. Feature Gates: vst3 is default-enabled but standalone requires additional audio/MIDI deps (cpal, jack, midir); partial builds may fail if not understood. Derive Macro Scope: #[derive(Params)] only works on structs; nested Params require explicit #[nested(...)] attributes — omitting this silently skips nested params. Platform-Specific GUIs: egui/iced/vizia have platform-specific windowing code (baseview); GUI tests may fail cross-platform without headless setup.
🏗️Architecture
💡Concepts to learn
- VST3 Plugin Standard — nih-plug's primary export target; understanding VST3 lifecycle (initialize, setActive, process, setState) is essential for debugging plugin behavior in DAWs
- CLAP (Common Language for Audio Plugins) — Secondary plugin format supported by nih-plug; increasingly adopted as an alternative to VST3 with simpler threading semantics
- Procedural Macros (Rust) — nih_plug_derive implements #[derive(Params)] as proc-macro to auto-generate parameter serialization code; core to the framework's low-boilerplate design
- Real-Time Audio Processing & RAII — Plugin process() runs in hard real-time context with allocation bans; the assert_process_allocs feature detects violations via assert_no_alloc crate
- Parameter Sweeping & Ramping — Audio parameters must smoothly ramp to avoid clicks/pops; nih-plug's ParamSetter abstraction handles this; shown in param_slider.rs widgets
- STFT (Short-Time Fourier Transform) — plugins/examples/stft demonstrates FFT-based DSP; spectral_compressor plugin uses STFT for frequency-domain processing
- Polyphonic Synthesis & Voice Management — poly_mod_synth example shows MIDI note-to-voice allocation patterns; critical for synthesizer plugins in nih-plug
🔗Related repos
rust-dsp/rust-dsp— Ecosystem library providing DSP primitives (filters, FFT, frequency analysis) used by nih-plug plugins for signal processingrobbert-vdh/nih-plug-template— Official cookiecutter template for scaffolding new NIH-plug projects; the recommended starting point for new plugin developersyamadapc/augmented-audio— Alternative Rust plugin framework with VST2/VST3 support; useful for comparing design philosophy and feature paritykubernetes/kubernetes— Not directly related, but included as reference for understanding container-based CI/CD patterns (though nih-plug uses GitHub Actions, not K8s)
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive integration tests for nih_plug_derive macro edge cases
The nih_plug_derive crate has minimal test coverage. Currently only nih_plug_derive/tests/params.rs and nih_plug_derive/tests/persist.rs exist. Given that this is a procedural macro powering the entire parameter system, it lacks tests for: derive on enums with complex variants (referenced in nih_plug_derive/src/enums.rs), parameter serialization edge cases, and macro error handling. This directly impacts plugin reliability since derive macro bugs silently break parameter handling.
- [ ] Create nih_plug_derive/tests/edge_cases.rs covering enum variants with custom discriminants
- [ ] Add tests in nih_plug_derive/tests/params.rs for parameter with min/max bounds validation
- [ ] Add tests for circular reference detection and proper error messages
- [ ] Test interaction between persist and enums macros in nih_plug_derive/tests/persist.rs
Add CI workflow to verify plugin examples build and link correctly across platforms
The repo has build.yml and test.yml workflows, but no dedicated verification that the 11 example plugins (plugins/examples/*) and 8 production plugins (plugins/soft_vacuum, plugins/buffr_glitch, etc.) actually build and produce valid VST3/CLAP binaries. The bundler.toml and cargo_nih_plug tool exist for packaging, but there's no CI validation that the build process works end-to-end. This catches linker issues and cross-platform build breakage early.
- [ ] Create .github/workflows/plugin-build.yml that builds all members under plugins/examples/ and plugins/
- [ ] Add matrix strategy for Linux (x86_64, aarch64), Windows (MSVC), and macOS (Intel + ARM)
- [ ] Verify bundled output exists via cargo_nih_plug in the workflow
- [ ] Add step to check VST3 bundle structure is valid on each platform
Add missing documentation for nih_plug_vizia, nih_plug_iced, and nih_plug_egui editor setup
The three UI frameworks (nih_plug_vizia, nih_plug_iced, nih_plug_egui) have separate README.md files but lack structured rustdoc comments in their entry points (src/lib.rs and src/editor.rs). Without this, the auto-generated docs.yml workflow produces incomplete API documentation. Specifically: nih_plug_vizia/src/editor.rs, nih_plug_iced/src/editor.rs, and nih_plug_egui/src/editor.rs need module-level and function-level documentation with examples. This directly impacts new contributors choosing between frameworks.
- [ ] Add module-level //! docs with framework comparison to nih_plug_vizia/src/lib.rs explaining CSS-based styling approach
- [ ] Add module-level //! docs to nih_plug_iced/src/lib.rs explaining elm-inspired architecture
- [ ] Add module-level //! docs to nih_plug_egui/src/lib.rs explaining immediate mode GUI approach
- [ ] Add /// doc comments with usage examples to editor creation functions in each framework's src/editor.rs
🌿Good first issues
- Add comprehensive doc comments to nih_plug_derive/src/params.rs explaining how nested parameter groups work and the #[nested(...)] macro requirement — currently underdocumented and causes user confusion.
- Write integration tests for parameter serialization round-trip (save → load → verify values match) in nih_plug_derive/tests/persist.rs covering f32/i32/enum types and nested groups; currently only basic tests exist.
- Create a minimal example plugin in plugins/examples/ demonstrating sidechain input or auxiliary output routing (the Crossover plugin uses aux outs but no simple example shows the pattern) with inline code documentation.
⭐Top contributors
Click to expand
Top contributors
- @robbert-vdh — 76 commits
- @BillyDM — 8 commits
- [@Billy Messenger](https://github.com/Billy Messenger) — 4 commits
- @andrewprograms — 2 commits
- @oilcake — 2 commits
📝Recent commits
Click to expand
Recent commits
28b149e— Fix wrong date in changelog (robbert-vdh)51a5db1— Merge branch 'andrewprograms/master' #230 (robbert-vdh)206834c— Fix warning "hiding a lifetime that's elided elsewhere" (andrewprograms)376d8d4— Fix for Issue #220 (andrewprograms)ecfd632— Make implicit autorefs explicit in buffer code (robbert-vdh)7da01b5— fix transparency in softbuffer example (BillyDM)d64b2ab— Mention the new BYO GUI examples in the readme (robbert-vdh)fe12248— Merge branch 'byo_gui_examples' (robbert-vdh)653f6f8— bump wgpu version in byo_gui_wgpu (BillyDM)c270d73— fix auto merge conflict shenanigans (BillyDM)
🔒Security observations
The nih-plug audio plugin framework demonstrates a generally secure posture with no critical vulnerabilities identified in the static analysis. The codebase is well-structured with clear separation of concerns across multiple crates. Primary security considerations relate to the inherent complexity of unsafe code required for audio plugin development and FFI, the extensive use of third-party UI framework dependencies, and monorepo workspace management. The project uses modern Rust (edition 2021, MSRV 1.80) and shows evidence of automated testing via CI/CD workflows. Recommendations focus on maintaining consistent dependency auditing practices and ensuring comprehensive documentation of unsafe code blocks.
- Low · Unsafe code in audio plugin framework —
Core framework (nih_plug main crate). The nih-plug framework is designed for audio plugins which typically require unsafe Rust code for FFI with VST3/CLAP specifications and real-time audio processing. While this is necessary for the domain, it increases the potential attack surface if not carefully managed. Fix: Ensure all unsafe code blocks are well-documented, audited for soundness, and follow Rust safety guidelines. Maintain comprehensive test coverage for unsafe operations. - Low · Multiple optional features increase complexity —
Cargo.toml, feature flags configuration. The framework supports multiple optional features (standalone, vst3, assert_process_allocs) and multiple UI frameworks (egui, iced, vizia). This increases the codebase complexity and potential for feature-interaction bugs. Fix: Maintain clear feature documentation, ensure feature combinations are tested, and consider security review of each UI framework integration separately. - Low · Workspace dependency management complexity —
Root Cargo.toml workspace definition. Large monorepo workspace with 20+ crates increases dependency management complexity. Each plugin and framework has its own dependencies which could diverge or create version conflicts. Fix: Implement regular dependency audits using 'cargo audit', maintain consistent dependency versions across workspace, and establish a dependency update policy. - Low · Third-party UI framework dependencies —
nih_plug_egui, nih_plug_iced, nih_plug_vizia crates. Integration with external UI frameworks (egui, iced, vizia) introduces dependencies on third-party projects. Vulnerabilities in these frameworks could affect plugins using nih-plug. Fix: Regularly audit UI framework dependencies using 'cargo audit', pin versions appropriately, and document security considerations for each UI backend.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.