About a year ago, I decided to spend a bit more time learning Clojure to be able to contribute more to the Defold Editor. During this journey I struggled a lot, mostly because I didn’t know about different tools and tricks I could use to make my life easier. The biggest headache was constant editor reloading when I needed to test something I had just written, which was totally unnecessary (I wish I had asked about it earlier).
Here I’m trying to collect all the tips I got from @mats.gisselson and @vlaaad which helped me work on Defold Editor tasks with joy.
Build Editor
The main thing you have to know is that you don’t have to build the engine if you want to develop only the editor.
Open this README.md and follow instructions for your platform.
Long story short:
- Clone the Defold repo
- Install JDK and Leiningen
- Use tsese commands, but instead of
<sha1>, pick the SHA1 of the latest commit from the Defold repo - Set up IntelliJ IDEA CE + Cursive and use this manual for the IDE setup
It’s basically the same info as in the Defold repo, but I just wanted to show that it’s easy. Yes, the full documentation is much bigger than that, but you don’t need it for now. For now, you just need the Repo/JDK/Leiningen/IDE - you are awesome!
REPL
Using the REPL is the very first thing you need to figure out.
Read this instruction one more time. Use the recommended shortcuts or your own, especially for Load File in REPL and Send function to REPL. I know that switching the REPL namespace is also very useful, but I’m not an expert and barely use it, though I should.
In the manual, you see suggestions like:
Suggested shortcut: Cmd+R followed by D.
That means you set hotkey Cmd+R, then check the Second stroke: checkbox and type D.
If you do as suggested, all your communication with the REPL will be Cmd+R and then one of the keys for running a command.
There is a suggestion in the manual “Clear all Test markers: Cmd+R followed by C” - don’t assign it for now, and here is why…
When you reload a function or the whole file, sometimes you see that your changes haven’t been applied. It’s very annoying and I reloaded the editor a thousand times because of that. Even though I knew about some command to clear cache, I barely used it and always forgot, until the tip Mats gave me:
- Open Preferences > Languages & Frameworks > Clojure > REPL Commands
+to create a new Global Command- Name it “Clear Caches”
- Pick Execute Command
(do
(require 'dev)
(dev/clear-caches!)
:caches-cleared!)
- Save
- Open Keymap and find your command by name “Clear Caches”
- Assign Cmd+R followed by C
Now you can clear cache using a hotkey and then reload the function after that and be sure the changes were applied!
Reflection warnings
Working with REPL, it’s important to reload the whole file from time to time because reloading the file may return reflection warnings. Reflection warnings occur when the compiler can’t determine the type of a Java object at compile time and must use slower runtime reflection to access its methods or fields. To fix them, add type hints (e.g., ^KeyEvent, ^YourType) to your code so the compiler knows the exact type and can avoid reflection.
Boxing warnings
Similar to reflection, boxing can quietly hurt performance. Boxing happens when primitive values like int, long, or double are wrapped into objects like Integer or Double, often during math operations. This usually occurs when the compiler cannot keep the computation in primitive form and has to allocate objects instead, which can cause significant slowdowns.
To catch these issues early, enable boxing warnings near the existing reflection warning compiler directive. With this enabled, the compiler will warn whenever boxed math occurs, making it easier to spot and fix performance issues. Boxing problems are typically resolved by adding appropriate type hints or using unchecked math operators so calculations stay in primitive form.
(set! *unchecked-math* :warn-on-boxed)
Development session
A regular Editor development session for me looks like:
lein runin terminal (in IDEA’s terminal) → load needed test project- Connect REPL
- Make fixes in needed functions and reload these functions using Cmd+R followed by D
- Check changes in the editor. Sometimes it requires reopening a file (e.g., if you’re testing an input event which is registered when a tilemap scene is opened, then you need to close and open the tilemap)
- Sometimes I switch namespace to current and run a function I wrote and loaded in REPL with some Editor’s data to check output and make sure I wrote it right, so I can now use it in the needed place
- Repeat 3, 4, 5 until I fix the issue
- When everything works as I like, reload the whole file(s) in REPL to make sure I don’t have reflection warnings. If I have some, I fix them (this step I forget pretty often)
- Add tests if possible
- Close the editor, open it again, and retest the fix (sometimes it’s possible to introduce circular dependencies or similar issues which you can’t catch in REPL, only on reload)
Here @mats.gisselson explains how a typical development session looks for him: How I work on the Defold Editor · GitHub
Building
Here’s the command to build the app version of the editor - the same way it’s packaged for delivery:
./scripts/bundle.py build --platform=arm64-macos --version MY_VERSION --engine-artifacts SHA1 --skip-tests
This feature could be useful for specific OS-level tasks, such as displaying a dock menu for an application.
Edit CSS styles
All changes should be done in scss files, and it is possible to build and hot-reload scss.
Run
lein sass auto
in the editor folder. This daemon will detect all changes in scss files and compile css files automatically.
Then call Help->Reload Stylesheet in the Defold Editor itself.
Profiles
Use lein with-profile +performance run when checking editor runtime performance; it disables exception decoration, schema checks, and compiled spec assertions.
Editor Extensions
There are a few Defold Editor extensions available in the official Defold org: extension-spine, extension-rive, extension-simpledata, extension-texturepacker, etc.
To be able to navigate in extensions, use code formatting the same as in the project, etc., do the following:
- Create
profiles.cljfile in~/.lein/profiles.clj - Add a list of paths to the extension folders in the :source-paths array
{:user {:source-paths []}} - Run
lein init <sha1>again - In IDEA, right-click on
project.cljand clickRefresh Leiningen Projects
Exampleprofiles.clj:
{
:user {:source-paths ["/Users/agulev/projects/extension-rive/defold-rive/editor/src"
"/Users/agulev/projects/extension-simpledata/defold-simpledata/editor/src"
"/Users/agulev/projects/extension-spine/defold-spine/editor/src"
"/Users/agulev/projects/extension-texturepacker/texturepacker/editor/src"]}
}
You can also define profiles for local extensions and use them for testing:
{:local-extension-lua-preprocessor {:jvm-opts ["-Ddefold.extension.lua-preprocessor.path=/Users/agulev/projects/extension-lua-preprocessor"]}
:local-extension-rive {:jvm-opts ["-Ddefold.extension.rive.path=/Users/agulev/projects/extension-rive"]}
:local-extension-simpledata {:jvm-opts ["-Ddefold.extension.simpledata.path=/Users/agulev/projects/extension-simpledata"]}
:local-extension-spine {:jvm-opts ["-Ddefold.extension.spine.path=/Users/agulev/projects/extension-spine"]}
:local-extension-teal {:jvm-opts ["-Ddefold.extension.teal.path=/Users/agulev/projects/extension-teal"]}
:local-extension-texturepacker {:jvm-opts ["-Ddefold.extension.texturepacker.path=/Users/agulev/projects/extension-texturepacker"]}
:local-extensions [:local-extension-lua-preprocessor
:local-extension-rive
:local-extension-simpledata
:local-extension-spine
:local-extension-teal
:local-extension-texturepacker]
:user {:source-paths ["/Users/agulev/projects/extension-rive/defold-rive/editor/src"
"/Users/agulev/projects/extension-simpledata/defold-simpledata/editor/src"
"/Users/agulev/projects/extension-spine/defold-spine/editor/src"
"/Users/agulev/projects/extension-texturepacker/texturepacker/editor/src"]}}
Then run:
lein with-profile +local-extension-spine,+local-extension-texturepacker
or
lein with-profile +local-extensions
The way it works: test projects have stuff like dependencies#0 = {{defold.extension.spine.url}} in game.project. The editor will replace these with the corresponding JVM defines during development.
Defold Editor Development functions
- Cmd+T in scene view switches render modes. I don’t know the exact order, I just know that the second view (after pressing Cmd+T twice) shows the render target responsible for click detection.
- There is a text field in
Preferences -> Devwhere it’s possible to specify a customdmenginewhich will be run on Cmd+B.
Here’s an updated version of the passage:
Clojure in Defold Editor and coding agents
When I need to find where an editor interaction is implemented, I describe it to Codex from the user’s point of view: what I clicked, what I expected, and what happened. I ask it to trace the relevant handlers, graph nodes, and tests, then verify the suggested entry points in the source.
For unfamiliar Clojure, I ask focused questions about a form’s inputs, outputs, and callers. I use the live REPL to check my understanding against the running editor.
I also use coding agents for focused changes, but I review every Clojure diff. I check for unintended changes to surrounding forms, clear and idiomatic code, reflection, and boxed math. Then I run the relevant lint checks and tests. For editor UI changes, I verify the behavior in the running editor with cljfx.plorer.
Coding agents are useful for exploring the codebase and implementing scoped changes. Their explanations and patches still need verification, especially when a change crosses several namespaces or affects performance.
Inspecting the Editor UI with cljfx.plorer
Defold includes cljfx.plorer for exploring the running JavaFX interface from the live REPL. LLM may use it to inspect the scene graph, find a control by its ID or properties, and send mouse or keyboard events while checking a UI change.
Most of the text is just a compilation of answers I got from @mats.gisselson and @vlaaad when I shared my struggles with them.
I’m sure there are more useful things I don’t know about, but I wanted to share what I already have and make sure this knowledge is available to everyone.
The LLMs section is about my personal experience and what helped me get started with the editor code.
Nowadays, LLM tools are better than they were a year ago, when I had to copy and paste functions—and sometimes full files—so the LLM could follow the logic and explain it to me.
I hope it will be useful for someone.
UPD: updated 26.09.2026
