Skip to main content

Overview

The IPlugReaperExtension example demonstrates how to create a REAPER extension using iPlug2. Unlike plugins, REAPER extensions are standalone modules that integrate directly into REAPER’s UI, providing custom tools, actions, and windows without audio processing constraints.
REAPER extensions are not audio plugins. They’re used for creating utility tools, custom actions, project management features, and specialized UI windows within REAPER.

Key Features

Native Integration

Access REAPER API functions directly, register custom actions, and integrate with REAPER’s menu system.

Dockable Windows

Create windows that can dock into REAPER’s interface or float independently with state persistence.

Custom Actions

Register keyboard shortcuts and menu items that execute your custom functionality.

IGraphics UI

Use the full IGraphics toolkit for building modern, cross-platform user interfaces.

Basic Structure

Header File

The extension class inherits from ReaperExtBase instead of the standard Plugin class:
#pragma once

#include "ReaperExt_include_in_plug_hdr.h"

enum EControlTags
{
  kCtrlTagText = 0,
  kNumCtrlTags
};

using namespace iplug;
using namespace igraphics;

class IPlugReaperExtension : public ReaperExtBase
{
public:
  IPlugReaperExtension(reaper_plugin_info_t* pRec);
  void OnIdle() override;
  void OnUIClose() override { mGUIToggle = 0; }
  
private:
  int mPrevTrackCount = 0;
  int mGUIToggle = 0;
};

Implementation File

#include "IPlugReaperExtension.h"
#include "ReaperExt_include_in_plug_src.h"
#include "IControls.h"
#include "roboto.hpp"

IPlugReaperExtension::IPlugReaperExtension(reaper_plugin_info_t* pRec)
: ReaperExtBase(pRec)
{
  // Import REAPER API functions you need
  IMPAPI(GetNumTracks);
  IMPAPI(CountTracks);
  IMPAPI(InsertTrackAtIndex);
  
  // Set up graphics creation function
  mMakeGraphicsFunc = [&]() {
    return MakeGraphics(*this, PLUG_WIDTH, PLUG_HEIGHT, PLUG_FPS);
  };
  
  // Register custom actions...
}

Registering REAPER API Functions

Before using REAPER API functions, you must import them using the IMPAPI macro:
// Import functions at construction
IMPAPI(GetNumTracks);
IMPAPI(CountTracks);
IMPAPI(InsertTrackAtIndex);
IMPAPI(ShowConsoleMsg);

// Now you can call them anywhere
int trackCount = CountTracks(0);
InsertTrackAtIndex(GetNumTracks(), false);
ShowConsoleMsg("Hello from extension!\n");
IMPAPI must be called in the constructor before any API functions are used. Calling unimported functions will result in crashes.

Registering Actions

Actions integrate your extension into REAPER’s command system:
1

Define Lambda Functions

Create lambda functions for your actions:
auto action1 = [](){
  MessageBox(gParent, "Action 1!", "Extension", MB_OK);
};

auto action2 = [](){
  InsertTrackAtIndex(GetNumTracks(), false);
};
2

Register with REAPER

Use RegisterAction() to make actions available:
// Basic action
RegisterAction("MyExtension: Action 1 - MsgBox", action1, true);

// Action without menu item
RegisterAction("MyExtension: Action 2 - AddTrack", action2);

// Toggle action with state tracking
RegisterAction("MyExtension: Show/Hide UI", 
               [&]() { ShowHideMainWindow(); mGUIToggle = !mGUIToggle; }, 
               true, &mGUIToggle);
3

Use in REAPER

Actions appear in REAPER’s Actions list and can be:
  • Assigned to keyboard shortcuts
  • Added to toolbars
  • Executed via scripting
  • Displayed in custom menus (if addMenuItem is true)

RegisterAction Parameters

void RegisterAction(
  const char* actionName,        // Action identifier
  std::function<void()> func,    // Function to execute
  bool addMenuItem = false,      // Add to extensions menu
  int* pToggle = nullptr         // Toggle state variable
);

Building the UI

The layout function works similarly to regular plugins, but integrates with REAPER’s docking system:
mLayoutFunc = [&](IGraphics* pGraphics) {
  const IRECT bounds = pGraphics->GetBounds();
  
  // Enable layout recalculation on resize
  pGraphics->SetLayoutOnResize(true);
  pGraphics->LoadFont("Roboto-Regular", (void*) ROBOTO_REGULAR, ROBOTO_REGULAR_length);
  pGraphics->AttachPanelBackground(COLOR_GRAY);
  
  // Add button that calls REAPER API
  pGraphics->AttachControl(new IVButtonControl(
    bounds.GetGridCell(0, 3, 1).GetPadded(-20.),
    [&](IControl* pCaller) {
      SplashClickActionFunc(pCaller);
      InsertTrackAtIndex(GetNumTracks(), false);
    }, 
    "Add Track"
  ));
  
  // Add dock toggle button
  pGraphics->AttachControl(new IVButtonControl(
    bounds.GetGridCell(1, 3, 1).GetPadded(-20.),
    [&](IControl* pCaller) {
      SplashClickActionFunc(pCaller);
      ToggleDocking();
    }, 
    "Dock"
  ));
  
  // Display track count with control tag
  WDL_String str;
  str.SetFormatted(64, "NumTracks: %i", CountTracks(0));
  pGraphics->AttachControl(
    new ITextControl(bounds.GetGridCell(2, 3, 1), str.Get(), 
                     IText(24, EAlign::Center)), 
    kCtrlTagText
  );
};

Real-time Updates with OnIdle

Use OnIdle() to update your UI based on REAPER’s state:
void IPlugReaperExtension::OnIdle()
{
  int tracks = CountTracks(0);
  
  if(tracks != mPrevTrackCount) {
    mPrevTrackCount = tracks;
    
    if(GetUI()) {
      GetUI()->GetControlWithTag(kCtrlTagText)->As<ITextControl>()
        ->SetStrFmt(64, "NumTracks: %i", tracks);
    }
  }
}
OnIdle() is called periodically (rate defined by IDLE_TIMER_RATE). It’s safe to call REAPER API functions here, but keep operations lightweight for good performance.

Docking System

Extension windows can dock into REAPER’s interface:
// Toggle between docked and floating
ToggleDocking();

// Check current state
if (IsDocked()) {
  // Window is docked
}
Dock state (position, size, docked/floating) is automatically saved to REAPER’s INI file and restored on next launch.

Configuration

Minimal configuration is needed in config.h:
#define PLUG_CLASS_NAME IPlugReaperExtension
#define PLUG_WIDTH 300
#define PLUG_HEIGHT 300
#define PLUG_FPS 60
#define PLUG_SHARED_RESOURCES 0

#define ROBOTO_FN "Roboto-Regular.ttf"
#define SHARED_RESOURCES_SUBPATH "IPlugReaperExtension"
Extensions don’t need the typical plugin identifiers (PLUG_UNIQUE_ID, PLUG_MFR_ID, etc.) since they’re not audio plugins.

Common Patterns

Responding to Project Changes

void OnIdle() override
{
  // Track when project changes
  static ReaProject* lastProject = nullptr;
  ReaProject* currentProject = EnumProjects(-1, nullptr, 0);
  
  if (currentProject != lastProject) {
    lastProject = currentProject;
    // Project switched - update UI
  }
}

Working with Track Selection

IMPAPI(GetSelectedTrack);
IMPAPI(SetTrackSelected);
IMPAPI(CountSelectedTracks);

auto processSelectedTracks = [&]() {
  int selCount = CountSelectedTracks(0);
  for (int i = 0; i < selCount; i++) {
    MediaTrack* track = GetSelectedTrack(0, i);
    // Process track...
  }
};

Showing Console Output

IMPAPI(ShowConsoleMsg);

// Debug output to REAPER console
WDL_String msg;
msg.SetFormatted(256, "Processing %d items\n", itemCount);
ShowConsoleMsg(msg.Get());

Platform Support

Windows and macOS

REAPER extensions are supported on both Windows and macOS. The iPlug2 framework abstracts platform differences, but be aware:
  • Windows: Uses Win32 dialogs and HWND
  • macOS: Uses NSWindow wrapping
  • Docking system works identically on both platforms

Extension vs Plugin

Extensions

  • No audio processing
  • Direct REAPER integration
  • Custom actions and menus
  • Dockable windows
  • Full API access
  • Single instance per REAPER

Plugins

  • Audio/MIDI processing
  • Hosted in FX chain
  • Parameter automation
  • Multiple instances
  • Limited host interaction
  • Standard plugin formats

Resources

When developing extensions, use REAPER’s console (View → Show REAPER console) to debug with ShowConsoleMsg() output.