Build worlds. Ship them in minutes.
Voxa Studio is a free 3D world editor. Drop in parts, write Luau scripts, hit Publish β your world goes live for everyone on Voxa to play.
Your worlds
β
Friends
β
Favorites
β
Worlds on Voxa
β
Quick-start templates
Open in Voxa Studio and Publish β that's it.
Empty
A blank canvas with a spawn point. Build whatever you can imagine.
Baseplate
A flat ground plane. Drop parts onto it and build out.
Obby Starter
A jumping obstacle course with kill bricks and a goal.
Lobby
Walls, floor, and a glowing centerpiece. Drop in spawn portals.
Creations
Your published worlds.
Loading...
Reading your worlds from Voxa.
Asset Library
Upload clothing, avatars, body parts, accessories, and animations for the Voxa community.
Format guide
Shirt / Pants: 1024Γ1024 .png β must be square. A non-square image stretches and lands in the wrong place on the body. Paint on the template so each area maps correctly.
Face: 1024Γ1024 .png β eyes & mouth go on the head-front panel (top-left). Use the face template so features line up.
Hair: .glb / .gltf. Origin at head bone, facing βZ.
Decal: .png / .jpg. Used as face image on parts.
Video: .mp4 / .webm, max ~50 MB.
Sign in (top-right) with your Voxa account β uploading needs your account.
My uploads
Your published assets.
Loading...
Gamepasses
Sell premium features inside your worlds. Players buy them with VoxUs.
How it works
Three steps to your first paid gamepass.
- Open your world in Voxa Studio β the world has to be published before you can attach gamepasses to it.
- Click π Gamepasses in the Studio toolbar. Give your pass a name, description, and price in VoxUs.
- Copy the pass ID. Paste it into a script like
prompt_gamepass("pass_123β¦")to pop up the buy dialog when the player touches a part β or just let it sit on the world detail page where anyone can buy it.
VoxUs the buyer spends is credited to the world creator's balance immediately. When real-money VoxUs purchases come online, creator earnings will flow through this same path.
In-script API
Commands you can use inside Studio scripts.
-- Pop up the buy dialog when the player touches this part
function on_touch(other)
prompt_gamepass("pass_1234567890")
end
-- Fire a custom event only if the player owns the pass
function on_start()
if_owns_gamepass_fire("pass_1234567890", "vip_door_open")
end
Earnings
Cash out the VoxUs you've earned from players buying your items and gamepasses.
Only VoxUs earned from sales counts here. VoxUs you bought or were gifted can be spent in Voxa but can't be cashed out. Minimum payout is 1,000. We review every request by hand, so allow a few days.
You must own this account and be allowed to receive payments where you live. We may ask you to confirm your identity before paying out, and we report earnings as required by law.
Analytics
Per-world plays, session length, and where players come from.
Learn & get help
Docs, scripting reference, and the community.
Luau scripting
The full reference: syntax, functions, lists and tables, events, and all 67 commands.
Read reference βTest mode
Walk through your world without publishing. Scripts run, scripts print, you see it live.
Watch demo βKeyboard shortcuts
Q select Β· M move Β· R rotate Β· T scale Β· Shift+A anchor Β· Ctrl+D duplicate.
View all βScripting reference
Everything you can write in a Script object β syntax, rules, and all 67 commands.
Your first script
In Studio, insert a Script, paste this, and press βΆ Test. Voxa scripting is
Luau-flavored: it reads like Roblox code, and it runs sandboxed, so a script can never touch a
player's computer.
-- runs once when the world loads function on_start() show_toast("Hello Voxa!") spawn_part("Floor", "Block", 0, 0, 0, 20, 1, 20, 0.3, 0.7, 0.4) end -- runs when a player touches this script's part function on_touch() show_toast("You touched it!") win() end
Basics
One statement per line. Comments start with --. Variables need no type.
score = 0 name = "Voxa" msg = "Score: " + str(score) -- + joins text; str() turns numbers into text
Operators are written like this β note these are not the Lua ones:
+ - * / % math (% is remainder) == != < > <= >= comparing (use != , NOT ~=) and or not logic (NOT && || !)
Handy built-ins you can use inside any expression:
abs min max clamp floor
ceil round sqrt sin cos
randi randf str, plus .size() on a list or table.
For a random 0β5: randi() % 6.
If & loops
if score > 10 then show_toast("Nice!") elseif score > 5 then show_toast("Getting there") else show_toast("Keep going") end for i = 0, 9, 1 do -- start, stop (included), step print(i) end while hp > 0 do hp = hp - 1 end
if on one line.
if x then y end will not work. if β¦ then must be alone on its line, the body
on the lines below, and end on its own line. Same for for and while.Loops stop automatically after 100,000 rounds, so a runaway loop can't freeze anyone's game.
Lists & tables
nums = [10, 20, 30] -- a list nums[1] = 99 -- change an item (counting starts at 0) push(nums, 40) -- add to the end (pop(nums) removes the last) count = nums.size() t = {} -- a table: store anything under a key t["hp"] = 100 t[row * 8 + col] = 3 -- keys can be math β perfect for a grid grid = [[1, 2], [3, 4]] -- lists inside lists work too grid[1][0] = 30
Functions
function add(x, y) return x + y end total = add(3, 4) -- use the result do_thing() -- or just run it
=. It can't sit inside a bigger expression.
β
if adjacent(a, b) == 1 then β n = 1 + score_of(p)
β
adj = adjacent(a, b) then if adj == 1 thenScope (where variables live)
Code in an event like on_start makes global variables.
Code inside a function you wrote makes local ones that disappear when it finishes β so a
function can't change a global number directly.
-- β does nothing to the real score function bump() score = score + 1 end -- β keep shared data in a table, which IS shared function on_start() G = {} G["score"] = 0 end function bump() G["score"] = G["score"] + 1 end
Events
| Event | When it runs |
|---|---|
| on_start() | Once, when the world loads. |
| on_tick() | Every frame. Great for spinning or moving things. |
| on_touch() | A player touches the part this script is on. |
| on_activate() | A Tool is clicked while a player holds it. |
| on_<button>() | A GUI button was clicked β named after the button. |
| on_click() | Any GUI button was clicked. Read clicked (its name) and clicked_num (its trailing number) to tell which. |
| on_<name>() | Something called fire("name"). |
Lines written outside any function run as part of on_start.
fire("my_event") runs on_my_event() on every script in the world β
that's how scripts talk to each other.
Making a GUI
Create a screen once, then add elements to it. A button fires an event named exactly after the button β that's how clicks reach your code.
function on_start()
G = {}
G["count"] = 0
spawn_screen_gui("Demo")
spawn_gui("panel", "frame", "Demo", "", 40, 40, 260, 130, 0.10, 0.11, 0.18)
spawn_gui("lbl", "label", "Demo", "Clicks: 0", 40, 60, 260, 28, 0.85, 0.90, 1.00)
spawn_gui("hit", "button", "Demo", "Click me", 90, 110, 160, 44, 0.36, 0.35, 1.00)
end
-- the button is named "hit", so its event is on_hit
function on_hit()
G["count"] = G["count"] + 1
set_text("lbl", "Clicks: " + str(G["count"]))
end
Element types: frame, label, button,
textbox, imagelabel, imagebutton. Positions are in pixels
(x, y, width, height), colors are three 0β1 numbers.
One handler for lots of buttons. Every click also fires
on_click() and sets two values: clicked (the button's name) and
clicked_num (its trailing digits as a number, or β1). So a whole grid needs
one handler, not one per cell:
-- 64 buttons named c0 β¦ c63, handled once
function on_click()
if clicked_num >= 0 then
tap(clicked_num)
end
end
Common mistakes
if β¦ then β¦ end β must be split across lines.2. A function call inside a bigger expression β pull it onto its own line first.
3. A function trying to change a global number β use a table instead.
4.
b[12] and b[12.0] are different table keys.
Loop counters are whole numbers, so this usually just works.5. A whole command must fit on one line β don't wrap the arguments.
6. Misspelled commands fail silently. If nothing happens, check the spelling and the output console.
All commands
Every command works both in Studio βΆ Test and in a published world. A test: simulated tag means Test fakes it β because an editor has no network, no real purchases, and no other players β and prints what it did to the output console.
Showing all 67 commands.
No commands match that search.
Messages
| Command | What it does |
|---|---|
| print(value, β¦) | Writes to the output console. |
| show_toast("message") | Short popup on screen. |
| chat_message("text") | Posts a system message in chat. |
This script's own part
| Command | What it does |
|---|---|
| rotate(x, y, z) | Spins it β degrees per second. Use in on_tick. |
| move(x, y, z) | Slides it β units per second. Use in on_tick. |
| hover(amount, speed) | Floats it up and down. |
| color(r, g, b) | Recolors it. |
| scale(x, y, z) | Resizes it. |
| delete() | Removes it from the world. |
Building
| Command | What it does |
|---|---|
| spawn_part("Name", "Block", x, y, z, sx, sy, sz, r, g, b) | Creates a part. Shapes: Block, Sphere, Cylinder, Cone, Wedge, Capsule, Torus, Plate. |
| spawn_light("Name", "point", x, y, z, r, g, b, energy, range) | Creates a light β "point" or "spot". |
| set_position("Name", x, y, z) | Moves a part. |
| set_size("Name", sx, sy, sz) | Resizes a part. |
| set_rotation("Name", x, y, z) | Rotates a part (degrees). |
| set_color("Name", r, g, b) | Recolors a part. |
| set_material("Name", "neon") | neon, wood, metal, plastic, glass⦠|
| set_anchored("Name", true) | Anchored parts ignore gravity. |
| clone_part("Source", "NewName") | Copies a part. |
| destroy("Name") | Deletes a part. |
Screen GUI
| Command | What it does |
|---|---|
| spawn_screen_gui("ScreenName") | Creates a screen. Do this before adding elements. |
| spawn_gui("Name", "type", "Screen", "text", x, y, w, h, r, g, b) | Adds an element. A button fires on_<Name> when clicked. |
| set_text("Name", "text") | Changes the words on a label or button. |
| set_gui_color("Name", r, g, b) | Recolors an element. |
| set_visible("Name", true) | Shows or hides an element. |
Lists, values & events
| Command | What it does |
|---|---|
| push(list, value) | Adds to the end of a list. |
| pop(list) | Removes the last item. |
| fire("event_name") | Runs on_event_name() on every script. |
| set("key", value) | Saves a Stored Value shared across the world. |
| random_int("key", min, max) | Puts a random whole number into a Stored Value. |
The player
| Command | What it does |
|---|---|
| win() | Triggers the win screen. |
| respawn() | Sends the player back to spawn. |
| boost(seconds) | Quick speed boost. |
| give_speed(multiplier, seconds) | Timed speed multiplier. |
| teleport_player(x, y, z) | Moves the player instantly. |
| kill_player("reason") | Respawns them with a message. |
| take_damage(amount) | Removes health. |
| heal(amount) | Restores health. |
| set_walkspeed(value) | How fast they walk. |
| set_jumppower(value) | How high they jump. |
| play_animation("Name") | Plays an avatar animation. |
| move_to(speed, "Waypoint")test: simulated | Walks this part to a spot. Test moves in a straight line (no obstacle routing). |
Camera
| Command | What it does |
|---|---|
| camera_shake(strength, seconds) | Shakes the view β great for explosions. |
| camera_follow("PartName") | Watches a part instead of the player. |
| camera_release() | Gives the camera back to the player. |
| camera_first_person(true) | Switches to first person. |
| set_camera_fov(degrees) | Zooms the view in or out. |
The world
| Command | What it does |
|---|---|
| set_gravity(value) | Changes gravity. 9.8 is normal, low = floaty. |
| set_time_of_day(0β¦24) | Moves the sun. 12 is noon. |
| set_hinge_angle("Hinge", degrees) | Opens or closes a hinge β doors, levers. |
| play_sound("SoundName") | Plays a Sound you placed in the world. |
Smooth motion (tweens)
| Command | What it does |
|---|---|
| tween_position(x, y, z, seconds) | Glides this part to a spot. |
| tween_rotation(x, y, z, seconds) | Turns it smoothly. |
| tween_color(r, g, b, seconds) | Fades its color. |
Teams & leaderboard
| Command | What it does |
|---|---|
| team_create("Name", r, g, b) | Makes a team. |
| team_assign("Name") | Puts the player on it. |
| leaderstat_set("Score", value) | Sets a leaderboard number. |
| leaderstat_add("Score", 10) | Adds to it. |
Saving & internet
| Command | What it does |
|---|---|
| data_set("key", value)test: simulated | Saves for that player between visits. In Test it only lasts the session. |
| data_get_fire("key", "event")test: simulated | Loads saved data, then fires your event so you can use it. |
| http_get_fire("url", "key", "event")test: simulated | Fetches from an approved site. Test skips the request and fires with an empty result. |
Gamepasses
| Command | What it does |
|---|---|
| prompt_gamepass("pass_id")test: simulated | Opens the buy window. Test never charges β it just logs it. |
| if_owns_gamepass_fire("pass_id", "event")test: simulated | Fires only for owners β VIP doors. Test assumes you own it so you can check the unlocked path. |
Automatic triggers
| Command | What it does |
|---|---|
| distance_fire("A", "B", range, "event") | Fires when two parts get close. |
| player_count_fire(count, "event")test: simulated | Fires when enough players join. Test always has 1 player. |
| for_count(n, "event") | Fires an event n times. |
Other
| Command | What it does |
|---|---|
| require("Module", "function")test: simulated | Calls a function in a ModuleScript. Not run in Test. |
| wait(seconds) | Pauses your script for that many seconds, then carries on β works inside loops and functions too. Great for countdowns, cooldowns and cutscenes. Max 60s. |
What's new
Recent updates to Voxa Studio and the platform.
FeatureGamepasses
Create gamepasses in Studio. Players buy with VoxUs from a world's detail page, or via prompt_gamepass() popups your scripts trigger.
FeatureAchievements & badges
Nine achievements (Newcomer Β· Explorer Β· Socializer Β· Loaded β¦). Each gives 25β200 VoxUs when you claim.
FeatureSmarter chat filtering
Improved second-pass classifier for chat + comments. Catches stuff the static word-list misses.
FeatureBlock + Report
Block hides their chat and comments client-side. Report writes to a private queue for review.
FeatureWorld details page in-game
Full detail page when you click a world tile: votes, comments, gamepasses, info, all without leaving the menu.
FeatureMove / Rotate / Scale gizmos
Q select Β· M move Β· R rotate Β· T scale. Drag the colored axis handles in the 3D viewport.
FeatureTest mode runs scripts
Scripts and built-in behaviors (rotate, hover, kill, goal, speedpad) run when you hit βΆ Test. Output console captures every print().
NoteAuto-save every 60 s
Studio silently saves a draft to your account every minute.