Skip to main content
The @type annotation allows you to specify concrete type information for variables, expressions, or objects in your Lua code. This enables better autocomplete, type checking, and documentation.

Syntax

---@type <type_expression>

Basic Types

Declare primitive types for variables:
---@type string
local userName = "John"
-- userName is now recognized as a string

---@type number
local userAge = 25
-- userAge is recognized as a number

---@type boolean
local isActive = true
-- isActive is recognized as a boolean
Adding type annotations helps the language server provide better autocomplete suggestions and catch type errors early.

Union Types

Define variables that can hold multiple types:
---@type string | number
local mixedValue = "Can be string or number"
-- mixedValue can be either a string or a number

---@type string?
local optionalString = nil
-- Equivalent to string | nil
-- The ? suffix is shorthand for "this type OR nil"

Intersection Types

Combine multiple types where values must satisfy all type requirements:
---@type T & U
local mergedValue = getMergedValue()
How intersection types work:
  • Fields from both types are included
  • If a field exists on both sides, its type becomes the intersection of the two types
  • Conflicting field types often collapse to never (e.g., {y: number} & {y: string}y: never)
  • Best used with classes or object types that define fields
For overwrite-style merges where one type should win, use Merge<T, U> (right wins) or Merge<U, T> (left wins) instead of intersection types.

Array Types

Declare arrays with specific element types:
---@type string[]
local nameList = {"John", "Jane", "Bob"}
-- Array of strings

---@type number[]
local scores = {95, 87, 92, 88}
-- Array of numbers

---@type (string | number)[]
local mixedArray = {"John", 25, "Jane", 30}
-- Array that can contain strings OR numbers

Dictionary Types

Define key-value mappings:
---@type table<string, number>
local ageMap = {
    ["John"] = 25,
    ["Jane"] = 30,
    ["Bob"] = 28
}
-- Keys are strings, values are numbers

---@type table<number, string>
local idToName = {
    [1001] = "John",
    [1002] = "Jane",
    [1003] = "Bob"
}
-- Keys are numbers, values are strings

Tuple Types

Define fixed-length arrays with specific types at each position:
---@type [string, number, boolean]
local userInfo = {"John", 25, true}
-- First element is string, second is number, third is boolean

Table Literal Types

Define structured object types inline:
---@type {name: string, age: number, email: string}
local user = {
    name = "John",
    age = 25,
    email = "[email protected]"
}

---@type {user: {id: number, name: string}, permissions: string[]}
local userWithPermissions = {
    user = {id = 1001, name = "John"},
    permissions = {"read", "write", "delete"}
}
-- Nested table structures are supported

Function Types

Declare function signatures:
---@type fun(x: number, y: number): number
local addFunction = function(x, y)
    return x + y
end
-- Function takes two numbers and returns a number

---@type fun(name: string, age: number): {name: string, age: number}
local createUser = function(name, age)
    return {name = name, age = age}
end
-- Function returns a table with name and age fields

---@type async fun(url: string): string
local fetchData = async function(url)
    return await httpGet(url)
end
-- Async function that returns a string

Class Types

Reference custom class types defined with @class:
---@class User
---@field id number
---@field name string

---@type User
local currentUser = {
    id = 1001,
    name = "John"
}

---@type User[]
local userList = {
    {id = 1001, name = "John"},
    {id = 1002, name = "Jane"}
}
-- Array of User objects

Generic Types

Use parameterized types for flexible, reusable type definitions:
---@class Container<T>
---@field items T[]

---@type Container<string>
local stringContainer = {
    items = {"hello", "world"}
}
-- Container holding strings

---@type Container<number>
local numberContainer = {
    items = {1, 2, 3, 4, 5}
}
-- Container holding numbers

---@type table<string, Container<User>>
local userContainerMap = {
    ["admins"] = {items = {{id = 1, name = "Admin"}}},
    ["users"] = {items = {{id = 2, name = "Regular User"}}}
}
-- Complex generic combinations are supported

Enumeration Types

Use type aliases for enumeration-like values:
---@alias Status 'active' | 'inactive' | 'pending'

---@type Status
local currentStatus = 'active'
-- currentStatus can only be 'active', 'inactive', or 'pending'

Callback Types

Define callback function signatures:
---@type fun(error: string?, result: any?): nil
local callback = function(error, result)
    if error then
        print("Error:", error)
    else
        print("Result:", result)
    end
end
-- Callback with optional error and result parameters

Practical Examples

Event Handlers

---@type table<string, fun(...)>
local eventHandlers = {
    ["click"] = function(x, y)
        print("Click position:", x, y)
    end,
    ["keypress"] = function(key)
        print("Key pressed:", key)
    end
}

Index Signatures

---@type {[string]: any}
local dynamicObject = {
    someKey = "someValue",
    anotherKey = 123,
    yetAnother = true
}
-- Object with dynamic string keys

Type Narrowing in Conditionals

---@type User | nil
local user = isLoggedIn and getCurrentUser() or nil

if user then
    -- In this block, user's type is User (non-nil)
    print("Username:", user.name)
    print("User ID:", user.id)
end

Type-Safe Loops

---@type string[]
local nameList = {"John", "Jane", "Bob"}

---@type string
for _, name in ipairs(nameList) do
    print("Name:", name)  -- name is inferred as string type
end

Common Patterns

Promise Types

---@class Promise<T>
---@field then fun(self: Promise<T>, onResolve: fun(value: T), onReject?: fun(error: any))

---@type Promise<string>
local dataPromise = fetchUserDataAsync(1001)

Readonly Types (by convention)

---@type {readonly name: string, readonly id: number}
local readonlyUser = {name = "John", id = 1001}
While Lua doesn’t enforce readonly at runtime, this convention helps document intent and some language servers may provide warnings when modifying readonly fields.

Best Practices

  1. Use union types for flexible parameters: string | number is clearer than any
  2. Prefer specific types over any: Type annotations are most useful when they’re specific
  3. Use optional types (?) for nullable values: Makes nil-checking explicit
  4. Combine with @class for complex structures: Define reusable types instead of repeating table literals
  5. Annotate public APIs: Focus on function parameters and return values that others will use
The @type annotation only provides type information to the language server. It does not perform runtime type checking or validation. Always validate user input at runtime.

Build docs developers (and LLMs) love