Every REAPER extension, from SWS to our own tools, is built on the same small kit from Cockos: the REAPER extension SDK. This piece explains how it works, for producers curious about what happens when an extension loads and for developers wondering how to make a REAPER extension. At the end we show how Analyse 1 and Kerf Modulators use it.
What the REAPER SDK is
The REAPER extension SDK is a set of C and C++ header files, a couple of example extensions, and the source to some of the plug-ins that ship with REAPER, such as the control surface support and the MP3 decoder. Cockos publishes it on GitHub, and the Extensions SDK page on reaper.fm links to it.
The two headers that matter most are:
reaper_plugin.h, which defines the entry point, the structures REAPER passes in, and the things an extension can register.reaper_plugin_functions.h, a generated list of the REAPER API: the functions an extension can call. The copy we work from was generated by REAPER 7.78 and declares more than 860 functions.
There is no framework or runtime library to link. An extension is a native .dll, .dylib or .so that REAPER loads from its UserPlugins folder at startup, and everything else happens through a few function pointers.
How a REAPER extension starts: ReaperPluginEntry
Every extension exports one function, ReaperPluginEntry. The SDK defines it through the REAPER_PLUGIN_ENTRYPOINT macro:
extern "C" REAPER_PLUGIN_DLL_EXPORT int
REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance,
reaper_plugin_info_t *rec)
{
if (!rec)
{
// REAPER is unloading us: unregister everything and clean up
return 0;
}
if (rec->caller_version != REAPER_PLUGIN_VERSION || !rec->GetFunc)
return 0;
// import API functions, register actions and windows...
return 1;
} REAPER calls it once when it loads the library. Returning 1 says “I am compatible, keep me loaded”; anything else and REAPER unloads the extension. When REAPER is done with the extension it calls the same function again with rec set to NULL, which is the signal to undo everything.
plugin_info_t and GetFunc: how an extension reaches the REAPER API
The rec argument is a reaper_plugin_info_t. It carries a version number, the handle of REAPER’s main window, and two function pointers:
GetFunc(name), which returns a pointer to any REAPER API function by name, or null if it does not exist.Register(name, info), which registers something with REAPER. The same function is also available asplugin_registerthroughGetFunc.
An extension does not link against REAPER. It asks for each function by name at startup:
CountTracks = (int (*)(ReaProject*)) rec->GetFunc("CountTracks"); The SDK can do this for you. Define REAPERAPI_IMPLEMENT in one source file, include reaper_plugin_functions.h, then call REAPERAPI_LoadAPI(rec->GetFunc) from the entry point. It returns 0 when every function loaded. Missing functions come back as null, so a careful extension checks the ones it needs.
This one mechanism is the whole REAPER API from C++. The same API is what ReaScript calls into from Lua and EEL2, and an extension can add to it: registering API_myfunction makes a function available to other extensions, and APIdef_myfunction exposes it to ReaScript too. That is how js_ReaScriptAPI and ReaImGui give scripts new abilities.
Registering actions, commands and dockable windows
Most of what an extension adds to REAPER goes through plugin_register. The header lists dozens of registration types. A few that most extensions use:
- Actions.
custom_actionadds an action to a section of the action list, andgacceladds one to the main section with an optional default shortcut. Each returns or uses a command ID. - Command hooks.
hookcommandandhookcommand2register a function that REAPER calls before an action runs. The extension checks the command ID and returns true if it handled it. - Toggle state.
toggleactiontells REAPER whether an action is currently on or off, so menus and toolbars can show a tick or a lit button. - Timers.
timerregisters a function REAPER calls periodically, which is where a lot of interface work happens. - Project state.
projectconfiglets an extension save its own data into the.RPPproject file and read it back when the project loads.
Every registration has a matching removal: prefix the name with a minus sign, as in plugin_register("-gaccel", &accel). A well-behaved extension removes everything it registered before it unloads, so REAPER never holds a pointer into code that has gone.
Dockable windows come from the API rather than plugin_register. An extension creates a window, then hands it to REAPER’s docker with DockWindowAddEx, giving it a name and a unique identifier string. REAPER then docks, floats and remembers it like its own panels.
Audio hooks: Audio_RegHardwareHook and audio_hook_register_t
Extensions can also reach the audio itself. An extension fills in an audio_hook_register_t and passes it to Audio_RegHardwareHook(true, ®). REAPER then calls the extension’s OnAudioBuffer function from the audio thread twice for every block: once before REAPER processes it (isPost false) and once after (isPost true). Inside that callback, and only there, GetBuffer returns pointers to the hardware input and output channels for that block.
The header’s own comment on this is short: you should be careful with it. The audio thread has a hard deadline. If the callback waits on a lock, allocates memory or does anything else that can take an unpredictable time, REAPER can miss the deadline and you hear a click or dropout. The rules are the same as for any real-time audio code:
- no locks that another thread might hold
- no memory allocation or freeing
- no file, network or user interface calls
- do the minimum, copy what you need, and return
The usual pattern is to copy samples into a lock-free buffer and do the heavy work on another thread.
Reading project and track state
Away from the audio thread, the API gives an extension a broad view of the project. For example:
CountTracksandGetTrackwalk the tracks in a project.GetSetMediaTrackInforeads and writes track attributes.TrackFX_SetParamandTrackFX_SetParamNormalizedchange a parameter on any plugin on any track.SetExtStatestores settings that persist between sessions, andSetProjExtStatestores data that belongs to one project.GetAppVersiontells the extension which REAPER it is running in.
How extensions differ from VST plugins
A VST, VST3 or CLAP plugin lives in one slot of one FX chain. The host hands it audio and MIDI, it hands audio back, and it owns its own parameters. It does not normally see the rest of the project.
An extension sits beside REAPER rather than inside a track. It can add actions and menus, open dockable windows, read every track, change any plugin’s parameters, save data in the project, and tap the hardware audio stream. What it does not do is process audio inside an FX chain. That is why some tools have to be extensions: a meter that reads the master output without being inserted anywhere, or a panel that controls parameters on several plugins at once.
SWELL on macOS and Linux
REAPER’s interface code is written against the Win32 API. On macOS and Linux, Cockos provides SWELL, the Simple/Small Win32 Emulation Layer, part of the WDL library. It implements enough of Win32 (windows, dialogs, menus, drawing) on top of Cocoa on macOS and GDK on Linux for the same code to run on all three platforms.
For an extension this matters in one practical way: a window you want REAPER to dock should be a SWELL window on macOS and Linux. If your interface uses another toolkit, create a SWELL window, dock that, and place your own view inside it.
The SDK’s licence
The SDK headers carry a short permissive licence in the style of the zlib licence. You can use them for any purpose, including commercial products, as long as you do not misrepresent where the original came from, you mark altered versions as altered, and you leave the notice in place. SWELL and WDL use the same style of licence. Some of the bundled plug-in source has its own terms (the MP3 decoder folder carries its own licence file), so check the licence on any file you reuse.
How Kerf’s tools use the REAPER SDK
Both of our REAPER products are extensions written in C++ against this SDK, with JUCE for the interface and SWELL for docking. Both need REAPER 7.8 or later.
Analyse 1
Analyse 1 is our free metering strip. Here is how it is put together:
- Start-up.
ReaperPluginEntrychecks the version, imports the functions it needs throughGetFunc, and registers a show/hide action withgaccel, a command hook, a toggle state for the menu tick, a menu entry under Extensions, timers, and aprojectconfighandler so the project remembers whether the panel was open. - The audio tap. It registers an
audio_hook_register_twithAudio_RegHardwareHook. On the audio thread it ignores the pre-processing pass and, on the post pass, reads the final hardware output: REAPER’s output after the master. It copies the left and right samples into a lock-free single-producer, single-consumer ring buffer and returns. Its scratch buffers are sized up front, so the audio thread never allocates and never takes a lock. If the ring is ever full, the newest samples are dropped rather than blocking. - Measuring and drawing. On the user interface thread, a timer drains the ring about 50 times a second and does the real work: momentary and short-term LUFS, VU, sample peak, phase correlation and the goniometer, the waveform scope and the spectrum. Each panel then draws the latest result.
- Docking. The interface lives inside a SWELL window that is handed to REAPER with
DockWindowAddEx, so it behaves like any other REAPER docker. - Shutting down. When REAPER calls the entry point with
NULL, it unregisters everything, including the dock and the audio hook.
Because it reads the hardware output, there is no plugin on the master. It is coming soon, free, on Linux first, with macOS to follow.
Kerf Modulators
Kerf Modulators is coming soon, at a £19 beta price. It is a docked panel that gives every plugin on a track its own modulators: envelopes, LFOs, drawn waves and followers, mapped to any parameter and saved with the project. In SDK terms that means a docked SWELL window, plugin parameters driven through the REAPER API’s TrackFX functions, and the modulation saved into the project through projectconfig.
Where to start if you want to build one
Clone the reaper-sdk repository and WDL, read the comments in reaper_plugin.h from top to bottom, and build one of the examples. Cockos also points to the SWS source as a good set of worked examples. On Windows the C++ interfaces need the MSVC-compatible ABI, so build with MSVC or a compatible compiler such as clang-cl. If you would rather just use extensions, our guide to the REAPER extensions worth installing covers the popular ones, our REAPER extensions page covers what we make, and support is there if you get stuck.
Kerf Audio Team (2026). The REAPER SDK: how extensions work, and how our tools use it. Kerf Audio. https://kerfaudio.com/journal/reaper-sdk-explained/
@article{kerf2026,
title = {The REAPER SDK: how extensions work, and how our tools use it},
author = {},
year = {2026},
journal = {Kerf Audio},
url = {https://kerfaudio.com/journal/reaper-sdk-explained/}
}