Talking between scripts#
Three ways for scripts to reach each other, and when each is right.
| Way | Reaches | Good for |
|---|---|---|
events:emit / listen | your own scripts | a switch and its door |
events:send / open | anyone who opted in | your HUD and somebody else's shop |
objects | one object you can address | painting every lamp |
Your own build talking to itself#
The sender:
--!strictlocal self = require("self")local events = require("events") self:on_touch(function(player) events:emit("alarm", { by = player.id })end)Every listener:
--!strictlocal self = require("self")local events = require("events") events:listen("alarm", function(data) self:set({ color = "#e35d6a", emissive = 3 }) self:after(5, function() self:set({ color = "#3a3a3a", emissive = 0 }) end)end)One button, twenty lamps, no list of ids anywhere. Adding a lamp is copying the listener onto it.
emit is private to you. Another creator's scripts never hear it, even if they guess the channel name.
Two scripts on the same object#
They do not share variables, but they hear the same events:
-- main.luauevents:emit("state", { open = true }) -- lock.luauevents:listen("state", function(data) -- ...end)emit reaches every script of the same owner including the one that sent it, so a script can listen to its own channel. That is occasionally what you want and occasionally a loop. See below.
Crossing owners#
Your personal HUD is yours; the shop is somebody else's. emit cannot reach across, and that is deliberate.
The receiver opens a door:
events:open("acme.shop", function(data, from) if from.kind ~= "hud" then return end serve(from.owner, data)end)The sender knocks:
events:send("acme.shop", { want = "rope" })open is the receiver's consent. Nothing can push a message into a script that never asked for one.
The from table is stamped by the region and is the only part you can trust:
| Field | |
|---|---|
kind | "object", "hud" or "attachment" |
owner | the account that owns the sending script |
object | the sending object's id, or nil from a HUD |
Careful
Channel names are global to the region. Two creators who both pick "shop" are on the same channel. Prefix yours: "acme.shop", "mira.lift".
Reaching a specific object#
When you know which object, objects is more direct than a broadcast:
local objects = require("objects") local lamps = objects:tagged("streetlight")if lamps then for _, lamp in ipairs(lamps) do lamp:set({ light = true }) endendTags beat names: a name is what a person reads and may rename; a tag is what a script means.
Tip
You cannot read another object's state, only act on it. Tags are the way round it: an object's own script controls its tags, so a tag like "door.open" is the public half of an object that anyone may check with has_tag.
Choosing: event or tag?#
| Event | something happened. A moment |
| Tag | something is true. A state |
A door that just opened emits; a door that is open carries a tag. Somebody arriving later missed the event but can still read the tag.
What can travel#
Nothing, a boolean, a number, a string, or or a table with string keys, 3 levels deep, 32 entries, 512-byte strings. Anything else is refused with a reason rather than trimmed.
Values arrive as what they were; unlike self.store, you do not convert.
Always check the shape you were given:
events:listen("alarm", function(data) if type(data) ~= "table" then return end local who = tostring(data.by or "") -- ...end)Loops#
If A emits to B, B emits to C, and C emits back to A, you have built a loop. The region cuts it at 64 deliveries rather than letting it run.
If a message mysteriously stops arriving, a loop is the first thing to check. The usual fix is a guard:
local settling = false events:listen("state", function(data) if settling then return end settling = true -- ... change things, which may emit ... settling = falseend)Ceilings#
Sends per event (emit + send) | 16 |
| Cascade cut at | 64 deliveries |
| Channel name | 1 to 64 characters |
Registering the same channel twice replaces the handler. A script has one handler per channel, not a list.
Built-in channels#
The world reports on its own channels; listen with events:listen:
| Channel | |
|---|---|
world.destroyed | something with health was destroyed |
world.collapse | a building piece lost its support and came down |
world.block | a block was placed or broken |
world.build | a building piece was raised |
See also#
Updated 04 September 2026