Build events guide

A comparison of on_built_entity, on_robot_built_entity, on_space_platform_built_entity, and script_raised_built

Player action
on_built_entity
Fires when a human player places an entity by hand from their toolbar or quickbar. The canonical "player built this" event.
Automation action
on_robot_built_entity
Fires when a construction robot fulfils a ghost or deconstruction blueprint. No human required at build time.
Space age DLC
on_space_platform_built_entity
Fires when an entity is assembled on a space platform. Distinct surface type with its own quirks and limitations.
Scripted action
script_raised_built
Explicitly raised by another mod or script via LuaBootstrap.raise_event. The interop contract between mods.

Event-by-Event Breakdown

on_built_entity

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).

Lua
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
)
Filter early. 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.

on_robot_built_entity

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.

Lua
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)
Upgrade robots (upgrading a machine to a higher tier) also raise this event — the old entity is removed and a new one is placed. Check event.tags and the prototype name carefully if you need to distinguish a fresh placement from an upgrade.

on_space_platform_built_entity

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.

Lua
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)
DLC check required. If your mod is not marked as requiring Space Age, you must guard this event with if script.active_mods["space-age"] then … end to avoid crashes on vanilla installs.

script_raised_built

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.

Lua — Raising
-- 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
Lua — Listening
-- 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)
Best practice for mod authors: If your mod places entities that other mods might care about (e.g. auto-placing machines during research), always raise script_raised_built. It's the polite way to announce your entity to the ecosystem.

Side-by-Side Comparison

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

Choosing the Right Event

// Decision guide
? Was the entity placed by another mod programmatically?
YES → script_raised_built
NO ↓
? Is the entity on a space platform surface?
YES → on_space_platform_built_entity
NO ↓
? Did a construction robot place it (no human at build time)?
YES → on_robot_built_entity
NO → on_built_entity

If you need to catch all build sources in one handler, subscribe to all four events and route to a shared function:

Lua — Catch-all pattern
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" } }
)

Common Pitfalls

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.

Remote interfaces and events. If you're building a library mod intended to be called by other mods, expose a remote interface that wraps your logic and raise script_raised_built after programmatic placements. This keeps downstream mods in sync without requiring them to know about your internal API.