- An app created with
lua apps new <name>, withnpm installrun in itsweb/folder. @lua-ai-global/app-clientinstalled inweb/(see Switch to the client package if your app hasweb/src/lua/client.ts).
Define the app
A web app is one file,src/apps/<name>/app.ts, that default-exports defineWebApp({...}). List it under webApps on your LuaAgent; lua apps new does both.
src/index.ts
defineWebApp takes:
lua compile fails with a clear message when the name, a route key, or a nav entry is wrong, or when the app has no routes.
Write a route
A route key is a method (GET, POST, PUT, PATCH, or DELETE), one space, and a path. A path segment that starts with : is a parameter. Wrap each handler in route() so the schemas are checked and the handler’s arguments are typed.
src/apps/ops-dashboard/app.ts
route() takes:
The handler’s return value is the response:
- A value (an object, an array) answers 200 with it as JSON.
undefinedanswers 204 with no body.json(value, status, headers?)sets the status.json(undefined, 204)answers with no body.- A Web
Responseis sent as it is.
code is HANDLER_ERROR. Every error the platform produces has the body { code, message }; a body your handler returns is passed through as it is.
A Zod .default() in a query or body schema fills the value in at runtime, but the handler’s type still allows undefined. Add ?? <default> where you read it, as query.status ?? 'open' above.
What a route can use
Routes run in the Lua sandbox, the same place as your tools and webhooks:authis the person using the page:userId,orgId,agentId,roles, andnameandemailwhen known. It is never an API key and never anonymous, so you can record who made a change.Dataand the other runtime objects work as they do in a tool.env('KEY')reads the agent’s environment variables. Set them withlua env.fetchcalls your own APIs. The page never sees the keys you use here.
Call routes from the page
The page project inweb/ is a normal Vite + React project. The template uses @lua-ai-global/ui components and Tailwind v4; add any npm package you need. The page talks to your routes through @lua-ai-global/app-client:
web/src/main.tsx
web/src/App.tsx
The session lives in memory only. The page keeps no token in cookies or storage, and a reload asks the shell for a new session.
Run and test locally
lua apps dev <name> serves the page on the app’s own Vite server with hot reload, and runs each route in the sandbox on your machine as you. Data and env() are the live ones, so writes are real. lua test webapp calls a single route:
auth.roles is empty: who may open an app is decided by the platform, not on your machine. See lua apps and lua test.
Release a version and roll back
lua push webapp builds the page with the app’s Vite and creates a web-app version. It is staged, not live. The agent version you create next pins it, and promoting that agent version makes it live:
lua push and lua push all --force push web apps too. To roll back, promote the previous agent version: the app goes back to the version that agent version pinned. See Release an agent to production.
Who can open the app
Open a live app by URL. There is no menu entry for apps yet, and the native desktop app does not open them yet:
The app opens full page. Anyone who can read the agent can open it: for an agent that is not private, every member of its organization; for a private agent, only the people who can see that agent. Anyone else, including people in other organizations, sees that the app does not exist or that they cannot open it.
Switch an app off
An admin can switch an app off, and on again, from the agent’s Apps page in the admin console. The change takes up to about 30 seconds to reach everyone. While it is off, the app does not open and its routes answer with the codeAPP_DISABLED.
Limits
lua push webapp refuses a build that breaks one of these before it uploads anything:
Keep large data out of
index.html, and rename source files whose built names would use other characters.
Switch to the client package
lua-cli 3.42 to 3.45 copy the page client into your app asweb/src/lua/client.ts. To use the package instead:
from './lua/client' to from '@lua-ai-global/app-client' in src/main.tsx and src/App.tsx, and push the app again.
If it isn’t working
400 VALIDATION from a route
400 VALIDATION from a route
The request did not match the route’s
params, query, or body schema. LuaApiError.message says which field. Check the body you pass to lua.api() against the schema.No web app named … in the compiled agent
No web app named … in the compiled agent
The app is not listed under
webApps on your LuaAgent, or lua compile failed. Add the import and the webApps entry that lua apps new prints.The agent build loads files from web/
The agent build loads files from web/
tsconfig.json must exclude src/apps/*/web. lua apps new adds it when your tsconfig.json is plain JSON; add it by hand when the file has comments.The app opens, but nothing changed after a push
The app opens, but nothing changed after a push
A push is staged. Create an agent version and promote it.
Next steps
About web apps
Where apps open and how access works.
lua apps reference
lua apps new and lua apps dev.Data reference
Every
Data method your routes can call.Release an agent
Agent versions, promote, and rollback.

