Skip to main content
In this tutorial you build ops-dashboard, a web app on your agent. It has a page that lists tickets and opens new ones, and two routes that read and write a Data collection as the person using the page. At the end, the app is live and open in Lua Workspace in your browser. Allow 20 minutes. You need a project created with lua init and a user session from lua auth configure (Install the CLI and sign in), and Node.js 20 or later. Verified against lua-cli 3.44.0. The complete project is in the examples repository: lua-apps-ops-dashboard. It adds a third route that closes a ticket.

What you’ll build

  • A web app listed on your agent, with a page project in src/apps/ops-dashboard/web.
  • A GET /tickets route and a POST /tickets route with a Zod body schema.
  • A React page that calls both routes with lua.api() and follows the shell’s theme.
  • A local run with hot reload, a route test from the terminal, and a pushed version.
  • An agent version that makes the app live, opened by its URL.
1

Scaffold the app

Run this in the root of your agent project, where lua.skill.yaml is:
Output
The command writes two things. app.ts holds the routes, which run on Lua. web/ is a Vite + React project with the @lua-ai-global/ui components and Tailwind; it runs in the browser. The command also lists the app under webApps on your LuaAgent and keeps the page project out of the agent’s TypeScript build. It never overwrites an existing app.
2

Install the page project

The page project has its own dependencies:
@lua-ai-global/app-client is the page side of the app. It opens the session that the shell hands over, talks to the shell, and calls your routes.
lua-cli 3.42 to 3.45 copy the client into web/src/lua/client.ts instead of adding the package. To switch, install the package as above, change from './lua/client' to from '@lua-ai-global/app-client' in web/src/main.tsx and web/src/App.tsx, and delete web/src/lua/client.ts.
3

Write the routes

Replace src/apps/ops-dashboard/app.ts:
src/apps/ops-dashboard/app.ts
A route key is a method and a path. The handler gets the request already checked against the route’s schemas: a body without a title is answered with 400 before your code runs. auth is the person using the page, never an API key. A handler that returns a value answers 200 with it as JSON; json(value, status) sets another status.Run lua compile --ci. The summary counts the app:
Output
4

Call the routes from the page

Replace src/apps/ops-dashboard/web/src/App.tsx:
src/apps/ops-dashboard/web/src/App.tsx
Then make src/apps/ops-dashboard/web/src/main.tsx start the client and follow the shell’s light or dark theme:
src/apps/ops-dashboard/web/src/main.tsx
lua.api(method, path, body?) calls a route of this app as the signed-in person and resolves the JSON it answers. In web/, npx tsc --noEmit reports no errors.
5

Run it locally

The command compiles the agent, starts the page on the app’s own Vite server with hot reload, and prints its URL. Open it, add a ticket, and it appears in the list. Locally the routes run in the sandbox on your machine as you, against the agent’s live Data, so the ticket is a real entry in the tickets collection. Edit App.tsx and the page reloads; edit app.ts and the routes recompile. Press Ctrl+C to stop.
6

Test a route from the terminal

lua test webapp calls one route the way the platform would, with you as auth:
Each call prints the status, the response headers, and the body. The first answers 201 with the new ticket; the second lists it. A body that fails the schema answers 400. Add --json to get the result as JSON for a script.
7

Push a version

The push builds the page with the app’s own Vite, uploads it with the route bundle, and creates a web-app version. --force picks the next version number without asking. The version is staged, not live: the command ends with Staged but NOT live yet and the two commands of the next step.
8

Go live

A web app has no deploy of its own. It goes live with the agent version that pins it:
lua version create prints ✓ Created v<n> (staged); pass that number to promote. To roll back, promote the previous agent version.
9

Open the app

Open it by URL in your browser. <agentId> is agent.agentId in lua.skill.yaml:
The app opens full page in Lua Workspace, signed in as you, and lists the tickets you added locally. The admin console lists the agent’s apps at https://admin.heylua.ai/admin/agents/<agentId>/apps. Neither has a menu entry for apps yet.
The app opens at its Workspace URL, a ticket you add there shows your name, and lua test webapp --name ops-dashboard --route 'GET /tickets' lists it.

What you learned

  • A web app is part of an agent: defineWebApp in src/apps/<name>/app.ts, listed under webApps. About web apps explains where it opens and who can open it.
  • Routes are typed handlers that run on Lua as the person using the page, with Data and env(). The page calls them with lua.api(). Write routes and pages covers params, query, errors, and limits.
  • lua apps dev and lua test webapp run the routes locally against live data.
  • lua push webapp stages a version; an agent version makes it live, and promoting an earlier one rolls it back.

Next steps

Write routes and pages

The full defineWebApp shape, the page client, and limits.

lua apps reference

lua apps new and lua apps dev.

Store and search data

More on the Data collections your routes use.

lua-apps-ops-dashboard example

The full project, ready to clone and run.