In short
Build the door as a Part or a Model, tick Anchored, move its pivot to the hinge edge (Model tab, Pivot), and add the tag Door in the Properties window. One Script in ServerScriptService finds every tagged door with CollectionService, adds a ProximityPrompt ("Open", hold E), and on Triggered tweens a NumberValue from 0 to 1 while moving the door with door:PivotTo(closedPivot:Lerp(openPivot, alpha)). Attributes on each door choose a swing or a slide, the angle, the time, auto close and a key.
- You cannot tween a Model's position directly: a Model has no CFrame or Position property, and its
WorldPivotonly sets where its pivot is. Tween a NumberValue and move the door withPivotTo, which Roblox calls the primary way to move models from a script. - Open doors on the server. A door opened by a LocalScript moves on that player's screen only.
- Check the key and the distance on the server. Roblox's security guide says ProximityPrompts can be abused just like RemoteEvents, and its own example of a check the server must make is "Do they have a key required to open a door?"
On this page
How a scripted door works
A door that works in a live game has to be moved by the server. If a LocalScript moves it, the door opens on that one player's screen and stays shut for everyone else, because a change a client makes stays on that device (see what replicates and what does not). So in this guide the player's device only shows the prompt and reports the key press, and one server Script does everything else. One use of a door goes like this:
- The player walks within 8 studs of the door and a prompt appears with the action Open and the object name Door, and E on a keyboard. Roblox shows the matching button on a gamepad or a touch screen.
- The player holds E for 0.2 seconds. The prompt's
Triggeredevent fires on the server with that player. - DoorService checks the player: alive, close enough, not spamming the prompt, and carrying the key if the door needs one.
- It tweens a number from 0 (closed) to 1 (open). Each time the number changes, it moves the door that fraction of the way from its closed position to its open one with
PivotTo. - Four seconds after the door is fully open, it closes the same way. A press while the door is moving turns it back from wherever it is.
| Piece | Where it lives | What it does |
|---|---|---|
DoorService Script | ServerScriptService | Finds every door, adds its prompt, checks each use and moves the door. The only script in this guide. |
The Door tag | On each door Part or Model | Marks what is a door. You add tags in the Properties window, and they are saved with the place. |
Attributes such as Style and KeyName | On each door (optional) | Settings for one door: swing or slide, the angle, the speed, auto close and the key. |
DoorPrompt ProximityPrompt | Inside the door (DoorService adds it) | Shows "Open" or "Close" and fires Triggered when a player holds E. |
DoorProgress NumberValue | Inside the door (DoorService adds it) | The number the tween moves between 0 and 1. Every change moves the door. |
RedKey Tool (optional) | StarterPack, for testing | The key for a locked door. Any Tool works if its name matches the door's KeyName. |
Workspace
FrontDoor Part new
VaultDoor Model new
Panel Part new
Knob Part new
ServerScriptService
DoorService Script new
StarterPack
RedKey Tool new
Handle Part new
Step 1: Build the door and set its hinge
- In the Explorer, hover over Workspace, click the + button and insert a Part. Name it
FrontDoor, set itsSizeto about4, 7, 0.5(4 studs wide, 7 tall, half a stud thick) and move it into your doorway. A door made of several parts works too: select them and press Ctrl+G to group them into a Model. - Tick Anchored on every part of the door and leave CanCollide on. Anchored parts never move from gravity or collisions, but a script can still move them, which is exactly what DoorService does.
- Set the hinge. Select the door, open the Model tab and turn on Pivot. Click and hold the small arrow in the corner of the Pivot button and tick Snap, so the pivot snaps to hotspots on corners, edges and centres. Drag the pivot to the edge of the door where the hinges would be. A sliding door can keep its pivot in the centre.
- Add the tag. With the door selected, scroll the Properties window to Tags, click + and type
Door, with a capital D. For a Model door, tag the Model, not the parts inside it. - Build a second door, for the sliding and key sections later. Insert two more Parts in Workspace:
Panel, sized about4, 7, 0.5, andKnob, about0.5, 0.5, 0.5, set against one face of Panel. Select both, press Ctrl+G and rename the new ModelVaultDoor. Anchor both parts, move the model into a second doorway and add theDoortag to the Model. Its pivot can stay in the centre, because this door will slide.
The pivot matters because a swinging door turns around it. Left in the centre, the door spins on the spot like a revolving door. For a part, the pivot you set is saved as its PivotOffset, and GetPivot includes it, so the script turns the door around exactly the point you chose. If a Model has a PrimaryPart, the model's pivot is that part's pivot, and DoorService also puts the prompt on that part. Without a PrimaryPart, the prompt goes on the first part it finds inside the model. If you set a PrimaryPart, set it before you move the pivot: assigning one moves the model's pivot to that part's pivot, as Roblox's Pivot tools page notes.
Door settings (attributes)
Every door works with the defaults below. To change one door, select it, scroll the Properties window to Attributes, click +, enter the name, choose the type and click Save, then set the value. DoorService reads the attributes once, when it sets the door up, so change them while the game is stopped.
| Attribute | Type | Default | What it does |
|---|---|---|---|
Style | string | Swing | Swing turns the door around its pivot. Slide moves it sideways along its width. |
OpenAngle | number | 90 | Degrees a swinging door turns. A negative number swings it the other way. Keep it between -179 and 179: the door always takes the shorter way round, so DoorService caps it there. |
SlideDistance | number | its width | Studs a sliding door moves along its width. A negative number slides it the other way. |
OpenSeconds | number | 0.6 | How long a full open or close takes, kept between 0.05 and 10. |
AutoCloseSeconds | number | 4 | How long the door stays fully open before it closes on its own. 0 keeps it open until someone closes it. |
KeyName | string | none | The name of a Tool the player must have to open the door, such as RedKey. Leave it out for a door anyone can open. |
Step 2: Add the door script
Hover over ServerScriptService, insert a Script named DoorService and paste the code below. It runs every door in the place, however many you tag. The three settings near the top apply to all doors; the attributes from Step 1 change one door.
-- Script in ServerScriptService named DoorService.
-- Opens and closes every Part or Model tagged "Door" with a hold-E prompt.
-- The server moves the door, so every player sees the same door, and the server
-- checks the distance and the key, so nobody can open a locked door from afar.
--
-- Optional attributes on each door (set them in the Properties window):
-- Style string "Swing" (default) or "Slide"
-- OpenAngle number degrees a swinging door turns around its pivot (default 90, -179 to 179)
-- SlideDistance number studs a sliding door moves along its width (default: its width)
-- OpenSeconds number how long opening takes (default 0.6)
-- AutoCloseSeconds number close again after this long; 0 keeps it open (default 4)
-- KeyName string the name of a Tool the player must have, such as "RedKey"
local CollectionService = game:GetService("CollectionService")
local TweenService = game:GetService("TweenService")
local DOOR_TAG = "Door"
local PROMPT_DISTANCE = 8 -- studs
local MIN_SECONDS_BETWEEN_USES = 0.3 -- per door, against prompt spam
local doorsSetUp: { [Instance]: boolean } = {}
local function attribute(door: Instance, name: string, default: any): any
local value = door:GetAttribute(name)
if value == nil or typeof(value) ~= typeof(default) then
return default
end
if typeof(value) == "number" and (value ~= value or math.abs(value) == math.huge) then
return default -- NaN or infinity typed into the Properties window
end
return value
end
local function hasKey(player: Player, keyName: string): boolean
local backpack = player:FindFirstChildOfClass("Backpack")
local character = player.Character
local inBackpack = backpack and backpack:FindFirstChild(keyName)
local held = character and character:FindFirstChild(keyName)
return (inBackpack ~= nil and inBackpack:IsA("Tool")) or (held ~= nil and held:IsA("Tool"))
end
local function setUpDoor(door: Instance)
if doorsSetUp[door] or not (door:IsA("BasePart") or door:IsA("Model")) then
return -- already a working door, or not a part or a model
end
local promptPart = if door:IsA("Model") then (door.PrimaryPart or door:FindFirstChildWhichIsA("BasePart", true)) else door
if not (promptPart and promptPart:IsA("BasePart")) then
warn(`DoorService: {door:GetFullName()} has no part to put the prompt on`)
return
end
doorsSetUp[door] = true
door.Destroying:Connect(function()
doorsSetUp[door] = nil
end)
-- A copy of a working door brings the old prompt and value with it: remove them
for _, leftover in door:GetDescendants() do
if leftover.Name == "DoorPrompt" or leftover.Name == "DoorProgress" then
leftover:Destroy()
end
end
-- Work out the closed and open positions once, from where the door is placed now
local closedPivot = door:GetPivot()
local openPivot: CFrame
if attribute(door, "Style", "Swing") == "Slide" then
-- Slide along the door's longer flat side, its width. A Model is measured along its
-- pivot's sides, a Part by its own Size, so keep a sliding Part's pivot unrotated
local size = if door:IsA("Model") then door:GetExtentsSize() else (door :: BasePart).Size
local alongX = size.X >= size.Z
local width = if alongX then size.X else size.Z
local direction = if alongX then Vector3.xAxis else Vector3.zAxis
openPivot = closedPivot * CFrame.new(direction * attribute(door, "SlideDistance", width))
else
-- Lerp always turns the shorter way round, so a bigger angle would swing the wrong way
local angle = math.clamp(attribute(door, "OpenAngle", 90), -179, 179)
openPivot = closedPivot * CFrame.Angles(0, math.rad(angle), 0)
end
local openSeconds = math.clamp(attribute(door, "OpenSeconds", 0.6), 0.05, 10)
local autoClose = math.max(attribute(door, "AutoCloseSeconds", 4), 0)
local keyName = attribute(door, "KeyName", "")
-- Tween a number from 0 (closed) to 1 (open) and move the door to match it.
-- PivotTo moves a single part or a whole model around its pivot.
local progress = Instance.new("NumberValue")
progress.Name = "DoorProgress"
progress.Parent = door
progress.Changed:Connect(function(alpha: number)
door:PivotTo(closedPivot:Lerp(openPivot, alpha))
end)
local prompt = Instance.new("ProximityPrompt")
prompt.Name = "DoorPrompt"
prompt.ActionText = "Open"
prompt.ObjectText = if keyName ~= "" then `Needs {keyName}` else "Door"
prompt.HoldDuration = 0.2
prompt.MaxActivationDistance = PROMPT_DISTANCE
prompt.RequiresLineOfSight = false
prompt.Parent = promptPart
local isOpen = false
local lastUse = 0
local moveCount = 0
local function move(open: boolean)
isOpen = open
moveCount += 1
door:SetAttribute("Open", open) -- other scripts can read this
prompt.ActionText = if open then "Close" else "Open"
-- A new tween on the same value cancels the old one, so a door can turn back halfway.
-- Scale the time by how far it still has to go.
local target = if open then 1 else 0
local seconds = openSeconds * math.abs(target - progress.Value)
TweenService:Create(progress, TweenInfo.new(seconds, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { Value = target }):Play()
if open and autoClose > 0 then
local thisMove = moveCount
task.delay(openSeconds + autoClose, function()
if moveCount == thisMove and isOpen then
move(false)
end
end)
end
end
prompt.Triggered:Connect(function(player: Player)
local now = os.clock()
if now - lastUse < MIN_SECONDS_BETWEEN_USES then
return
end
-- Roblox's security guide says prompts can be abused like remotes, so check again here
local character = player.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
local rootPart = humanoid and humanoid.RootPart
if not (humanoid and rootPart) or humanoid.Health <= 0 then
return
end
if (rootPart.Position - promptPart.Position).Magnitude > PROMPT_DISTANCE + 6 then
return -- too far away to have used the prompt honestly
end
if keyName ~= "" and not isOpen and not hasKey(player, keyName) then
return -- locked, and this player has no key
end
lastUse = now
move(not isOpen)
end)
end
-- Every door tagged now, and any tagged later (including doors cloned in by scripts)
for _, door in CollectionService:GetTagged(DOOR_TAG) do
setUpDoor(door)
end
CollectionService:GetInstanceAddedSignal(DOOR_TAG):Connect(setUpDoor)
DoorService adds DoorPrompt and DoorProgress to each door itself when the game starts, so there is nothing to insert by hand. Keep one copy of the script: it already handles every tagged door.
| Part of the script | What it does | Why |
|---|---|---|
attribute() | Reads one attribute and falls back to the default when it is missing, the wrong type, or not a real number (NaN or infinity). | A typo in the Properties window gives a door with default settings instead of a broken one. |
doorsSetUp and the leftover loop | Sets each door up once, and first removes any DoorPrompt or DoorProgress copied from another door. | A copy of a working door would otherwise have two prompts, one of them dead. |
closedPivot and openPivot | Worked out once, from where the door stands when it is set up. A swing turns the pivot around its own up axis; a slide moves it along the door's longer side. | Lerping from the closed pivot to the open one keeps the pivot in place while the door turns, so the hinge edge stays put through the whole swing. |
DoorProgress and PivotTo | The tween changes a number; each change moves the door to closedPivot:Lerp(openPivot, alpha). | TweenService can tween a NumberValue, and PivotTo moves a part, or a model with all its parts, so its pivot lands on that CFrame. |
| The prompt | HoldDuration 0.2, MaxActivationDistance 8, RequiresLineOfSight off. A locked door shows "Needs RedKey". | With line of sight on, the prompt hides when something blocks the view of its part; turning it off keeps the frame or the rest of a model door from hiding it. |
move() | Starts a tween toward 1 or 0, with the time scaled by how far the door still has to go. | A new tween on the same value cancels the old one, so a door turned back halfway reverses smoothly and takes half the time. |
| Auto close | Waits for the opening time plus AutoCloseSeconds, then closes the door only if moveCount has not changed. | An old timer never shuts a door that someone closed and opened again in the meantime. |
The Triggered checks | One use per door every 0.3 seconds, a living character, within 14 studs of the prompt's part, and the key, but only to open. | Anything a client can trigger can be abused, so the server repeats the checks. The 14 studs are the prompt's 8 plus 6 of allowance for a player who moved while holding E. |
The Open attribute | Set on the door at every move. | Attributes set on the server replicate to every player, so other scripts, LocalScripts included, can listen with GetAttributeChangedSignal("Open"). |
Tagging every door and running them from one script is the pattern Roblox recommends: its CollectionService reference says that if you find yourself adding the same script to many objects, a script that uses CollectionService may be better. GetTagged returns the doors that exist when the script starts, and GetInstanceAddedSignal catches doors tagged or added later. The client and server scripts guide covers this way of organising a place.
Make a sliding door
Select VaultDoor, add a string attribute named Style and set it to Slide. The door then moves sideways instead of turning: by its own width, or by SlideDistance studs if you set that. To slide it the other way, use a negative distance, such as -4 for a 4-stud door. Any other door, FrontDoor included, slides the same way once it has this attribute.
DoorService measures the door's two sideways sizes, its width and its thickness, and slides along the longer one. For a Model it measures with GetExtentsSize, whose box follows the orientation of the model's pivot, so a door turned 90 degrees after you group it still slides along its width rather than its thickness. For a Part it measures the part's own Size, so leave a sliding part's pivot unrotated. Moving that pivot is fine.
- Double doors: two tagged halves side by side, both with
StyleSlide, and one with a negativeSlideDistanceso they part in the middle. For swinging double doors, put each half's pivot on its outer edge and give one half a negativeOpenAngle. Each half has its own prompt and opens on its own. If the halves move into each other, move the minus sign to the other half. - A gate that slides up: this script slides along the door's width only. To lift doors instead, set
directionin the script toVector3.yAxis(every sliding door then moves up) and give each one aSlideDistanceequal to its height. - A physics door: Roblox's moving objects tutorial moves parts along one axis with a PrismaticConstraint. Like the hinged door, that is physics, with no prompt or lock unless you script one.
Lock a door with a key
A locked door opens only for a player who has a Tool with the right name. Make the key, then tell the door its name:
- Hover over StarterPack, click + and insert a Tool named
RedKey. Insert one Part into it namedHandle, the part the character holds, and make sure that part is not anchored: an anchored part in a tool pins the character in place. Untick the Tool'sCanBeDroppedtoo if players should not be able to drop the key for someone else: while it is ticked, Backspace drops an equipped tool into Workspace. - Select VaultDoor, add a string attribute named
KeyNameand set it toRedKey. The prompt's object name now reads Needs RedKey.
DoorService looks for the key in two places. A Tool sits in the player's Backpack until they equip it, and equipping moves it into their character, so the door opens whether the key is in the hotbar or in their hand. The check runs on the server because Roblox's security guide names this exact case, "Do they have a key required to open a door?", as a permission the server must verify. Only opening needs the key: anyone can close an open door, and it closes on its own after AutoCloseSeconds.
StarterPack gives the key to everyone on every spawn, which is right for testing. In a real game, hand it out instead:
- From a shop: the shop GUI guide gives Tools from ServerStorage to the buyer's Backpack and StarterGear.
- From a character: the NPC dialogue guide gives a Tool from
ServerStorage.DialogueItemswhen the player picks a choice with theGiveItemaction. Put a Tool named RedKey in that folder and give a choice in DialogueDataaction = "GiveItem"anditem = "RedKey", and the key comes from a conversation. - As an inventory item: with the inventory system, a key item works once the player equips it, because equipping puts a copy of its Tool in their Backpack and hand.
A key in the Backpack is lost when the character dies, because Roblox refills the Backpack from StarterPack and StarterGear on every spawn. To keep it, also put a copy in the player's StarterGear, as the shop guide does. To use a key up instead, find the key Tool in the player's Backpack or character and destroy it in the Triggered handler, right after the key check passes.
What older door tutorials get wrong
If you are comparing this guide with a video or a forum post, these are the patterns that cause most "my door does not work" threads, and what this guide does instead.
| The tutorial | The problem | This guide |
|---|---|---|
Tweens the CFrame of a door Model | The Model reference lists no CFrame or Position property. Its WorldPivot only sets where the model's pivot is, so tweening it moves the pivot point and leaves the parts where they are. | Tweens a NumberValue and moves the whole model with PivotTo. |
Moves a model with SetPrimaryPartCFrame | Superseded: Roblox says to use PivotTo for new work. | Uses PivotTo, which Roblox calls "the primary function that should be used to move Models via scripting". |
| Welds every part to one hinge part and tweens only the hinge | Works when every weld is right, but any part left unwelded stays behind when the hinge moves. | Keeps every part anchored with no welds. PivotTo moves the door and everything inside it. |
| Copies the same script into every door | Each fix has to be repeated in every copy. Roblox suggests tagging the objects and handling them from one script with CollectionService. | One DoorService and a Door tag. |
| Opens the door in a LocalScript | The door opens on that player's screen only, and everyone else still sees it shut. | The server moves the door, so every player sees the same door. |
Opens the door from Touched | Touched fires several times in quick succession for one contact (Roblox's debounce page), and a player's device can control when Touched fires for its own character (Roblox's security docs). | A prompt that the server checks. |
Moves the door step by step in a loop with wait() | Roblox marks wait() superseded by task.wait(), and a loop cannot turn back halfway when someone presses again. | One tween per move. A new tween on the same value cancels the old one, so the door turns back from where it is. |
| Checks the key in a LocalScript, or not at all | A cheater controls everything on their own device, so a client-side check stops nobody. | The server looks for the Tool in the player's Backpack and character. |
| No cooldown on the prompt | Roblox's security guide says prompts are open to abuse just like remotes, so nothing stops one from being triggered over and over. | One use per door every 0.3 seconds. |
What a cheater can and cannot do
Assume a cheater controls everything on their own device, including their character. Here is what that means for these doors.
| A cheater tries to | Result | Why |
|---|---|---|
| Trigger the prompt from across the map | Ignored unless their character is at the door | DoorService measures from their character's root part to the prompt's part and ignores anything beyond 14 studs. They can teleport their character next to the door first (see the last row), so the distance check stops use from afar, not teleporting. |
| Trigger it many times a second | At most one use per door every 0.3 seconds | The server keeps the time of the last use for each door. |
| Open a key door without the key | Refused | The server looks for the Tool in their Backpack and character, where it sees them, not on their screen. |
| Use the door while their character is dead | Ignored | DoorService checks for a Humanoid with more than 0 health and a root part. |
| Walk through a closed door anyway | Possible | Roblox's security docs list bypassing physical obstacles and changing their own collision among the things a client can do with its own character. |
| Teleport past the door | Possible | The same docs list teleporting their own character to any position. |
Step 3: Test it
- Press Play (F5). Walk up to FrontDoor: within 8 studs, a prompt appears with the action Open and the object name Door. Hold E. After 0.2 seconds the door swings 90 degrees around its hinge edge in 0.6 seconds, and the prompt now says Close.
- Walk through and wait. Four seconds after it finished opening, the door closes on its own. Hold E while it is closing: it turns back and opens from where it was.
- Test the lock. If RedKey is in StarterPack, move it to ServerStorage for now, press Play and hold E at VaultDoor: the prompt says Needs RedKey and nothing moves. Stop, put RedKey back in StarterPack and play again: VaultDoor opens, with the key in your hotbar or in your hand.
- Check the slide. VaultDoor, a Model with
StyleSlide, moves sideways by its own width when it opens. Stop, select VaultDoor, rotate it 90 degrees with the Rotate tool and play again: it should still slide along its width, not its thickness. Rotate it back when you are done. - Choose Server & Clients in the test mode dropdown, set 2 clients and press Play. Open a door in one client window: it is open in the other window too, and it closes in both.
- Stop the game, select a door and press Ctrl+D to duplicate it, then move the copy to another doorway. Check that the copy still lists Door under Tags, then press Play: the copy works with no extra script.
Keep the Output window open (Window menu, then Output). DoorService prints nothing when it works. A warning that a door "has no part to put the prompt on" means a tagged Model has no parts inside it. See Common errors for the rest.
Extending it
Automatic doors
For a door that opens when someone walks up, call move(true) from inside setUpDoor instead of waiting for the prompt. At the end of setUpDoor, start a loop with task.spawn, because a plain loop there never finishes and DoorService would never get past the first door. Keep it running only while the door is in the game: task.spawn(function() while door.Parent do ... task.wait(0.5) end end). Inside the loop, check with workspace:GetPartBoundsInBox whether a character is inside a box around the doorway, and call move(true) when one is and the door is shut. Auto close then shuts it as before. Set the prompt's Enabled to false if the door should only open by itself.
VIP doors
The game pass guide already builds a VIP door that pass owners walk straight through, using collision groups. For a VIP door that opens with E instead, add one more check in the Triggered handler, next to the key check: return unless player:GetAttribute("OwnsPass_VIP") == true, the attribute the game pass guide's server script sets. Read it on the server only.
A code keypad
For a door that opens with a typed code, keep the code in a server Script or in ServerStorage, which players never receive, and never in a LocalScript, where a cheater can read it. The keypad's LocalScript sends only the typed digits through a RemoteEvent. On the server, check that the player is near the door, limit how often they can guess, compare the digits, and only then call move(true). The RemoteEvents guide shows how to check what a remote receives.
Sounds
Put a Sound inside the door's part, so it plays from that part's position, and call its Play method at the start of move. Or play it from a LocalScript that listens to the door's Open attribute with GetAttributeChangedSignal("Open"), since the server sets that attribute on every move.
Doors added while the game runs
Clone new doors from a closed template in ServerStorage. DoorService reads a door's closed position when the door appears, so a copy of a door that was open at the time would treat its open position as closed. Move the copy into its doorway with PivotTo before you set its Parent to Workspace, so DoorService records that spot as closed. Old prompts and values that come with a copy are removed automatically. For a door that takes players to another area or another place, see the teleporter guide.
Common errors and fixes
Open the Output window (Window menu, then Output) before you press Play. Most door problems come from a tag, a pivot or an attribute that is not quite what the script expects.
No prompt appears at the door
Check these in order: DoorService is a Script, not a LocalScript, directly in ServerScriptService; the door has the tag Door with a capital D, on the Model itself for a Model door; the door is a part (a Part, MeshPart or other BasePart) or a Model; and you are within 8 studs of the part the prompt sits on. A warning in Output that the door "has no part to put the prompt on" means the tagged Model has no parts inside it.
The door falls, drifts or gets pushed around when the game starts
A part of it is not anchored. Select every part of the door and tick Anchored. Anchored parts never move from physics, but DoorService can still move them.
I changed an attribute but the door ignores it
Check three things. The type: OpenAngle, SlideDistance, OpenSeconds and AutoCloseSeconds must be number attributes, and Style and KeyName must be string attributes; DoorService uses the default when the type is wrong. The spelling: names are matched exactly, so openAngle is a different attribute. Values are matched exactly too: Style must be Slide with a capital S and no spaces. The timing: DoorService reads attributes once, when it sets the door up, so a change made during a playtest does nothing until you play again.
The door swings the wrong way, or tips over sideways
To swing it the other way, make OpenAngle negative, for example -90. If it turns around a sideways axis instead of an upright hinge, the pivot itself has been rotated: turn on Pivot on the Model tab and rotate the pivot back so it stands upright.
A sliding door slides into the wall or only moves a little
Give it a negative SlideDistance to slide the other way. If it moves too little or too far, set SlideDistance to the number of studs you want instead of relying on the measured width.
I have the key but the door stays shut
The key must be a Tool, not a Part or a Model, and its name must match KeyName exactly, capitals included. It has to be in your Backpack or in your hand. Also check that KeyName on the door is a string attribute.
Build it with RoCode
If you would rather not set this up by hand, RoCode can build it in the place you have open. RoCode is an independent AI agent for Roblox Studio, not made by Roblox: you describe the change in a web chat, and its Studio plugin creates the scripts and instances in your Explorer, so there is no code to paste.
Make the doors tagged Door open with a hold-E prompt: swing 90 degrees around the pivot in 0.6 seconds, close after 4 seconds, slide instead when the Style attribute is Slide, and stay shut unless the player has the Tool named in KeyName. All on the server, with a distance check.
- Can look through your place when a step needs it, with Search Game and Search Scripts, so it can build on items, remotes or save code you already have instead of adding a second system
- Writes the Script in ServerScriptService with Create Script, and can create a door part with Create Instance and set attributes such as Style and KeyName on it
- Is told to compile-check each script it writes with Check Script, which compiles it in Studio without running it
- Sets an undo point before and after each batch of changes, so Ctrl+Z steps back one batch at a time
Check each door's pivot with Studio's Pivot tool before you test: the door turns around it. With plugin 3.9 or newer RoCode can run a short playtest and read the errors, but it does not move a character or press keys, so walk up to each door and hold E yourself. How RoCode connects to Studio.
Questions
How do I make a door open with E in Roblox Studio?
Put a ProximityPrompt in the door and connect its Triggered event in a server Script, which moves the door. DoorService does both for every door tagged Door. E is the prompt's default key, and Roblox shows the right input for keyboards, controllers and touch screens.
Why does my door spin around its middle?
Its pivot is still in the centre. Turn on Pivot on the Model tab and drag the pivot to the hinge edge; the script turns the door around the pivot.
How do I make a sliding door?
Set the door's Style attribute to Slide. It moves sideways by its own width, or by SlideDistance studs; use a negative distance to slide the other way.
Can I make a door without scripting?
Yes. Roblox's hinged door tutorial joins an unanchored door to its frame with a HingeConstraint, with limits, so players push it open by walking into it. That door has no prompt, no lock and no auto close.
How do I make a door that only VIPs can open?
Use the game pass guide's VIP door, which lets pass owners walk through, or add a check to DoorService's Triggered handler that reads the player's pass ownership on the server before it opens, such as the OwnsPass_VIP attribute that guide sets.
Why does my door open for me but not for other players?
It is being opened in a LocalScript, and a change a LocalScript makes stays on that player's device. Move the opening code to a server Script, as DoorService does, so the change reaches everyone.
Sources and further reading
- Roblox Creator Docs: PVInstance:PivotTo. moving a part or a whole model by its pivot
- Roblox Creator Docs: Pivot tools. moving the pivot, Snap and hotspots, and pivots with a PrimaryPart
- Roblox Creator Docs: Model. WorldPivot, GetExtentsSize following the pivot, and SetPrimaryPartCFrame being superseded
- Roblox Creator Docs: CFrame:Lerp. rotation interpolated along the shortest arc
- Roblox Creator Docs: TweenService. what can be tweened, and a second tween on the same property cancelling the first
- Roblox Creator Docs: Proximity prompts. creating and customising prompts, inputs for every device, and line of sight
- Roblox Creator Docs: ProximityPrompt. default key and button, HoldDuration, MaxActivationDistance and Triggered
- Roblox Creator Docs: CollectionService. tags, GetTagged and GetInstanceAddedSignal
- Roblox Creator Docs: Properties window. adding tags and attributes in Studio
- Roblox Creator Docs: Securing the client-server boundary. ProximityPrompts as an attack surface, and the key-and-door permission check
- Roblox Creator Docs: Network ownership, movement validation, and physics. what a client can do with its own character
- Roblox Creator Docs: Build a hinged door. the no-code HingeConstraint door
- Roblox Creator Docs: In-game tools. the Handle, anchoring and StarterPack
- Roblox Creator Docs: Tool.CanBeDropped. what Backspace does to an equipped tool
How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 15 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.