Tutorial · Gameplay

How to make a press E to interact prompt in Roblox

The "Press E" bubble you see next to chests, doors and NPCs in Roblox games is a ProximityPrompt. Roblox draws it for you, shows the right key or button on keyboard, controller and touch screens, and fires an event on the server when a player uses it. This guide makes a working prompt with no UI work, then covers hold-to-use, one script for every prompt in the game, prompts only some players can see, and replacing Roblox's look with your own.

Updated 5 November 20268 min readBeginnerLuauWritten by

In short

Insert a ProximityPrompt into a Part (or an Attachment inside it), set ActionText and ObjectText, and in a Script in ServerScriptService connect prompt.Triggered, which passes the player who used it. Change HoldDuration for hold-to-use and MaxActivationDistance for how close players must be. E is the default key; Roblox shows the matching gamepad button and a tap target on phones.

  • Handle Triggered on the server, so the result counts for everyone.
  • Treat a prompt like a RemoteEvent: Roblox's security guide says prompts can be abused, so check the player and add a cooldown.
  • For many prompts, connect ProximityPromptService.PromptTriggered once instead of one script per prompt.

Get the script

Press E to open chest script (ProximityPrompt)

One server Script gives every part tagged Chest a hold-E prompt that pays coins once per player, with distance and cooldown checks on the server.

Script in ServerScriptService named ChestPrompts
-- Script in ServerScriptService named ChestPrompts.
-- Every part tagged "Chest" gets a hold-E prompt that pays coins once per player.
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")

local CHEST_TAG = "Chest"
local CHEST_COINS = 25
local HOLD_SECONDS = 0.5
local MAX_DISTANCE = 10 -- studs, the prompt's MaxActivationDistance

local opened: { [Player]: { [Instance]: boolean } } = {}

local function giveCoins(player: Player, amount: number)
	local leaderstats = player:FindFirstChild("leaderstats")
	local coins = leaderstats and leaderstats:FindFirstChild("Coins")
	if coins and coins:IsA("IntValue") then
		coins.Value += amount
	end
end

local function setUpChest(chest: Instance)
	if not chest:IsA("BasePart") or chest:FindFirstChild("ChestPrompt") then
		return
	end
	local prompt = Instance.new("ProximityPrompt")
	prompt.Name = "ChestPrompt"
	prompt.ActionText = "Open"
	prompt.ObjectText = "Chest"
	prompt.HoldDuration = HOLD_SECONDS
	prompt.MaxActivationDistance = MAX_DISTANCE
	prompt.Parent = chest

	prompt.Triggered:Connect(function(player: Player)
		-- Prompts can be abused like remotes, so check again on the server
		local character = player.Character
		local humanoid = character and character:FindFirstChildOfClass("Humanoid")
		local root = humanoid and humanoid.RootPart
		if not (humanoid and root) or humanoid.Health <= 0 then
			return
		end
		if (root.Position - chest.Position).Magnitude > MAX_DISTANCE + 4 then
			return -- too far to have used the prompt honestly
		end
		local playerOpened = opened[player]
		if not playerOpened or playerOpened[chest] then
			return -- already opened by this player
		end
		playerOpened[chest] = true
		giveCoins(player, CHEST_COINS)
	end)
end

Players.PlayerAdded:Connect(function(player: Player)
	opened[player] = {}
end)
for _, player in Players:GetPlayers() do
	opened[player] = {}
end
Players.PlayerRemoving:Connect(function(player: Player)
	opened[player] = nil
end)

for _, chest in CollectionService:GetTagged(CHEST_TAG) do
	setUpChest(chest)
end
CollectionService:GetInstanceAddedSignal(CHEST_TAG):Connect(setUpChest)

RoCode builds it in the place you have open in Roblox Studio, on Windows or macOS with a desktop browser; on a phone the button sends the prompt and the link to your computer. Free to start, with free credits every day. More Roblox Studio scripts

Where to put it

Explorer: one Script, then tag your chests
ServerScriptService
  ChestPrompts Script new
Workspace
  Chest Part (tag: Chest)

Settings you can change

CHEST_COINSIn the script
Coins a player gets from each chest. Default: 25.
HOLD_SECONDSIn the script
How long E must be held. 0 makes it a single press. Default: 0.5.

Coins go into a Coins IntValue in the player's leaderstats, which the leaderboard guide creates.

What a cheater can and cannot doThe server checks the player is alive and near the chest, and each player can open each chest once. Coins are only added on the server.

Type-checked with luau-lsp strict against Roblox's API definitions on 5 November 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.

Prompt for RoCode

Add treasure chests: every part tagged Chest gets a hold-E ProximityPrompt that says Open Chest. On the server, give the player 25 coins in leaderstats once per chest, check they are alive and within 12 studs, and hide the prompt for players who already opened it.

On this page
  1. Get the script
  2. How it works
  3. 1. First prompt
  4. 2. Server script
  5. 3. One handler
  6. 4. Per player
  7. 5. Custom look
  8. Common mistakes
  9. Common errors
  10. With RoCode
  11. Questions
  12. Sources

How a ProximityPrompt works

A ProximityPrompt sits inside the thing players interact with. When a player's character comes within MaxActivationDistance studs (10 by default) and the prompt is the closest one, Roblox shows it on their screen. When they press the key, or hold it for HoldDuration seconds, the prompt fires Triggered with that player, both on the server and on their client.

The properties you change most
PropertyDefaultWhat it does
ActionTextInteractThe verb on the prompt: Open, Talk, Buy.
ObjectTextemptyThe thing: Chest, Shopkeeper, Door.
HoldDuration0Seconds to hold before it triggers. 0 is a single press.
MaxActivationDistance10How close, in studs, a player must be.
KeyboardKeyCodeEThe key on a keyboard. GamepadKeyCode is the controller button (ButtonX).
RequiresLineOfSighttrueHide the prompt when something blocks the view of its part.
ExclusivityOnePerButtonHow many prompts can show at once for the same key.
EnabledtrueTurn the prompt off without deleting it.

Where you put it matters. Inside a Part, the prompt floats at the part's centre. Inside an Attachment in that part, it floats at the attachment, which lets you place it on a handle or above an NPC's head.

Step 1: A prompt with no scripting

  1. Insert a Part in Workspace, name it Chest, and tick Anchored.
  2. Hover over the part, click + and insert a ProximityPrompt.
  3. In the Properties window set ActionText to Open and ObjectText to Chest.
  4. Press Play and walk up to the part. The prompt appears within 10 studs. Pressing E does nothing yet: Step 2 adds what happens.

Players on a gamepad see the X button and players on a phone see a button they tap, with no extra work. Roblox picks the closest prompt when several are in range, and Exclusivity decides whether others for the same key can show at the same time.

Step 2: Do something when it is used

This script turns every part tagged Chest into a chest that pays coins once per player. Add the tag to the part (select it, scroll the Properties window to Tags, click +, type Chest). Delete the prompt you inserted in Step 1, because the script adds its own to each chest and two prompts would show. Then insert a Script in ServerScriptService named ChestPrompts.

Script in ServerScriptService named ChestPrompts
-- Script in ServerScriptService named ChestPrompts.
-- Every part tagged "Chest" gets a hold-E prompt that pays coins once per player.
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")

local CHEST_TAG = "Chest"
local CHEST_COINS = 25
local HOLD_SECONDS = 0.5
local MAX_DISTANCE = 10 -- studs, the prompt's MaxActivationDistance

local opened: { [Player]: { [Instance]: boolean } } = {}

local function giveCoins(player: Player, amount: number)
	local leaderstats = player:FindFirstChild("leaderstats")
	local coins = leaderstats and leaderstats:FindFirstChild("Coins")
	if coins and coins:IsA("IntValue") then
		coins.Value += amount
	end
end

local function setUpChest(chest: Instance)
	if not chest:IsA("BasePart") or chest:FindFirstChild("ChestPrompt") then
		return
	end
	local prompt = Instance.new("ProximityPrompt")
	prompt.Name = "ChestPrompt"
	prompt.ActionText = "Open"
	prompt.ObjectText = "Chest"
	prompt.HoldDuration = HOLD_SECONDS
	prompt.MaxActivationDistance = MAX_DISTANCE
	prompt.Parent = chest

	prompt.Triggered:Connect(function(player: Player)
		-- Prompts can be abused like remotes, so check again on the server
		local character = player.Character
		local humanoid = character and character:FindFirstChildOfClass("Humanoid")
		local root = humanoid and humanoid.RootPart
		if not (humanoid and root) or humanoid.Health <= 0 then
			return
		end
		if (root.Position - chest.Position).Magnitude > MAX_DISTANCE + 4 then
			return -- too far to have used the prompt honestly
		end
		local playerOpened = opened[player]
		if not playerOpened or playerOpened[chest] then
			return -- already opened by this player
		end
		playerOpened[chest] = true
		giveCoins(player, CHEST_COINS)
	end)
end

Players.PlayerAdded:Connect(function(player: Player)
	opened[player] = {}
end)
for _, player in Players:GetPlayers() do
	opened[player] = {}
end
Players.PlayerRemoving:Connect(function(player: Player)
	opened[player] = nil
end)

for _, chest in CollectionService:GetTagged(CHEST_TAG) do
	setUpChest(chest)
end
CollectionService:GetInstanceAddedSignal(CHEST_TAG):Connect(setUpChest)

The distance check uses 4 studs more than the prompt's range, for a player who moved while holding E. "Opened" is kept per player in a table on the server, so it resets when they leave; to remember it between sessions, save it with the DataStore tutorial's module. The CollectionService guide explains the tag pattern used here.

Step 3: One handler for every prompt

ProximityPromptService.PromptTriggered fires for every prompt in the game, with the prompt and the player. That lets one script route all prompts by an attribute, instead of connecting each one. Give each prompt a string attribute named Action (for example Heal or Sit) and handle it here:

Script in ServerScriptService named PromptRouter
-- Script in ServerScriptService named PromptRouter.
-- Handles every ProximityPrompt that has an "Action" attribute.
local ProximityPromptService = game:GetService("ProximityPromptService")

local handlers: { [string]: (player: Player, prompt: ProximityPrompt) -> () } = {
	Heal = function(player: Player, _prompt: ProximityPrompt)
		local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
		if humanoid and humanoid.Health > 0 then
			humanoid.Health = humanoid.MaxHealth
		end
	end,
	Ring = function(_player: Player, prompt: ProximityPrompt)
		local sound = prompt.Parent and prompt.Parent:FindFirstChildOfClass("Sound")
		if sound then
			sound:Play()
		end
	end,
}

ProximityPromptService.PromptTriggered:Connect(function(prompt: ProximityPrompt, player: Player)
	local action = prompt:GetAttribute("Action")
	local handler = if typeof(action) == "string" then handlers[action] else nil
	if handler then
		handler(player, prompt)
	end
end)

Add the same distance and alive checks as in Step 2 to any handler that gives something of value. ProximityPromptService.MaxPromptsVisible limits how many prompts a player sees at once, which helps in a crowded shop.

Step 4: Prompts only some players see

A prompt's Enabled set by a LocalScript changes it for that player only. That is how you hide a chest's prompt for someone who already opened it, or show a "Sell" prompt only to players carrying something. The server stays in charge: it sets an attribute, and the LocalScript reads it.

LocalScript in StarterPlayerScripts named ChestPromptVisibility
-- LocalScript in StarterPlayerScripts named ChestPromptVisibility.
-- Hides a chest's prompt for this player once the server marks it opened.
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")

local player = Players.LocalPlayer

local function refresh(chest: Instance)
	local prompt = chest:FindFirstChild("ChestPrompt")
	if prompt and prompt:IsA("ProximityPrompt") then
		prompt.Enabled = player:GetAttribute("Opened_" .. chest.Name) ~= true
	end
end

local function watch(chest: Instance)
	chest.ChildAdded:Connect(function()
		refresh(chest)
	end)
	player:GetAttributeChangedSignal("Opened_" .. chest.Name):Connect(function()
		refresh(chest)
	end)
	refresh(chest)
end

for _, chest in CollectionService:GetTagged("Chest") do
	watch(chest)
end
CollectionService:GetInstanceAddedSignal("Chest"):Connect(watch)

For this to work, add one line to ChestPrompts in Step 2, right after playerOpened[chest] = true: player:SetAttribute("Opened_" .. chest.Name, true). Give each chest a different name made of letters and numbers only, because attribute names cannot contain spaces. Attributes set on the server replicate to the player, so the LocalScript sees the change. Hiding a prompt is only a convenience: the server check in Step 2 is what really stops a second opening.

Step 5: Change how the prompt looks

Set Style to Custom and Roblox stops drawing the prompt; instead, ProximityPromptService.PromptShown fires on the client and you draw your own. This LocalScript shows a simple BillboardGui with the action text and hides it when the prompt hides.

LocalScript in StarterPlayerScripts named CustomPrompts
-- LocalScript in StarterPlayerScripts named CustomPrompts.
-- Draws a simple label for prompts whose Style is Custom.
local ProximityPromptService = game:GetService("ProximityPromptService")

ProximityPromptService.PromptShown:Connect(function(prompt: ProximityPrompt, _inputType: Enum.ProximityPromptInputType)
	if prompt.Style ~= Enum.ProximityPromptStyle.Custom then
		return
	end
	local billboard = Instance.new("BillboardGui")
	billboard.Size = UDim2.fromOffset(160, 44)
	billboard.StudsOffset = Vector3.new(0, 2.5, 0)
	billboard.AlwaysOnTop = true
	billboard.Adornee = prompt.Parent :: any

	local label = Instance.new("TextLabel")
	label.Size = UDim2.fromScale(1, 1)
	label.BackgroundColor3 = Color3.fromRGB(30, 24, 46)
	label.TextColor3 = Color3.new(1, 1, 1)
	label.Font = Enum.Font.GothamBold
	label.TextScaled = true
	label.Text = "[" .. prompt.KeyboardKeyCode.Name .. "] " .. prompt.ActionText
	label.Parent = billboard

	billboard.Parent = (game:GetService("Players").LocalPlayer :: Player):WaitForChild("PlayerGui")
	prompt.PromptHidden:Once(function()
		billboard:Destroy()
	end)
end)

A custom prompt still triggers the normal way: the player presses the key, Triggered fires on the server, and your Step 2 or Step 3 handler runs. For touch players, also make the label a TextButton and call prompt:InputHoldBegin() and prompt:InputHoldEnd() from its events, since a label alone cannot be tapped. The overhead name tag guide covers BillboardGui sizing in more depth.

Common ProximityPrompt mistakes

What goes wrong, and the fix
MistakeWhat happensFix
Handling Triggered in a LocalScriptThe reward or change only exists on that player's screen.Handle it in a server Script.
No checks in TriggeredA cheater can fire prompts from far away or many times a second.Check distance, health and a cooldown on the server.
Prompt inside a ModelNothing shows: a prompt needs a Part or an Attachment as its parent.Put it in a part of the model, or an Attachment.
Prompt hidden behind its own wallWith RequiresLineOfSight on, the part itself or a frame blocks the view.Move it to an Attachment in front, or turn line of sight off.
Using Touched for "press E"Fires repeatedly and does not wait for a key.Use a ProximityPrompt, or the debounce guide if it must be Touched.

Common errors and fixes

Open the Output window (Window menu, then Output) before you press Play.

The prompt never appears

Check that its parent is a Part, MeshPart or Attachment, not a Model or Folder; that Enabled is ticked; that you are within MaxActivationDistance; and, if RequiresLineOfSight is on, that nothing blocks the view of its part. A prompt with Style set to Custom shows nothing until your own script draws it.

Pressing E does nothing

The prompt has nothing connected. Check that the Script is in ServerScriptService and that it found the prompt: a misspelt tag or name means Triggered was never connected. Add print(player.Name) as the first line of the handler.

It triggers but the player gets nothing

The handler returned early. Usually there is no leaderstats.Coins IntValue, or the distance check is too strict because the part is large: measure from the prompt's Attachment or raise the allowance.

Two prompts fight over the E key

Only the closest shows with the default Exclusivity. Give one of them another key with KeyboardKeyCode, or set Exclusivity to AlwaysShow.

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.

You type

Give every part tagged Chest a hold-E prompt that says Open Chest. On the server, give 25 coins once per player per chest, check the player is alive and close, and hide the prompt for players who already opened that chest.

RoCode does
  • Can look through your place first with Search Game, so it uses your existing leaderstats and chests instead of making new ones
  • Writes the server Script and the LocalScript with Create Script, and can add the tag to your chest parts
  • 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

With plugin 3.9.15 or newer RoCode can control a test character that walks and presses keys, but it checks only what it chose to test, so walk up to each prompt and hold E yourself. How RoCode connects to Studio.

Questions

How do I make a press E to interact in Roblox?

Insert a ProximityPrompt into a part and connect its Triggered event in a server Script. E is the default key, and Roblox shows the right button on controllers and phones.

How do I make a hold E prompt?

Set the prompt's HoldDuration to the number of seconds to hold, for example 1. Roblox draws a ring that fills while the key is held.

How do I change the key of a ProximityPrompt?

Set KeyboardKeyCode (for example to F) and GamepadKeyCode for controllers in the Properties window or from a script.

Should I handle Triggered on the server or the client?

On the server for anything that changes the game: coins, doors, items. The client version is only for effects on that player's screen.

Can I make a ProximityPrompt look different?

Yes. Set Style to Custom and draw your own UI from ProximityPromptService.PromptShown in a LocalScript, as in Step 5.

Sources and further reading

How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 5 November 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.

Try RoCode on your own place.

Free every day, no card required. Install the plugin, describe the feature, and review what lands in Studio.