Why I built this
Working in the FiveM ecosystem, I kept running into issues with unvalidated data flowing into database mutations. There was no good way to enforce data shapes in Lua the way libraries like Zod do in TypeScript. I built vBuilder to bring that same schema validation pattern to FiveM — define a schema, parse your data, and get clear errors when something doesn’t match.
What it does
vBuilder is a validation schema builder written entirely in Lua. Since Lua doesn’t have a type system or built-in validation primitives, vBuilder fills that gap by providing a chainable API to define schemas and parse incoming data against them. If the data doesn’t match, you get structured errors with the path, message, and error code.
Defining schemas
local schema = vBuilder:object({
name = vBuilder:string().min(1).max(50),
age = vBuilder:number().min(0).max(150),
role = vBuilder:enum({ "admin", "user", "moderator" }),
tags = vBuilder:array(vBuilder:string()).optional(),
})
Parsing data
local result = schema.parse({
name = "John",
age = 25,
role = "admin",
})
-- Invalid data returns a ValidationError with path, message, and code
local bad = schema.parse({ name = 123 })
-- ValidationError: { path = "name", message = "Expected string", code = "invalid_type" }
Supported types
- Strings — with
.min()and.max()length constraints - Numbers — with
.min()and.max()value constraints - Objects — key-value schemas with optional
.passthrough()for extra properties - Arrays — typed collections with size constraints
- Booleans — true/false validation
- Enums — case-sensitive fixed value sets
- Unions — accept multiple types for a single field
All types support .optional() to permit nil values, and custom error messages via options.
Design choices
Objects strip unrecognized properties by default — you have to explicitly opt in with .passthrough() to keep them. This makes it safer for database mutations where you don’t want unexpected fields slipping through.