# foleys_gui_magic gives a JUCE plugin a DOM and a stylesheet

> foleys_gui_magic is a JUCE module that lets you build a plugin interface without writing layout code, by describing the interface as a DOM tree styled with a cascading stylesheet, and it ships a drag and drop editor, a FireBug style inspector and a component library, at the price of an extra layer between your processor and JUCE.

**ffAudio/foleys_gui_magic** — A GUI builder module for JUCE - with examples

- Repository: https://github.com/ffAudio/foleys_gui_magic
- Website: https://foleysfinest.com/developer/pluginguimagic/
- Stars: 600 · Forks: 66
- Language: C++
- License: NOASSERTION
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/ffaudio-foleys-gui-magic

## A DOM model and a cascading stylesheet

foleys_gui_magic is a GUI builder module for JUCE, and the central idea is that a plugin interface can be described the way a web page is.

The module allows you to create GUIs without any coding. It is created with a DOM model that provides a hierarchical information, and a CSS cascading stylesheet to define rules for the appearance of the GUI. So the structure of the interface is a tree, and the look of it is a stylesheet, and the two are separate concerns.

That separation is the whole value proposition for a plugin developer. In plain JUCE, a panel of sliders is a nested set of addAndMakeVisible calls with hard-coded positions, and changing the layout means editing and recompiling. Here, the layout lives in a document, the appearance lives in a stylesheet, and a designer can work on either without touching the C++.

Two tools come with it. There is a drag and drop editor to add GUI elements and to connect them to parameters of your AudioProcessor, which is the what-you-see-is-what-you-get part. And there is an editor in the style of FireBug to investigate the individual properties, and how they were obtained or calculated, which is a debugging view of the same tree.

The FireBug style inspector is the detail that tells you the model is a real DOM rather than a convenience wrapper, because you can see where every value in the interface came from.

## The module, the examples and the test suite

The repository map tells you how the project is laid out and how a consumer is expected to use it.

The module to add to your project is in modules/foleys_gui_magic, and the guidance is that ideally you just use the juce_add_module() call in your CMakeLists.txt or add it via Projucer. So the integration path is the standard JUCE module mechanism, which is the answer to the question of how a third party module is meant to be consumed.

In the Examples folder you find the example projects, and the build will run pluginval on them where it applies. The CMake option for the examples is FOLEYS_BUILD_EXAMPLES, and for pluginval it is FOLEYS_RUN_PLUGINVAL. The example projects listed in the tree are APVTS_Tutorial, EqualizerExample, ExtendingExample, FoleysSynth, PlayerExample, SignalGenerator and Vanilla, which is a decent map of what the module can be used for, from a tutorial through to extending the component set.

In Tests there are the Catch2 unit tests, and each build will run those unit tests against the latest version, with the CMake option FOLEYS_BUILD_TESTS. CMake is responsible for fetching JUCE, pluginval and Catch2 when those options are switched on, so the dependencies are resolved by the build system rather than by hand.

The development setup also includes CMakePresets.json, a .clang-format file, a CONTRIBUTING.md, a DESIGNING.md, a VERSION.md and a doxygen/ directory, and the API documentation is published at foleysfinest.com/foleys_gui_magic/.

## Setup: a different base class and one macro

The setup section is short and specific about what changes in your processor class.

Instead of inheriting from juce::AudioProcessor, you inherit from foleys::MagicProcessor. Three of the usual methods go away: bool hasEditor(), juce::PluginEditor* createEditor(), and void setStateInformation() and void getStateInformation(), with the last pair marked optional because a default saving and loading method is provided. The MagicProcessor will provide a foleys::MagicProcessorState magicState member with protected visibility, which is what you use to add visualisations or other objects to expose to the GUI.

Then there is one macro. The floating editor has an auto save, which needs to know the location of your source files, and to activate that you copy a macro into the constructor of your MagicProcessor:

```cpp
FOLEYS_SET_SOURCE_PATH(__FILE__);
```

Otherwise autosave starts working once you have loaded or saved the state, since then the editor has a location to use. That fallback is the kind of detail that saves an afternoon of wondering why autosave does nothing.

The module is not limited to plugins. It is also possible to use it in an Application, in which case you add a MagicGuiState and a MagicGUIBuilder yourself, and the PlayerExample example shows that path.

## Visualisations are objects, not drawings

Adding a visualisation such as an Analyser or an Oscilloscope is done in the constructor, and the pattern is that you create the object, prepare it, and then push samples into it.

```cpp
// Member:
foleys::MagicPlotSource* analyser = nullptr;

// Constructor
analyser = magicState.createAndAddObject<foleys::MagicAnalyser>("input");

// prepareToPlay
analyser->prepareToPlay (sampleRate, samplesPerBlockExpected);

// e.g. in processBlock send the samples to the analyser:
analyser->pushSamples (buffer);
```

The important part is the last step of the wiring. You create the object with a name, input in this case, and then in the editor you drag a Plot component into the user interface and select the name you supplied as its source. So the plot is bound to the data source by name, at design time, without the editor needing to know anything about your C++.

That is the same idea the parameter binding uses, applied to a continuous data stream, and it is why a meter or a scope in the interface is not a special case in the component set.

## Settings, presets and baking the interface into the binary

The MagicProcessor takes care of saving and restoring the parameters and properties, and you can add your own values to the ValueTree in the magicState. There is a callback after restoring that you can override, in case you need to do some additional action.

Beyond the plugin state, you can have settings across all instances of your plugin, which needs a path to a file:

```cpp
// in constructor
magicState.setApplicationSettingsFile (juce::File::getSpecialLocation (juce::File::userApplicationDataDirectory)
                                       .getChildFile (ProjectInfo::companyName)
                                       .getChildFile (ProjectInfo::projectName + juce::String (".settings")));

// use it like
presetNode = magicState.getSettings().getOrCreateChildWithName ("presets", nullptr);
presetNode.setProperty ("foo", 100, nullptr);
```

The file is not a flat list, and it can deal with hierarchical data in the form of ValueTrees. PluginGuiMagic deals with interprocess safety and updates if the file has changed, which is the part that would otherwise be a race between two instances of the plugin writing the same settings file.

The last piece of the workflow is shipping the interface. To bake in the GUI, you save the XML from the PGM panel and add it to the BinaryResources via Projucer, and in the constructor you set the XML file:

```cpp
magicState.setGuiValueTree (BinaryData::magic_xml, BinaryData::magic_xmlSize);
```

So the designed interface becomes a compiled-in resource, which is what makes the WYSIWYG editor safe to leave out of a release build.

## The component set, and how to add your own

The available components are listed, and the list is short enough to be the actual API surface of the interface layer.

A Slider, a ComboBox, a ToggleButton and a TextButton, each attachable to parameters. An XYDragComponent, attachable to two parameters, which is how a two dimensional control is bound. A Plot for two dimensional data and a LevelMeter for RMS and maximum levels, both of which connect to the visualisation objects described earlier. Then a Label, a MidiKeyboardComponent, MidiLearn, a ListBox and a WebBrowserComponent.

All components have the option to add margins and paddings and a border, also with rounded corners, which is where the stylesheet starts to matter. The View component serves as a container, and it has the option either to layer all child components on top of each other or to lay them out using flex-box.

It is completely possible to register your own bespoke components into the builder, and the ExtendingExample in the examples folder is presumably where that is demonstrated.

One deployment detail is worth planning for. The WYSIWYG editor panel attached outside the PluginEditor is switched on or off with the Projucer config named FOLEYS_SHOW_GUI_EDITOR_PALLETTE, and the recommended approach is to add FOLEYS_SHOW_GUI_EDITOR_PALLETTE=0 into the extra preprocessor defines of the release build, so that you design in debug mode and the editor is not included in the release build for deployment.

## A custom licence, a 2023 release and a branch that is going away

The licence is recorded as a custom one that GitHub cannot classify, with the terms in the LICENSE.md file, so they are worth reading directly rather than assuming a standard grant.

The release history is where a prospective user should look carefully. The recent tags are v1.4.0 published on 2023-07-27, v1.3.9 on 2023-07-13 and v1.3.8 on 2022-10-31. The repository is not archived and the last push was on 2026-08-15, so the source is being worked on, but the newest published version is more than three years old.

The deprecation note at the top of the README explains the branch strategy and is unusual enough to quote. The branch develop will not be updated and will be deleted soon, and development is done in feature branches which are merged directly to main, so you can always fast-forward the main branch. If you had a workflow that tracked the develop branch, that stops working, and the fix is to track main.

The support channels are also worth noting. There is a forum for the modules of Foleys Finest Audio, a Discord server, and a stated willingness to take feedback. The contributing guidance asks for ideas as GitHub issues and pull requests, and says maintainers may ask for changes to keep the style consistent and the API as concise as possible, which is a reasonable request for a module that sits between a developer and their IDE.

## Conclusion

foleys_gui_magic fits a JUCE developer who ships several plugins and finds that rebuilding the same knob panel for each one is the repetitive part of the work, since the DOM and stylesheet approach lets a designer change the look without touching C++. It does not fit a project with one small interface, and it does not fit a team that wants its GUI to be plain JUCE code with no framework in between. Before adopting it, check the JUCE and CMake versions your project pins against the module, read the develop branch deprecation note, and plan for the GUI description to become a build artefact, since the documented path is to bake the XML into BinaryResources.

## FAQ

### What is foleys_gui_magic?

It is a JUCE module that lets you create plugin GUIs without coding, using a DOM model for the hierarchy and a CSS cascading stylesheet for the appearance, with a drag and drop editor to connect elements to your AudioProcessor parameters and a FireBug style editor to inspect properties.

### How do I add the module to a JUCE project?

The module lives in modules/foleys_gui_magic and the guidance is to use the juce_add_module() call in your CMakeLists.txt or add it via Projucer. CMake fetches JUCE, pluginval and Catch2 when the example and test options are switched on.

### Which JUCE methods do I no longer need to write?

By inheriting from foleys::MagicProcessor instead of juce::AudioProcessor you can drop hasEditor(), createEditor(), and setStateInformation() and getStateInformation(), the last pair being optional because a default saving and loading method is provided.

### How do I add an analyser to the interface?

Create the object in the constructor with magicState.createAndAddObject<foleys::MagicAnalyser>("input"), call prepareToPlay on it, push samples into it from processBlock, and then in the editor drag a Plot component and select the name you supplied as its source.

### How do I ship a designed interface and keep the editor out of the release?

Save the XML from the PGM panel, add it to the BinaryResources via Projucer and set it with magicState.setGuiValueTree, and define FOLEYS_SHOW_GUI_EDITOR_PALLETTE=0 in the release build so the WYSIWYG editor is excluded from deployment.

## Sources

- [ffAudio/foleys_gui_magic on GitHub](https://github.com/ffAudio/foleys_gui_magic)
- [Issues](https://github.com/ffAudio/foleys_gui_magic/issues)
- [Project website](https://foleysfinest.com/developer/pluginguimagic/)
- [README](https://github.com/ffAudio/foleys_gui_magic/blob/main/README.md)
- [Releases](https://github.com/ffAudio/foleys_gui_magic/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ffaudio-foleys-gui-magic
