Controllers and views
A controller is server code. A view is the React page that calls it. The pair is typed end to end: the client knows the controller’s method signatures, so a changed parameter breaks the build rather than production.
Controllers
Section titled “Controllers”Subclass PlatformController and add public async methods. Each becomes callable
from a view.
import { PlatformController, sql, tableRef } from "platform:stdlib";import { Issue, User } from "platform:entities";
const i = tableRef(Issue);const u = tableRef(User);
export default class IssueController extends PlatformController { async getIssueDetail({ recordId }: { recordId: string }) { const { rows } = await sql.query<Issue>( `SELECT ${i.issue_id}, ${i.title}, ${u.full_name} FROM ${i} JOIN ${u} ON ${u.user_id} = ${i.assignee_id} WHERE ${i.record_id} = $1`, [recordId], ); return rows[0] ?? null; }}Methods take a single object argument and return a promise. That shape is what makes the generated client type work.
Errors are caught and returned as { error: message } rather than crashing the
page, so a view should handle a result that is not the shape it expected.
A view is a React component. Build a typed client with createController,
parameterized by the controller’s type — imported with import type, so no
server code ships to the browser.
import React, { useEffect, useState } from "react@19.2.5";import { Stack, Typography } from "@mui/material@7.3.10";import { createController } from "platform:stdlib";import type IssueController from "../controllers/IssueController.ts";
const controller = createController<IssueController>();
export default function IssueView({ recordId }: CustomViewProps) { const [issue, setIssue] = useState<Awaited< ReturnType<IssueController["getIssueDetail"]> > | null>(null);
useEffect(() => { controller.getIssueDetail({ recordId }).then(setIssue); }, [recordId]);
if (!issue) return <Typography>Loading…</Typography>;
return ( <Stack spacing={2}> <Typography variant="h5">{issue.title}</Typography> </Stack> );}Imports carry a version (react@19.2.5, @mui/material@7.3.10) so a page pins
what it was written against and upgrades on your schedule.
ControllerClient<T> is the underlying type: it maps the controller’s methods,
dropping run, and preserves each signature.
Where they live
Section titled “Where they live”guest_files/ controllers/ IssueController.ts views/ issueView.tsxDeploy with polymesa deploy ./config, then attach the view to an entity as its
view or create page. See Pages.