Skip to content

Permissions

Why a verb answers nil, "permission", and what to do about it.

There are three separate gates, and they answer different questions. Almost every permission problem is a matter of working out which one you hit.

GateQuestion
The world kindis this a social world or an experience?
The objectdoes this script own what it is acting on?
The playerdid they agree to this when they walked in?

1. The world kind

Two kinds of world, and scripts are trusted differently in each.

A social world is a place people visit. Anyone may bring in objects with scripts, so a script is trusted only with its own things. Anything that changes the world around it is reserved for the world owner's scripts.

An experience is a game somebody made. Entering it is already consent to being inside somebody's rules, so any script in it may act on the world.

VerbIn a social worldIn an experience
world:spawnworld owner's scripts onlyany script
world:play_soundworld owner's scripts onlyany script
world:set_weatherworld owner's scripts onlyany script
world.loading:set / clearworld owner's scripts onlyany script
world.loading:getany script — the front door is not a secretany script
world.terrain:*world owner's scripts onlyany script
world.blocks:set / fillneeds the block permissionneeds the block permission
http:sendneeds the net permissionneeds the net permission
world.store:set / updateworld owner's scripts onlyany script
world.store:getany script — reading is publicany script
players.store:*world owner's scripts only — including readingany script
show_cursorneverany script

If a verb works in your test world and refuses in somebody else's, this is almost always why. Nothing about the script changed; the world did.

show_cursor is the strictest of them: revealing a pointer freezes the avatar, so it only exists where entering was already a decision.

The one a worn item can never hold

http:send is the exception to everything on this page, and it is worth knowing why before you spend an afternoon on it.

Everywhere else, your own script on your own body is free: a worn attachment and a personal HUD run as you, so they act on you in any world, with no grant from anybody. That is what makes personal items work.

The network is not your body. It is the address of whoever is hosting the region you happen to be standing in — their machine, their bandwidth, their name on the request. So http asks about the region, always, and:

  • a personal HUD cannot call it, in any world, including your own;
  • a worn attachment cannot call it, even the one you built;
  • an object script can, if the world's owner granted net to whoever owns it — and a world owner's own scripts hold it without asking.

2. The object

A script acts freely on its own object — moving it, painting it, storing things in it.

Acting on another object through objects requires that you own that object too, or that you have been granted edit rights over it. Otherwise: nil, "permission".

Carrying is different: it needs an interaction

self:hold and world:spawn{ hold = } ask one more thing: that player must have interacted with this object.

What counts as an interaction:

  • they clicked it (on_touch)
  • they pressed a button on a HUD this object showed them

What does not count: standing near it, looking at it, being in the region.

luau
-- works: the click IS the interaction
self:on_touch(function(p)
    self:hold({ player = p.id })
end)

-- refused: nobody interacted with anything
players:on_enter(function(p)
    self:hold({ player = p.id })       -- nil, "permission"
end)

This is why a shop leaves a purchase on the ground unless the buyer pressed a button on the shop's own menu — and why handing something over from a menu works, because pressing that button was the interaction.

An object that world:spawn has just created has no history at all, which is exactly why handing a brand-new copy straight into a hand is allowed: nobody can have touched what did not exist a moment ago.


3. The player

Some things belong to the person, not to the world. They decide at the door, in the world's settings — what this world asks of its visitors — and they can refuse.

What it coversVerbs that need it
cameraset_camera, and an action's camera preset
bodyplayers:teleport
animationplayers:play_animation
holdself:hold, world:spawn{ hold = }
luau
local ok, why = players:teleport(p.id, spawn_point)
if not ok then
    print("could not respawn: " .. why)   -- "permission"
end

Check these. A world that assumes everyone granted everything is a world that silently breaks for the one visitor who did not — and the symptom is not a crash, it is a respawn that never happens.

set_camera is the one that refuses silently on the player's machine, because moving somebody's eye is theirs to allow. If your framing does not take hold on one particular person, this is why.


Latches are released for you

set_camera, set_weapon and show_cursor are latches: they stay until something takes them off.

If the script that set one dies — the item comes off, the object is deleted, the script is re-saved — the engine hands the player back their own camera, their own aim and their own pointer. Otherwise somebody would be stuck inside their own head with nothing left in the world able to let them out.

You still want the matching call on the way out (clear_weapon on on_drop, hide_cursor when the menu closes) — the release is a safety net, not the design.


Ownership of things a script makes

An object created by world:spawn belongs to the owner of the script that made it, not to whoever triggered it. A copy made from a prototype carries its scripts across with the builder as their creator.

See also

Hungrit scripting documentation.