Skip to content

Window Actions

A WindowAction is what a menu item, a stat button or a link points at. It answers one question — what happens when someone opens this? — and its type field is the answer.

- data_type: WindowAction
name: Products
identifier: sellable_products_action
model: Product
modes: Kanban, List, Form
default_view: product_kanban_view
search_view: product_search_view
views:
- - set
- - product_kanban_view
- product_list_view
- product_form_view
typeWhat rendersKey field
Window (default)The action’s views of its model, inside the app shellviews, modes
ClientA compiled screen the frontend ships, inside the app shellclient_action
RouteNothing — it navigates, leaving the shellroute

Window is the overwhelming majority and needs no type: line. Client names a bespoke screen the platform already ships (you cannot add one from a module — see Views Overview).

Route — a surface that is not a view of the shell

Section titled “Route — a surface that is not a view of the shell”

A Route action does not render anything itself. It sends the browser to route and stops.

- data_type: WindowAction
name: Shop Floor
identifier: mrp_shopfloor_action
model: Shopfloor # still required — it decides who sees the menu
type: Route
route: /app/shopfloor
action_ctx:
title: Shop Floor

When the thing you are opening is not a view of the back office — an operator kiosk (a till, a shop-floor tablet, a warehouse handset), a standalone tool, or an external system.

The distinction is not cosmetic. A screen rendered as a Client action is drawn over a live app shell, which is still mounted underneath: it has already fetched the back office’s navigation, chat presence, activity feed and notification subscriptions before the kiosk booted, and it keeps polling them for the rest of the shift on a screen nobody can see them on. On a device whose whole point is to keep working with a bad connection, that is a real cost. A Route leaves the shell instead of covering it.

If your screen is a view of the back office — it wants the navigation, the app switcher and the record chrome around it — it is a Window action, not a route.

Exactly two shapes, and the model refuses anything else at save:

ValueBehaviour
/app/shopfloorAn in-app path. Navigates without a page load.
https://status.example.comAn absolute address. Leaves the application.

A value that is neither — example.com, say — is rejected with a message naming it, because it would otherwise be read as a relative path and quietly land the user on something like /app/example.com: no error, no console message, just the wrong page. A Route with no route at all is refused for the same reason — the menu item would do nothing whatsoever. javascript: targets are refused outright.

For an absolute http(s):// address, the action’s existing window field decides where it opens — the same choice that field governs everywhere else:

windowResult
New WindowOpens in a new browser tab
Current Window (default)Replaces the current page

An in-app path always navigates in place; window does not apply to it.

model is still required — and still does something

Section titled “model is still required — and still does something”

A Route action renders no records, but model is not ceremony: menu visibility is derived from the acting user’s access to the model its action opens (see Model Access). Point it at the model the surface actually works with — the shop-floor action names Shopfloor — and users without access to that model do not see the menu. Adding groups: to the action narrows it further.

A Route also does not grant anything. The destination enforces its own authentication and access exactly as it would if the user typed the address; an unauthenticated visitor is sent to the login page and returned to the route afterwards.

route wins over client_action when both are set — which is what lets an existing client action be converted to a route by adding type: Route and route: without stripping the old value out of every database that already has it.