Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>luautNew to Visual Studio Code? Get it now.
luaut

luaut

uav1010

|
2 installs
| (0) | Free
luaut language support — diagnostics, hover, completion, go-to-definition
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

luaut for VS Code

Thin client for luaut-language-server. It starts the server and gets out of the way — every feature is the server's, so anything you see here works the same in Neovim, Zed or any other LSP client.

The server is bundled into the extension (dist/server.cjs, built from the luaut-language-server package), so a packaged .vsix is self-contained: no npm install on the user's machine, no path to configure.

The server comes from npm like any other dependency, so this project builds on its own — no checkout of anything else required.

luaut is Luau, written the TypeScript way

luaut compiles to Luau and keeps Luau's semantics: the same values, the same nil, the same 1-based arrays, the same metatables, the same runtime. What changes is the surface — where Lua and TypeScript spell the same idea differently, luaut takes TypeScript's spelling, and the type system that comes with it. So a Roblox script does what it always did; it just reads like .ts.

Everything below is a difference you can see in this editor. Everything not listed is Luau as you know it: and / or / not, nil, .., ~=, #x, -- comments, : method calls, 1-based indexes, pairs / ipairs, elseif, repeat … until, continue.

Blocks — braces, and a parenthesised condition

Luau luaut
if x then … end if (x) { … }
elseif y then } elseif (y) {
while x do … end while (x) { … }
for i = 1, 10 do … end for (i = 1, 10) { … }
for k, v in pairs(t) do … end for (k, v in pairs(t)) { … }
repeat … until x repeat { … } until (x)
do … end do { … }
function f() … end function f() { … }

There is no end, and then is not a word luaut knows — both are still reserved, so using one is an error rather than a silent identifier.

The parentheses around a condition are not decoration: f {} is a call with a table argument, so without them if ready { … } would read as a call of ready followed by a block.

if (n < 0) {
    return "negative"
} elseif (n == 0) {
    return "zero"
} else {
    return "positive"
}

Declarations — const / let, no local

local is gone. const and let are hard keywords; function name() { … } declares name in the enclosing scope and cannot be reassigned, the way a TypeScript function declaration cannot.

Function declarations hoist: a name is visible to its whole block, above itself too.

{} and [] mean different things

Luau writes every table with {}. luaut splits them, as JavaScript does:

Luau luaut
object { x = 1, y = 2 } { x: 1, y: 2 }
array { 1, 2, 3 } [1, 2, 3]
computed key { [k] = v } { [k]: v }
shorthand — { x, y }
spread — { ...base, c: 3 }, [...a, ...b]

Arrays are still Luau arrays underneath — the first element is index 1.

One arrow, for the type and for the function

Luau writes a function type with ->. luaut writes both a function type and a function value with =>, and decides which one from where it stands, since a type and a value never share a place. -> is gone.

type Reducer = (total: number, value: number) => number

const double = (x: number) => x * 2
const add: Reducer = (a, b) => a + b        -- typed by the contract
each(n => print(n))                         -- one parameter needs no parens
const wrap = (n: number) => ({ value: n })  -- an object body is parenthesised

An arrow is just a short function expression — there is no second kind of function — so this inside one is the this of the method around it.

Optionality — no T?

? in type position always belongs to a conditional type, and in expression position to a ternary or an optional chain. Optionality is TypeScript's:

name?: T        -- may be absent; its type is `T | nil`
name: T | nil   -- must be written, but may be nil

Omitting an argument requires ? or a default — a parameter typed T | nil still has to be passed something.

Things Luau does not have at all

  • Classes — class Dog extends Animal { … }, constructor, get / set, static, super, new Dog(…), generic class Box<T>. It is sugar over the usual metatable idiom, and new Dog(x) is Dog.new(x).
  • Modules — import / export instead of require, including import type, re-exports and export default. Imports are read-only.
  • Optional chaining — a?.b and a?:m(x), which stop the whole chain when the receiver is nil, and narrow what they tested.
  • Ternary — c ? a : b.
  • Template strings — `hello ${name}`.
  • Rest and spread — ...parts: string[] in a signature, f(...names) at a call. Bare ... is still Lua's pack.
  • Array and string methods — names:filter(…):map(…), text:trim(), written with : as JavaScript writes them with .. Which methods exist comes from the type library, not the language.

A real type system

This is the part that shows up as squiggles in the editor. luaut checks what TypeScript checks, not what Luau checks:

  • strictNullChecks always on — reading a member of a possibly-nil value is an error until a check narrows the nil away.
  • TypeScript's narrowing model: references (x.a.b) rather than just variables, discriminated unions, and / or, early return, break, user type guards (v is T), assertion signatures (asserts v).
  • Unions, intersections, tuples, keyof, T[K], conditional types with infer, mapped types, template literal types, generics with constraints and defaults, satisfies, as const, overload sets, branded types.
  • Only nil and false are falsy — 0 and "" are truthy, as in Lua, not as in JavaScript.

No globals are built in — not print, not game. A project lists its type libraries in luaut.config.json, the way TypeScript uses @types/*:

// luaut.config.json
{
  "types": ["roblox"],                          // npm i -D @luaut/roblox
  "paths": { "@shared/*": ["src/shared/*"] },
  "sourceMap": "sourcemap.json"                 // a Rojo sourcemap, or null
}

The extension validates this file as you type it, and the server reloads when it changes.

Moving a file over

npx tsx scripts/to-braces.ts <file|dir> in the luaut-parser checkout rewrites Lua-spelled source into braces. Each rewrite is parsed and compared against the tree the original made; a file it cannot say the same thing about is left alone.

Full language reference: the luaut-parser README.

Three ways to run it

1. Develop it — F5

npm install
npm run build

Open this folder in VS Code and press F5. A second window opens on sample/, with hello.luaut to poke at: hover a name, ctrl-click it, type part., watch the deliberate type error appear as you edit.

Changed the server? Rebuild (Ctrl+Shift+B) and run luaut: Restart Language Server in the dev window — no need to restart the host. To debug the server itself, run the Attach to server launch configuration while the dev window is open (port 6009).

2. Install it into your own VS Code

npm run reinstall    # package, then install into your VS Code

Then Developer: Reload Window — the installed extension is a copy, so npm run build alone changes nothing you can see there (that is what F5 is for). code --uninstall-extension luaut.luaut-vscode removes it. (Or: Extensions view → ... → Install from VSIX….)

This is also the file to hand someone else — it runs anywhere without a checkout.

3. Publish it to the Marketplace

One-time setup:

  1. Create a publisher at https://marketplace.visualstudio.com/manage and put its id in package.json → "publisher" (currently uav1010, which is almost certainly not yours).
  2. Create an Azure DevOps personal access token — https://dev.azure.com → User settings → Personal access tokens → All accessible organizations, scope Marketplace ▸ Manage.
  3. npx vsce login <publisher-id> and paste the token.

Then, per release: bump version, and

npm run publish            # or: npx vsce publish minor

For the VS Codium / Cursor / Gitpod side, publish the same .vsix to Open VSX: npx ovsx publish luaut-vscode-<version>.vsix -p <token>.

Before the first publish, add a LICENSE file (vsce warns without one) and, if you want the listing to look finished, a 128×128 icon.

Settings

setting what it does
luaut.server.path absolute path to a cli.js/server.cjs to use instead of the bundled server
luaut.trace.server messages / verbose logs LSP traffic to the luaut output channel

What is in here

  • src/extension.ts — locate the server, start it, register the restart command
  • syntaxes/luaut.tmLanguage.json — TextMate grammar (colours only; the server does the understanding)
  • language-configuration.json — comments, brackets, indentation
  • sample/ — the folder the dev host opens
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft