Skip to content

SDK reference ​

An OpenAct app uses two packages. Both ship as TypeScript source in the OpenAct repository, so your editor shows the full types.

PackageWhere it runsImport from
@openact/coreThe Cloudflare Worker@openact/core/server for server code, @openact/core/shared for types and rules that both sides use
@openact/uiThe web app@openact/ui for the shell and helpers, @openact/ui/components/<name> for shadcn/ui components

Apps import only from these entry points, never from files inside the packages. Read Build an app for a walkthrough and Permissions for access.

The app manifest ​

Every app has a package.json with an openact field. The openact script reads it.

FieldRequiredWhat it does
idYes2 to 31 lowercase letters, numbers or dashes, starting with a letter. Unique in a deploy. Not one of the reserved names.
nameNoThe name in the app rail and Company settings. The default is the id.
serverNoThe file that exports defineServerApp(...) as default
clientNoThe file that exports defineClientApp(...) as default
migrationsNoA folder of numbered SQL files. The default is ./migrations.
durableObjectsNoDurable Object classes that the server file exports
enabledByDefaultNofalse makes each company turn the app on first. The default is true.

Server: @openact/core/server ​

defineServerApp(app) ​

FieldTypeWhat it does
idstringThe same id as the manifest
namestringThe app name
permissionsRecord<string, PermissionDef>What people can do, by company role. See Permissions.
routes(api: Hono<AppEnv>) => voidAdds the app's API routes. Use /api/apps/<id>/....
middlewareMiddlewareHandler<AppEnv>[]Runs on every API request, after the session is read
scheduled(env, cron) => Promise<void>Runs on each cron trigger. DAILY_CRON is the daily schedule.
hooksAppHooksJoins the core's sign-in and company events. See below.

Hooks ​

HookWhen it runsWhat your app does
allowSignup(env, email)Sign-up is limited, and this email asks for a codeReturn true to let the email sign up, for example for your own invitation
signedIn(env, user)After each sign-inAccept your app's invitations
companyCreated(env, companyId, userId)A company was createdSet up defaults
companyDetail(env, companyId)GET /api/companies/:idReturn extra fields, such as your company settings
companyUpdate(env, companyId, body)PATCH /api/companies/:idSave your fields. Return true when you changed something.
companyMemberRemoved(env, companyId, userId)A person leaves or is removedRemove their access to your data in that company
companyDeleting(env, companyId)Before a company is deletedDelete your data and files for that company
userDeleting(env, userId)A person deletes their account, after they leave each companyDelete personal data that is not in a company, such as a profile or settings

Access ​

HelperWhat it does
requireUser(c)The signed-in person. Throws 401 when nobody is signed in.
requirePermission(env, companyId, userId, permission)Checks the company, the app switch and the role. Returns the company role.
hasPermission(env, companyId, userId, permission)The same check, as true or false
requireAppOn(env, companyId, appId)Throws 403 app_off when the app is off in the company or for the whole site
siteAppOff(appId)true when a super admin turned the app off for the whole site. Call loadSite(env) first in routes outside /api/.
loadSite(env)Loads the site settings, such as the site app switches. /api/ routes and the cron load them for you.
companyAppState(env, companyId)enabled(appId) and roles(permission) for one company, from one query
appOffError(appId)The app_off error, for your own access checks
companyRole(env, companyId, userId)The person's company role, or null
requireCompanyRole(env, companyId, userId, roles)Throws unless the person has one of the roles. Prefer requirePermission() in apps.
currentCompanyId(env, userId)The company the person works in now
listCompanies(env, userId)The companies the person is in
getCatalog()Every installed app, with id, name and enabledByDefault
isSuperAdmin(env, user)true when the person's email is in SUPER_ADMIN_EMAILS
requireSuperAdmin(c)The signed-in super admin. Throws 403 not_super_admin for anyone else.

Input and errors ​

HelperWhat it does
readJson(c)Reads a JSON object from the request body. Throws 400 for anything else.
text(value, field, { max, required })A trimmed string, with a length check
oneOf(value, field, allowed)One of the allowed strings
num(value, field), dateOrNull(value, field)A number, or a YYYY-MM-DD date or null
normalizeEmail(value), isEmail(email)Email helpers
HttpError(status, message, code?)Throw it to send { "error": message, "code": code }. Write a message that the person can act on.
newId(), nowIso(), todayIso(), addDays(iso, days)Ids and dates
sha256Hex(text), randomToken()Hashes and tokens. Store only hashes of secrets.
authSecret(env, url)The server secret (AUTH_SECRET). Localhost runs without it.
sealSecret(secret, purpose, ownerId, value), openSecret(...)Encrypts a short secret, such as a share token, so your app can show it again. Each purpose gets its own key, and ownerId is bound to the result. Store a hash next to it for lookups.

Live updates ​

HelperWhat it does
registerRoomKind(kind, readRevisions)Call once. readRevisions(env, ids) returns the newest revision of each item.
markChanged(kind, id)Marks an item as changed in this request. Open pages reload it.
flushChanges(env)Sends the changes now, for work after a streamed response starts
assertLiveRequest(c)Checks the WebSocket upgrade and the Origin header. Call it before your access check.
acceptLive(c, kind, id, viewer, rev)Accepts the WebSocket after your access check
disconnectFromRoom(env, kind, id, userId)Closes a person's open pages, for example after they lose access

Live messages carry only revision numbers and viewer names. Pages load the data through your API, which checks access.

Email and files ​

HelperWhat it does
sendEmail(env, message)Sends with Resend, or prints to the log when no key is set
emailLayout(env, heading, html), emailButton(url, label)The standard email look
deletePrefix(bucket, prefix)Deletes every R2 file under a prefix. Use <app id>/... keys.

Client: @openact/ui ​

defineClientApp(app) ​

FieldWhat it does
id, nameThe same id and name as the manifest
iconThe app's icon in the app rail, as a Lucide name for Icon, such as "users"
MarkA custom rail icon, such as a brand mark. Draw it with currentColor.
homeWhere the rail icon goes. Default: / for the first app with a home page, otherwise /a/<id>
ownsPath(path)Which paths belong to the app. Default: the home, /a/<id>/... and the app's routes
routesPages: { pattern: RegExp, render: (match) => ReactNode }. Start each page with SiteHeader and PageBody.
HomeThe page at /. The first app with a home page that is on owns it.
publicRoutesPages that work without sign-in, by path
navItems at the top of the app's panel (main), or icons at the bottom of the rail (footer)
SidebarSectionGroups in the app's panel, such as Pinned, Projects and Recent
commandsDialogs: { id, label, icon, shortcut?, inSidebar?, Dialog }. shortcut: "k" opens with Ctrl+K or Cmd+K.
companySettingsTabsExtra tabs in Company settings: { id, label, Tab }
ProviderState around the whole signed-in shell

An app shows in the rail only when it has an icon or a Mark, and only in companies where it is on.

Hooks ​

HookWhat it returns
useShell()me, config, apps, open(dialog) and runCommand(id)
useWorkspace()companies, current, switchTo(id), reloadCompanies(), access and reloadAccess()
useCan(permission)true when the person has the permission in the current company
useAppOn(appId)true when the app is on in the current company
usePath()The current path
useLive(path, enabled, onRev)Opens a live room. Returns connected and viewers.
useToast()Shows a short message: toast("Saved.")
useCloseOnPhone()Closes the side panel on a phone after a person picks an item

Helpers and components ​

ExportWhat it does
api(path, { method, body })Calls the API with the session cookie. Throws ApiError with the server's message.
errorText(error)A message to show for any error
navigate(to, replace?), LinkMoves between pages without a reload
SiteHeader, PageBodyThe page header and the padded page body
Icon, Avatar, CompanyMark, LogoIcons and marks
Modal, Field, Notice, ConfirmButton, LinkedTextCommon building blocks
getTheme(), setTheme(pref)Light, dark or system
@openact/ui/components/<name>shadcn/ui components: button, card, dialog, dropdown-menu, sidebar, table, tabs, checkbox and more

Use theme token classes, such as bg-muted and text-muted-foreground. Do not use fixed colors.

Shared: @openact/core/shared ​

ExportWhat it does
COMPANY_ROLES, CompanyRole, COMPANY_ROLE_LABELSowner, admin, member and guest
User, Company, CompanyDetail, AppConfigThe core types
PermissionDef, CompanyAccess, CompanyAppSettingsPermission types
rolesWithPermission(def, chosen)The roles that have a permission, with the fixed rules applied
canManageCompany(role)true for owners and admins

Apache License 2.0. Made by MarketGrowth.