Guides
Lifecycle hooks
Last updated August 24, 2026
A hook is not a new kind of manifest entry. It is an ordinary
[scripts] entry whose name is pre<something> or
post<something>: Ember looks it up by name, runs it exactly the way
embr run would, and skips it silently when it isn't
defined. Nothing needs enabling, and there is no fixed list of hookable
names.
A name only counts as a hook when something answers to the part after
pre/post — either a command event (install, add, publish) or
another entry in the same [scripts] table. So prelude is an ordinary
script, and so is a prebuild in a manifest with no build: it can never
fire on its own, and embr run prebuild is the only way it runs. Bare
embr run groups them the same way it classifies them, so
the listing tells you which of your entries are really hooks.
Scripts hook their own name#
Running build runs prebuild, then build, then postbuild:
[scripts]
prebuild = "rm -rf build"
build = "rojo build -o game.rbxl"
postbuild = "echo built"
That holds however the script was started — embr run build and the
embr build shortcut behave the same.
Commands hook their verb#
embr install, embr add and
embr publish run pre<verb> before their work and
post<verb> after it:
[scripts]
preinstall = "echo about to install"
postinstall = "embr build" # build once dependencies are in place
preadd = "echo about to add"
postadd = "echo added"
prepublish = "embr build" # runs before the archive is packed
postpublish = "echo shipped"
Where exactly they sit:
preinstallruns before anything is resolved or downloaded;postinstallruns after the install completes.preaddruns before the index prompt, so it can set up whatever resolving needs;postaddruns after ember.toml has been written.prepublishruns before the archive is packed, which is the point of it — that is where you build what ships.postpublishruns only after a successful publish.
prepublish runs on --dry-run too, since a dry run exists to show
the archive a real publish would produce.
Rules that apply to every hook#
- Hooks never nest.
prebuildis run as-is; Ember does not look forpreprebuild. A hook is free to callembr runwithout recursion. - A hook is announced with the same two-line banner every script gets — it is an ordinary script, and it is the one nobody typed.
- A failing hook stops the command. Ember exits with the hook's own exit
code, after an error naming it:
✗ Script 'preinstall' failed with exit code 3. Aposthook only runs if the work before it succeeded.
In a workspace#
Each manifest's hooks bracket its own portion, and run in its own
directory. embr install at a workspace root goes:
root preinstall
root install
member preinstall → member install → member postinstall (per member)
root postinstall
So a root postinstall sees a fully installed workspace, and a member's
own hooks see only that member. embr publish at the root has the same
shape.