Scripts, hooks and runtimes
A build action is one file in one folder. Its name decides when it runs, its extension decides whether Python or Node.js runs it, and anything else in the folder is yours to use as helpers.
Where the scripts go
<repository root>/ .dsl/ build_actions/ pre_build.py <- runs before the build post_build.js <- runs after the build on_end.py <- runs last, every time lib/ <- anything else is yours: helpers, data, package.json
The folder is always .dsl/build_actions/ at the repository root, even when your project lives in a subfolder. Scripts are read from the commit being built: changing a script changes what the next build of the new commit does, and rebuilding an old commit runs that commit's scripts.
How scripts are named
- A script's file name is the hook name plus
.py,.jsor.mjs, for examplepre_build.pyorpost_build.mjs. - One script per hook. Having both
pre_build.pyandpre_build.jsfails the build. - Any other file in the folder is ignored, so keep helper modules there and import them. Python scripts can import from their own folder, and Node scripts can
require("./lib/..."). - If a file name is close to a hook name but misspelled, such as
prebuild.py, the build log warns you.
The hooks
| Script name | When it runs | Runs in |
|---|---|---|
pre_checkout | Before your code is checked out | A folder holding only .dsl/build_actions/ |
post_checkout | Right after checkout | Repository root |
pre_build | After the engine is ready, before the build | Project folder |
post_build | After the build, before signing and packaging | Project folder |
pre_sign | Before code signing (only when signing is on) | Project folder |
post_sign | After code signing (only when signing is on) | Project folder |
pre_archive | Before the build output is zipped and uploaded | Project folder |
post_archive | After the zip is uploaded | Project folder |
on_end | Always last, whether the build succeeded or failed | Project folder (or the repository root) |
- A hook runs only if the build gets that far. If
pre_buildfails, the build stops and the later hooks do not run, excepton_end, which always runs. - Files written in
post_buildare shipped. For Godot, Unity and Unreal, anything apost_buildscript writes intoDSL_OUTPUT_DIRis signed (if signing is on), zipped and uploaded with the rest of the build. For CMake, MSBuild, SCons and Xcode, where your artifact settings pick files from the repository by pattern,DSL_OUTPUT_DIRis not set: write the file into the repository where your artifact patterns match, and it ships. on_enddoes not run when a build is canceled or interrupted by maintenance.
Languages and versions
| Language | Extension | Version |
|---|---|---|
| Python | .py | 3.14 |
| JavaScript (Node.js) | .js, .mjs | Node 24 LTS |
The same versions run on every build machine: Windows, macOS and Linux. Write scripts that work on all three, or branch on DSL_HOST_OS.
There is no shell. To run a command, call it from your script: subprocess.run([...]) in Python, child_process.execFileSync(...) in Node. The runtime's own tools come first on PATH while your script runs, so python, pip, node and npm are the versions above.
Third-party packages
Nothing beyond the standard library is preinstalled. Install what you need from the script itself:
import subprocess, sys, os subprocess.run([sys.executable, "-m", "pip", "install", "--target", "_deps", "requests"], check=True) sys.path.insert(0, "_deps")
The Node example runs npm ci in the script's own folder, so commit a package.json and package-lock.json in .dsl/build_actions/ alongside it.
What a script can reach
Scripts run on the build machine with the same access as your engine's own build scripts, such as Unity editor scripts or an Unreal build graph. They are part of your build, so review them like any other code. Every build's record lists which scripts ran and their hashes, so you can tell exactly what produced a build.
Next, What your script is told lists the variables each hook receives.
Something out of date or unclear? Email support@devstreamlabs.com.