Copy Files On Export - my first Godot addon
Wed Mar 19 2025
So, usually I try to use this space for some form of tutorial-type article, but I figured I'd change it up and try to write, like, a regular-ass blog where I just share an experience of mine.
I've been wanting to write an addon for the Godot game engine for a while, and for multiple reasons. First of all, obviously for the experience. I've written "internal" addons for use with The Rise of the Golden Idol, but doing anything open-source, no matter the scope and size, is always a fun challenge. You feel exposed since your work is there for everyone to see. Not just the result, but also the internals, and just like in game development, it's interesting to see how the product might evolve from what you had in your mind to how it ends up actually being. Maybe someone from the outside will contribute.
Another reason is to contribute to the Godot ecosystem. I'm no engine programmer, but I can certainly cook up some GDScript that, hopefully, someone else finds useful.
And finally, writing tools for Godot is plain fun. I love that you can hook into anything, easily add your own UI to the existing UI, and make something that looks like a natural part of the engine editor.
Now all that is fine and all, but I've got to make the thing first. And before I get to make it, I need to decide what it's going to be in the first place. I don't know how about you, but a lot of times when I want to start working on a thing, it feels like it's already been made. Don't get me wrong; I'm in it just as much for the process as the end result, but I want the result to be something that doesn't feel like a clone of something that already exists. Or, at least, it should be something whose equivalent I can't immediately find on the Godot AssetLib.
Anyway, it's kinda natural that tools are born out of necessity. One evening I was working on a little game project, and just as I tend to, I got distracted by details, in particular, third-party licenses. I had barely started on the project, and I was already starting to think about how I'd put third-party licenses in the credits screen. A normal person just waits until the game is done, then makes the credits with whatever info that's needed, then ships the game, then goes to have a beer to celebrate.
But, you see, I was in the middle of browsing fonts, which was the distraction that led me into the licensing distraction I mentioned above. I thought that, "OK, I'll pick a font and copy the license into the credits right away so I don't forget, but what if I change the font sometime later but forget to update the license text? I must automate this somehow!" A very rational idea I justified to myself entirely, because we changed a font for The Rise of the Golden Idol halfway through its development, which instantly makes it a common problem that needs to be solved immediately!
I'll spare you the stupid ideas I had in between, but eventually I arrived at the idea that I can just copy the license text to the file system alongside the game files instead of putting it in the credits, and I'd be alright in the eyes of the law. Not that I expect to ever get sued, especially over a game I'm not sure that will even be finished, but it's more about the fun of trying to solve a problem (which isn't even being a problem to begin with).
The solution I was going for right away was to just add it as a job to my Github Actions pipeline. I have a template that I use that automatically exports games and uploads them to itch.io. And as you might guess, I have never used it for any game that I have published to the public. Me finding solutions to problems I don't have is a pattern you might have already noticed.
Going this way would have been a couple of line changes, and I would have been free to return to my previous distraction of browsing fonts, but I had an issue with the fact that my automated exports would differ from exports I make from my own machine, so what if I could somehow make the Godot editor do that for me independently from my CI/CD setup?
Not too long, and my searches lead me to the EditorExportPlugin class. It has functions that are called when the export begins, ends, or for every file that gets exported, among other things. Its primary use is to determine what files are being included in the exported project. All I needed was the _export_begin()
hook.
It's a function that gets called when the export starts, but conveniently when the target path has already been created, so I just had to add the code that copies the license files to that path, and I'm done. It worked, surprisingly, without any issues, but I wasn't feeling like the job was done just yet. I wanted to get rid of the hardcoded array of target/destination pairs, which was standing out like a bad metaphor in an otherwise decent blog post. I wanted to make it externally configurable.
Do I make it into some form of an "ini" (a.k.a., ConfigFile)? Do I go a little cheeky with a TOML? It's a list of pairs; it's gonna be ugly anyway. Eventually I landed on just making a UI. Eventually I realized that this might as well be my plugin.
Thankfully this all happened during my vacation. I was excited enough that concentrating on work would be difficult, so I could just continue on working on this thing right away on the following day. I was very naive in estimating how long that would take me. I thought that I'd be done by the afternoon—just a quick UI, some docs, and off to the AssetLib we go, and then I'll go play some Fallout to reward myself.
First of all, when scaffolding the file structure of my addon, I realized that I needed a name. In my game project I had called the addon "File Includer," which I didn't mind as a name as it was an internal addon, but for something that I meant to publish for people to see, I don't know, it felt to me like if the color beige was an addon name. Which is slightly ironic, considering I eventually ended up calling it "Copy Files On Export," which is in the same tier of quality of a name for a thing as "File Includer." It doesn't even resolve to a funny acronym! But it's not like I'm making the next Coca-Cola here. I just needed a name.
Starting with the code, I realized that I'm really handling just the one case, which is a Windows export straight into a directory. If it's going to be an addon, it might need to support a little bit more configurations than that. Handling a Windows ZIP export didn't need any changes as Godot just creates a temporary directory where it puts the game executable and PCK and then ZIPs that up, so the path
argument of _export_begin()
is just the path to it (e.g., /tmp/gamename/foo.exe
on Linux), even if the user wants to export the project as a ZIP archive. This turned out to be the case for Linux and Web exports as well, and I will just bravely assume that it's the case for every platform that doesn't need a special container for the project like an APK in the case of Android.
MacOS was a bit different. I am not very knowledgeable about the Mac platform, but sticking the copied files into the resulting APP folder didn't feel adequate, and I was afraid that I'd open a can of worms in terms of code signing, so I happily let Godot copy the files alongside the APP. A MacOS ZIP export wasn't going to be as easy as now; instead of a path in a temp directory, the path argument of _export_begin()
is the path to a ZIP. Thankfully, Godot has ZIPPacker for manipulating ZIP files, so I added a process in _export_end()
that appends the files that need to be copied to the resulting ZIP.
Considering that there's no real way to differentiate between a ZIP export of the whole project and a ZIP export of just the data, that'd mean that I'll run my file copying process in the case when the user is making a data export as a ZIP when the MacOS platform is selected. It's just something I'll have to live with for now, hoping that it won't cause too many problems until I have a solution for that.
At that point I realized that there's no way that I'll submit the addon for review before the evening, but that was alright. This was going to be my mark on the Godot addon ecosystem! And those aren't to be rushed.
I realized that I had totally forgotten about phones. Off in the TODO section they go. My workstation isn't set up for any form of mobile development. It's just my Java allergy. It's a real thing. Don't ask about it.
I still had to do the UI. Much less exploration there as I had already made forms in Godot before. If you weren't familiar, internally the Godot editor uses the Tree node for creating data tables. Don't let the name mislead you. Trees are great for tables. Not just in Godot, but in woodworking as well.
Making the UI, I realized that I need icons and a logo for the addon, and I'm no artist. My visual skills leave much to be desired. I'd be one of those clients from hell who'll tell an artist I commission, "I know what I want when I see it." I pinged my good friend Jānis Dimants, who then dedicated himself to my dream of open-source stardom by drawing me SVGs of a pencil, a trashcan, and a nice, abstract logo because both of us didn't want to do a logo with the internationally recognized logo for copying, also known as two slightly overlapping sheets of paper.
I finished the UI, gave it a couple of quick tests, typed up the documentation, made a screenshot, realized that the screenshot has a typo, remade the screenshot, created the repository, pushed it, and submitted it to the Asset Library queue.
If you, like me, hadn't made an addon up to this point but were curious about what the process was, it's quite simple—you sign up for the Asset Library by giving the Godot Foundation a user name and your email address. Don't wait for a confirmation email. It's not coming—you can use your account right away. Then in the top bar you click "Submit Asset." A huge scary form will open, but all the fields are well explained. If you host your addon on a popular code hosting platform like Github (just make sure to follow their guide on setting up the repository, which is mainly just using their provided .gitignore
and .gitattributes
files), you will have no issue filling it out. The most technical thing you need to know is the commit hash of the state where your addon is ready for release. And then you submit. And then you just wait.
In short, I was pretty bad at the waiting part. After I submitted, I didn't expect anything to be published that day, as I was well aware that Godot is in large part run by volunteers. The weekend passed by, Monday arrived, and I had forgotten that it's run by volunteers and was starting to get anxious even though I was sure that my addon was compliant and simple enough that it shouldn't have trouble passing moderation. I calmed down when I checked that the last updated dates on the recently updated plugins coincide with the date that I submitted the plugin, which meant that I probably had done everything correctly, I hadn't been forgotten, and I'm deserving of love. I just had to wait a little bit more. Actually, I only had to wait one more day, and the addon was up, viewable and installable from the Asset Library!
By that time I had already found a couple of bugs, which I have to fix at some point, but until then I'll go play some of that Fallout I've been meaning to play.
Finally ...
You got through this whole truckload of text? Damn! If you got this far and enjoyed it, send me a picture of a cute kitten on bsky or mastodon! Oh, and check out my addon Copy Files On Export, the thing this whole post is about, and let me know what you think! Docs and source code are in the Github repository.