A comparison of on_built_entity, on_robot_built_entity,
on_space_platform_built_entity, and script_raised_built
LuaBootstrap.raise_event. The interop contract between mods.This event fires immediately after a player character successfully places an entity. It will not fire if the placement is cancelled, if the entity ends up as a ghost (e.g. the player is in a vehicle and places to an out-of-range tile), or if placement is reverted by a script.
The event table contains: entity (the newly placed LuaEntity),
player_index, stack (the item used), and tags
(blueprint tags, if any).
script.on_event(defines.events.on_built_entity, function(event) local entity = event.entity local player = game.get_player(event.player_index) -- event.stack = the item that was consumed -- event.tags = blueprint tags (may be nil) log("Player " .. player.name .. " built " .. entity.name) end, { { filter = "name", name = "my-machine" } } -- optional filter )
on_built_entity fires for every entity the player
places, including trees, rocks from mining, and cliffs. Always pass an event filter
({ filter = "name", name = "…" }) to avoid performance penalties at scale.
Fires when a construction robot finishes placing an entity, typically from a blueprint ghost or when upgrading an existing entity. There is no human player involved at the moment of construction, though a player may have placed the ghost earlier.
The event table contains: entity, robot (the LuaEntity
for the robot itself), stack, and tags. Note the absence of
player_index — you need to trace the ghost's last user if you need attribution.
script.on_event(defines.events.on_robot_built_entity, function(event) local entity = event.entity local robot = event.robot -- No player_index! Robots work autonomously. -- robot.logistic_network gives you the network it belongs to. log("Robot built " .. entity.name .. " at {" .. entity.position.x .. "," .. entity.position.y .. "}") end)
event.tags and the prototype name carefully
if you need to distinguish a fresh placement from an upgrade.
Introduced in the Space Age DLC, this event fires when an entity is built on a space platform surface — whether by a player or by the platform's own construction system. It is surface-scoped and will never fire for entities on Nauvis or other planets.
The event table is similar to on_built_entity but includes a
space_platform field giving you direct access to the
LuaSpacePlatform object, which carries hub state, cargo inventory, and
travel progress.
script.on_event(defines.events.on_space_platform_built_entity, function(event) local entity = event.entity local platform = event.space_platform -- platform.name, platform.space_location, platform.hub_connector… log("Built on platform: " .. platform.name .. " currently at " .. platform.space_location.name) end)
if script.active_mods["space-age"] then … end to avoid
crashes on vanilla installs.
This is the mod interoperability event. When a mod creates an entity via the API
(e.g. surface.create_entity(…)), it optionally raises
script_raised_built so that other mods listening for build events can react
as if a normal build happened.
Crucially, Factorio itself does not raise this event — only scripts do.
If a mod silently calls create_entity without raising it, no other mod
will know the entity was placed. This is intentional: mods choose whether to participate
in the "someone built something" contract.
-- Inside your mod, after programmatically creating an entity: local new_entity = surface.create_entity({ name = "assembling-machine-2", position = { 10, 20 }, force = "player", }) if new_entity and new_entity.valid then script.raise_event(defines.events.script_raised_built, { entity = new_entity, }) end
-- In any mod that wants to react to script-built entities: script.on_event(defines.events.script_raised_built, function(event) -- event only contains: entity (and optionally stack/tags) -- No player_index, no robot — pure entity reference. handle_new_entity(event.entity) end)
script_raised_built. It's the polite way to announce your entity to the ecosystem.
| Property | on_built_entity | on_robot_built_entity | on_space_platform_built_entity | script_raised_built |
|---|---|---|---|---|
| Raised by | Engine (player action) | Engine (robot action) | Engine (space platform) | Another mod / script |
| player_index | ✔ always | ✘ absent | ~ optional | ✘ absent |
| robot field | ✘ | ✔ always | ✘ | ✘ |
| space_platform field | ✘ | ✘ | ✔ always | ✘ |
| stack field | ✔ always | ✔ always | ✔ always | ✘ absent |
| tags field | ~ from blueprint | ~ from blueprint | ~ from blueprint | ✘ absent |
| Requires Space Age DLC | ✘ | ✘ | ✔ yes | ✘ |
| Supports event filters | ✔ yes | ✔ yes | ✔ yes | ✔ yes |
| Guaranteed to fire | ✔ engine-guaranteed | ✔ engine-guaranteed | ✔ engine-guaranteed | ✘ only if mod raises it |
| Typical use case | Tagging player builds, achievements, sounds | Tracking automated construction, logistics | Space platform mechanics, DLC content | Cross-mod compatibility, API interop |
If you need to catch all build sources in one handler, subscribe to all four events and route to a shared function:
local function on_entity_built(event) local entity = event.entity if not (entity and entity.valid) then return end if entity.name ~= "my-machine" then return end -- Handle the entity regardless of who built it register_machine(entity) end local build_events = { defines.events.on_built_entity, defines.events.on_robot_built_entity, defines.events.script_raised_built, } -- Only add space platform event if DLC is present if script.active_mods["space-age"] then table.insert(build_events, defines.events.on_space_platform_built_entity) end script.on_event(build_events, on_entity_built, { { filter = "name", name = "my-machine" } } )
1. Ghosts are not entities. When a player places a ghost (blueprint cell not yet
built), on_built_entity fires with the ghost entity — its type is
"entity-ghost". Check entity.type ~= "entity-ghost" if you only care
about real builds.
2. script_raised_built is opt-in. Not every mod raises it. If you're writing a compatibility layer for popular mods (e.g. Space Exploration, Krastorio 2, Bob's mods), test whether they raise this event for their script-placed entities — many don't.
3. Event filter scope. Event filters for on_built_entity and friends
apply to the entity's prototype properties (name, type, flag, etc.). They cannot filter
by runtime state like position. Do position checks inside the handler.
4. Undo/revive interactions. When a player uses Ctrl+Z to undo a build,
on_player_mined_entity fires — not a "built" event in reverse. If you store state
on build, make sure you clean it up on mine/revive pairs as well.
script_raised_built after programmatic placements. This keeps downstream mods in sync
without requiring them to know about your internal API.