ObjectStackObjectStack

Setup App

The platform's built-in administration UI — what it is, what it shows, and how it is wired

Setup App

The Setup App (/apps/setup) is the built-in administration console for every ObjectStack project. It surfaces the platform sys_* objects, the System Overview dashboard, and a set of settings pages behind a left-hand navigation tree.

Under ADR-0029 D7 the app is not a fixed navigation tree. SETUP_APP ships as a thin shell of stable, empty navigation group anchors ("slots"); the actual menu entries are contributed at runtime by the packages that own the underlying objects, via navigationContributions. The runtime merges every contribution into the app's navigation tree by group id + priority on read, so the rendered menu reflects exactly which capability plugins are loaded — a disabled capability contributes nothing and its slot stays empty.

The app itself ships from the dedicated @objectstack/setup package (package id com.objectstack.setup), which registers it at runtime. Per ADR-0048 (one app per package), /apps/setup — resolvable as /apps/<packageId> — maps to exactly this app.

Where it lives

FilePurpose
packages/platform-objects/src/apps/setup.app.tsThe App definition — the navigation shell (group anchors), branding, required permissions
packages/platform-objects/src/apps/setup-nav.contributions.tsSETUP_NAV_CONTRIBUTIONS — the nav entries owned by @objectstack/platform-objects, merged into the shell at runtime
packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts"Overview → System Overview" dashboard
packages/apps/setup/src/index.ts@objectstack/setup package — SetupAppPlugin.start() performs the runtime registration via manifest.register({ apps: [SETUP_APP], navigationContributions: SETUP_NAV_CONTRIBUTIONS })

The requiredPermissions: ['setup.access'] permission declared on the Setup App is contributed by plugin-security (in its default permission sets), not by the package that registers the app. Other capability plugins (e.g. plugin-audit, plugin-webhooks, plugin-approvals) contribute their own Setup nav entries into the shared group anchors.

setup.app.ts defines the stable group anchors (the shell); the entries under each group are merged in from the contributing packages. The group anchors are:

Group (anchor id)Filled by
Overview (group_overview)System Overview dashboard — platform-objects
Apps (group_apps)Packages — platform-objects; marketplace — cloud-connection (capability plugin)
People & Organization (group_people_org)Users · Organization · Business Units · Teams · Organizations · Invitations — platform-objects
Access Control (group_access_control)Positions / Permission Sets — plugin-security; Sharing Rules / Record Shares — plugin-sharing; API Keys — platform-objects
Approvals (group_approvals)Approvals Inbox (the approvals:inbox component) · Requests · Action History · Delegations (OOO) — plugin-approvals
Configuration (group_configuration)All Settings · Localization · Company · Branding · Authentication · Email · File Storage · AI & Embedder · Knowledge · Feature Flags — platform-objects
Diagnostics (group_diagnostics)Sessions · Notification Events — platform-objects; Audit Logs — plugin-audit
Integrations (group_integrations)plugin-webhooks
Advanced (group_advanced)OAuth Applications · Identity Links · User Preferences — platform-objects

The exact rendered menu depends on which capability plugins are loaded. A few notable entries:

  • Sessions (sys_session) — better-auth session records, contributed into group_diagnostics.
  • OAuth Applications (sys_oauth_application) — third-party OAuth client registrations, contributed into group_advanced.
  • Signing Keys (JWKS) (sys_jwks) — not in the nav (#7544). The object is the environment's JWT signing-key store (private_key) and declares enable.apiEnabled: false / apiMethods: [], so the generic data API answers OBJECT_API_DISABLED (404) on every list — for every persona, platform admin included, since that gate reads only the object's enable block and takes no user or permissions. The entry that used to be here carried a manage_platform_settings gate, which could not prune it: a permission gate and an API-disabled object are independent conditions. (sys_verification and sys_device_code are likewise deliberately absent: sensitive, ephemeral secrets — not browsable, and also private. All three are pinned in setup-nav-dead-key-tombstone.test.ts.)
  • Audit Logs (sys_audit_log) — contributed by plugin-audit into group_diagnostics. (The sys_activity and sys_comment objects also live in plugin-audit, but they are not contributed as Setup nav entries.)

Why a shell + contributions

The Setup App is a shell of empty group anchors rather than a fixed navigation tree because the objects it surfaces are owned by different capability plugins (auth / security / sharing / audit / webhooks / approvals). Letting each package contribute its own menu entries keeps the menu for an object shipping with the package that owns the object — the UI-layer analog of object extend. Benefits:

  • The rendered menu always matches the loaded capabilities: a disabled plugin contributes nothing and its slot stays empty.
  • No code in setup.app.ts needs to know about objects that live in other packages; entries are merged by group id + priority on read.
  • The shell can still be type-checked against App from @objectstack/spec/ui, and contributions against NavigationContribution.

Customising it

You do not edit setup.app.ts to add entries — it only holds the empty group anchors. To add a new entry:

  1. Register the object/dashboard in the package that owns it (or in a project-local plugin if it is project-specific).
  2. Add a NavigationContribution targeting the relevant group id (app: 'setup', group: 'group_*') — either in SETUP_NAV_CONTRIBUTIONS for @objectstack/platform-objects-owned objects, or in the navigationContributions of the plugin that owns the object.
  3. If the entry needs its own permission, declare it where the relevant permissions live (e.g. setup.access is granted in plugin-security's default permission sets) so the requiredPermissions check picks it up.

For project-local Setup-style entries, define a separate App in your project — Setup is reserved for platform-level administration.

On this page