The Portal Builder is the staff side of the client portal: the workbench where you assemble a portal before anyone outside sees it, decide exactly what each client may touch, hand out their access, and watch what comes back. The portal your client logs into is the front of the house; the builder is the back of it. Every choice you make here - what is attached, how much of it shows, who holds a link - is a governed action on the record, and every one of those actions has a matching way to undo it cleanly.
This is where a portal goes from empty to live and, when the work is done, from live to closed without losing a thing. You build it in private, share precisely what you mean to, and keep a kill switch within reach the whole time.
What it does
A portal starts as your staging area and only becomes a window once you decide it should.
-
Build from a pinned template. Create a portal from a template version and that version is snapshotted onto the portal at the moment of creation. A later change to the template never reshapes a portal already in flight, so what you assembled is what your client gets. A new portal starts at access version zero with no links and nothing exposed: it does nothing until you fill it.
-
Curate the exposure allow-list, one object at a time. You attach specific objects - a matter, a form, a file, a request - to the portal, and only an attached object exists for the client. Each attach validates that the row really exists and sits in your workspace before it goes on the list, and the default for a freshly attached object is conservative: full read, no hidden fields, no write-back until you say so. Detaching is a hard deny again, and re-attaching is always available, so the allow-list is a living thing you tighten and loosen as the matter moves.
-
Set how much of each object shows. Visibility is per object, drawn from a small fixed set of states: full, read-only, hidden fields, request-only, or hidden. Write-back is a separate flag layered on top: it is the one setting that lets a client change your data, and it is gated behind its own precondition (it only applies on a full-visibility object), because letting the outside world write into the matter is the most sensitive thing a portal can do.
-
Read back exactly what is exposed. The exposure manifest answers “what, precisely, can this client see” from a single read: every attached object with its visibility, its hidden fields, its write-back flag, and its traversal depth. You never have to reconstruct the client’s view in your head.
-
Generate the forms the portal collects. When the portal needs to gather something, you can have a form schema drafted from a prompt or spec. The draft is never published straight to a client: it lands under a lint-and-refuse-harmful gate first, so a generated form is reviewed before it ever goes out. When a client submits, the answers ingest into the matter and move it forward, and you can read every submission back with its field-level change history and lifecycle status.
-
Hand out access by name, and bind it to one holder. A link is added per named email, which resolves or creates the client as a portal principal and mints them a personal token returned once. A link starts inactive and cannot authenticate until it is claimed: the first claim wins under a serialized first-claim flip, binds the holder, and a second claim is a no-op rather than a double-activate. A forwarded link cannot be hijacked by whoever happens to open it next.
-
See your portals without leaking anyone else’s. The portal list shows your workspace’s portals with their status and their link and object counts (computed, not stored, so they never drift), scoped so you only ever see your own. There is no fleet-wide index to page through.
-
Run the message thread. The portal carries an external-boundary thread you and the client post to (inbound, outbound, or system messages). Attachments on the thread honour each file’s sensitivity and internal flags, so an internal-only file does not slip across the boundary in a message.
-
Close it down at three different grains. A single link can be cut on its own axis, leaving the rest of the portal intact. A whole portal can be suspended to pause access and reactivated later. And there is a scope-wide kill switch that bumps the portal’s access version and invalidates every outstanding link at once - any stale token then fails fast rather than lingering. When the work is finished for good, archiving is the terminal teardown: it withdraws all external access while retaining the record and the audit chain, and refuses to reactivate.
How you use it
The build-to-close flow is the same whether you are onboarding one client or running many in parallel.
Open a new portal. Create it from a template version. It is private until you share it - your staging area, with the branding and layout pinned and nothing yet exposed.
Attach what the client should act on. Add the matter, the forms, the files, and the requests to the allow-list, one object at a time. Anything you do not attach simply does not exist for the client.
Scope each object. For every attached object, set its visibility - full, read-only, hidden fields, request-only, or hidden. Turn write-back on only where you genuinely want the client writing back, and only on a full-visibility object.
Check exactly what is exposed. Read the exposure manifest to confirm the client’s view in one place before any link goes out, then walk the bounded, deny-by-default traversal - the same read path the client uses - as a true preview.
Draft and post the forms. Generate a form schema for what you need to collect, review the draft past the lint gate, and assign it as a client to-do.
Send access by name. Add a recipient by email to mint their personal link. It arrives inactive and binds to the first person who claims it.
Watch it come back. Submissions ingest into the matter and advance it; you read each one back with its changelog and status. Messages and uploads land on the thread and the record. There is no separate inbox to reconcile.
Close it when you need to. Cut a single link, suspend the whole portal, bump the access version to kill every outstanding link in one call, or archive the portal to end all external access for good while keeping the full record.
The kernel verbs behind it
Each goes through the one front door - permission-checked and audited. The builder you use, the link you send, and the read your client performs are all the same governed verbs. These are the ones most specific to building and running a portal.
Building and scoping:
portal.create- instantiate a portal from a template version (defaults plus overrides), pinned at access version zero.portal.get- read one portal and its resolved config; the builder’s render source.portal.list- list your workspace’s portals with status and computed link and object counts.portal.object.attach- add an object to the exposure allow-list; validates it exists and is in-workspace.portal.object.detach- remove an object from the allow-list, making it a hard deny again (reversible re-attach).portal.object.list- read the exposure manifest: every object’s visibility, hidden fields, write-back, and traversal depth.portal.set-visibility- set an object’s visibility, hidden fields, or write-back, across the fixed state set.portal.traverse- the bounded, deny-by-default read that powers your exposure preview and the client’s own walk.
Forms the portal collects:
form.generate- draft a form schema from a prompt or spec, under a lint-and-refuse-harmful gate, never auto-published.form.ingest- fold a completed submission into the matter, extracting uploads to encrypted blobs.submission.get- read one submission: the snapshot body, the field-level changelog, and its lifecycle status.submission.list- list a form’s submissions with their status and aging.
Access and messaging:
portal.link.add- add a named-email access principal and mint its token, returned once.portal.link.activate- the serialized first-claim flip that binds a link to exactly one holder.portal.link.revoke- terminally cut one link on its own axis, independent of the access version.portal.message.post- append a message to the portal thread (inbound, outbound, or system), honouring file sensitivity.
Closing down:
portal.suspend- pause access to a portal, reversibly.portal.reactivate- resume a suspended portal.portal.bump-access-version- the scope-wide kill switch: bump the access version and invalidate every outstanding link at once.portal.archive- terminal teardown: withdraw all external access, retain the record and audit chain, refuse reactivation.
See the full set in the portal verb reference.