You've made a drive you love. It sounds right here, in the browser, and it builds as a desktop plugin on your Mac. A friend on Windows asks: will it work in my DAW? Before you answer, play it.
This drive is running in your browser. If it sounds right here, how much do you now know about the plugin version?
One target, playing now
Electric Guitar: Andy G. Cohen, CC BY
What you just heard proves something real: this drive, built for the browser, works in your browser. It doesn't prove anything about the plugin on a Mac, and it proves even less about Windows or Linux. Those are different files, run by different programs, on different machines.
Musicians trust plugins with their sessions. A plugin that loads perfectly in one DAW and crashes another halfway through a mix costs someone an evening and their trust in you. That's why "works as a plugin" isn't one claim. It's a list of claims, each with its own check.
One source, several outputs
A Maru Processor module starts as one TypeScript file that owns the sound. The build turns that one source into several outputs.
One source, many builds
Two words help keep this straight. A format is what a host loads: the plugin format most DAWs load, the open CLAP format, Apple's macOS-only plugin format, a standalone app. In this guide, "the plugin" means the first of those. A target is a format on a specific kind of machine: the plugin for macOS on Apple silicon and the plugin for Linux on Intel are two different files, built from different inputs, each needing its own test.
Sharing one source is a big deal. It means the browser version and the plugin version can't quietly drift into two different products, and it lets Maru check that the JavaScript and WebAssembly builds produce identical output, sample for sample. But identical maths isn't the same as a working plugin. The plugin also has to load in a host, show its face, save and restore its state, and survive being opened twice.
Four kinds of "it works"
Different checks answer different questions about the module.
| Check | What it tells you | What it doesn't |
|---|---|---|
| Type check | The code is consistent: names, connections and parameter paths line up | Whether it makes sound, resets, or runs anywhere |
| Declaration check | The module's description is valid: ids, ranges, capacities, ports | Whether the sound is right |
| Package check | The right pieces are in the package, with the right fingerprints | Whether it runs in realtime or sounds clean |
| Build and run proofs | The browser and native builds process audio, events, resets and limits correctly, and agree | That any particular DAW accepts the plugin, unless that DAW was actually tested |
Read a build report as a list of questions, then ask which ones were really answered. "Type-checked" isn't "played in a DAW". "The bundle exists" isn't "the host loads it". "Passed last month" isn't "passes with today's code".
Unavailable, unsupported and untested are different
Here's what an honest release report looks like, one target at a time. It's an illustration built from how Maru's build works today, not the status of a real release.
<TraceStepper title="Read a release report" question="Which rows could you tell a user is supported?" trace='[{"title": "Browser, JavaScript", "at": "target 1", "text": "Built and run in a real browser: plays, resets, meets its deadline.", "values": {"status": "tested", "checks": "build, browser run"}}, {"title": "Browser, WebAssembly", "at": "target 2", "text": "Built from the same source. Its output matches the JavaScript build exactly.", "values": {"status": "tested", "checks": "build, output matches JavaScript"}}, {"title": "Plugin, macOS arm64", "at": "target 3", "text": "Compiled, passed the native test harness and the format's official validator.", "values": {"status": "tested", "checks": "build, harness, validator"}, "highlight": "status"}, {"title": "Apple format, macOS", "at": "target 4", "text": "Apple's plugin format only exists on macOS. On any other platform this row is unsupported, permanently. That is a fact about the format, not a gap.", "values": {"status": "unsupported elsewhere"}}, {"title": "Plugin, Linux x86-64", "at": "target 5", "text": "Declared, but cannot be built yet: there is no Linux toolchain on the build machine. The row names the missing piece instead of staying blank.", "values": {"status": "unavailable", "reason": "no Linux toolchain"}, "highlight": "reason"}, {"title": "Plugin, Windows", "at": "target 6", "text": "No build exists. Nothing can be claimed.", "values": {"status": "not built"}}, {"title": "Download link", "at": "release", "text": "A verified build can be staged, ready for upload. It only becomes a download once its publication is recorded. Staged is not published.", "values": {"status": "staged, not published"}}]' fallback="Browser JavaScript: tested. Browser WebAssembly: tested, output matches. Plugin, macOS arm64: built, harness and validator passed. Apple's plugin format: macOS only, unsupported elsewhere. Plugin, Linux: unavailable, no Linux toolchain. Plugin, Windows: not built. A staged build is not a published download.">
Four words do a lot of work here, and they mean different things to a user:
- Tested: built, and the check for this target actually ran and passed.
- Untested: a file exists, but nobody has run the check that would back a claim.
- Unavailable: the target is wanted but can't be built yet, and the reason is something someone can fix (a missing toolchain).
- Unsupported: the format doesn't exist there at all (Apple's plugin format on Windows). Not a to-do; a fact.
If a command prints SKIPPED and still exits successfully, that row is not a pass. Read the result, not just whether the command finished.
So, to your friend on Windows: "Not yet. It's tested on Mac as a plugin and in the browser. Windows hasn't been built." That's a less exciting answer than "yes", and a much better one to give before their session than after.
Features travel separately too
Even on a tested target, every feature has to make the whole trip. Take a sidechain: an extra audio input, like the kick that ducks a bass. Maru's engine and its browser host carry a sidechain today. The native plugin shells, in all three formats, currently give a plugin one input, so a module with an external key has to fall back to an internal source in those formats, and say so on its face. Silently ignoring the sidechain and calling it the same product would be the dishonest option.
The same goes for loaded sample banks, saved state, the face itself, MIDI input. Each needs checking in the format you ship, not just in the browser where you built it.
Same idea, different place: Adding a model to your app checks what a device can run before offering a feature. A plugin target needs the same habit: the maths compiling is only the start of the user's experience.
Test the install, not just the sound
A plugin can render perfect audio and still fail on someone else's machine: its face doesn't load, its bank isn't found, two copies interfere. So test like a user: a fresh host session, default settings, a few edits, a saved and reopened project, a preset recalled, two instances side by side. If the module needs content, try opening it when that content is missing, and make sure the message helps.
Our take: ship one target well, with a clear statement, before collecting format badges. Add the next target when your audience needs it, with its own build and its own checks. A small, true promise is easier to trust than a long, hopeful one.
Make it yours
Pick one target you can build today for your own module. Run your project's checks, then its plugin build, then actually load the result in a host and play it: edit a few controls, save, reopen, run two copies.
Then write a short compatibility table for it, one row per target you care about, with what ran and what didn't:
| Target | Status | What ran | What's missing |
|---|---|---|---|
| Browser | Tested | Played, reset, saved and reopened | Mobile browsers not tried |
| Plugin, macOS arm64 | Tested | Validator, opened in one DAW, preset recall | Other DAWs |
| Plugin, Windows | Not built | Nothing | A Windows build |
Save it beside the build, and turn the top two rows into one honest sentence for your users. Next release, update the table first.
Quick check
- Your module plays perfectly in the browser. Is the desktop plugin tested? No. It's a different file in a different host. It needs its own build and its own checks, even though the maths is shared.
- A report row says "unavailable: no Linux toolchain". Can you list Linux as supported? No. Unavailable means it hasn't been built. The reason tells someone what to fix; it isn't a pass.
- Your build is verified and sitting in a staging folder, ready to upload. Can you post a download link? Not yet. Staged means ready; a download exists only once it's actually published.
Next steps
- Use it: Building with an AI agent. You've made and checked a module by hand. How do you review these claims when an agent writes the change and the report?
- Go deeper: Shipping a sample library. When the sound is content, delivery has its own rules.













