Customers email an account manager to ask for an invoice, a usage figure, or who has access. Here is the model behind a portal that answers those, the prompts to build it, and what it takes to run it.
Your private status link is on its way to your inbox.
A B2B customer portal is four parts: an organisation model where users belong to a company, a tenant boundary enforced in one place, self-service for what customers currently email about, and customer-side admin roles. The one decision that cannot be retrofitted cheaply is the tenant boundary. Every query must be scoped by organisation from the first commit, because adding that later means auditing every query you have written.
Customer portal
288 of 300
Seats in use
4
Open requests
14 Nov
Renewal date
2
Invitations pending
Halden Foods
What this organisation sees when they sign in
Their own recent activity
You are building or specifying a portal your customers will sign into. What you want is the multi-tenant structure right at the start, and the self-service scope aimed at the questions your team currently answers by hand.
What a customer does when they sign in, and what has to be true underneath for that to be safe.
Partners, contractors and acquisitions all produce that case, and a single organisation column on the user record is expensive to unpick later.
Denying by default, so a query with no explicit scope returns nothing rather than everything.
This is the one build where they are not optional.
Invoices, usage against entitlement, who has access, documents, and open requests. In most B2B businesses that is what your team answers by email.
Inviting, removing and setting roles, which removes a large share of the inbound the portal exists to reduce.
A read-only, time-limited, visibly indicated and audited view-as, using a separately authenticated staff role.
A customer portal is not a logged-in version of your website. It is four parts, and the second is the one you cannot add later cheaply.
Organisations as first-class records, users belonging to them through a membership with a role, and support for a user belonging to more than one. Partners, contractors and acquisitions all produce that case.
Every query scoped by organisation in a single layer, denying by default. This is the security property the whole portal rests on and it is not something to reimplement per page.
The things customers currently email about: invoices and payment history, usage against entitlement, who at their company has access, documents, and the status of open requests.
An administrator at the customer who can invite and remove their own users, set roles, and see the audit of who did what. This removes a large share of the inbound the portal exists to reduce.
These prompts start from a Helix project, which is what handles sign-in, credentials and hosting. Start with Helix
Paste these into Claude Code, Codex or Cursor in order. Each one leaves you with something that runs, so you can stop after any step.
The one decision here that cannot be retrofitted cheaply.
In this Helix project, build me a B2B customer portal. Before any feature,
get the tenant model right, because this is the part that cannot be added
later without auditing everything.
Organisations are first-class records: name, status, plan, account
manager, created date, and a reference to our CRM or billing record.
Users have no organisation column. Instead there is a membership record
linking a user to an organisation with a role, a status, who invited them
and when. A user can belong to more than one organisation. Partners,
contractors and customers who acquire each other all produce that case.
Add an active organisation in the session, with a switcher for people who
belong to several.
Then build one data access layer that every query goes through. It
requires an organisation id and denies by default: a query with no explicit
scope returns nothing, not everything.
Now write tests for that boundary before writing a single feature. For
every table, assert that a user in organisation A cannot read, update or
delete a record belonging to organisation B, whether by id, by search, or
through any list endpoint.
I sign in through Helix, so do not build a login page. Read the signed-in
user from the runtime. And do not put any key or connection string in the
code: if this needs to reach another system, tell me and I will create the
connection in my workspace. Worth knowing. Boundary tests written first are the cheapest insurance in this build. Written afterwards they are an audit of everything you already shipped.
In the order those emails arrive.
Now the customer-facing features, in this order, because it is roughly the
order your team currently answers them by hand.
Account overview: plan, contract dates, renewal date, primary contact, and
the account manager with a way to reach them.
Billing: invoices with status and amount, downloadable, plus payment
history. This is the single most requested item in most portals.
Usage against entitlement: what they have used this period against what
they are entitled to, with history. Put the definition of the metric next
to it. A usage number people do not understand generates more enquiries
than it prevents.
Users: everyone at their organisation with access, their role, and their
last sign-in.
Documents: contracts, order forms, security documentation, anything we
currently email on request.
Requests: any open support ticket or service request with its status.
For each of these, if we do not have a reliable source for the data, say
so rather than showing an approximation. A wrong invoice figure costs more
trust than a missing feature. Customer-side administration, carefully.
Define roles within an organisation in a config file: at minimum
administrator, member and billing contact, each with a description of what
it can see and do.
An administrator at the customer can invite users by email, set and change
roles, deactivate people, and see pending invitations. Taking the account
manager out of that loop removes a large share of the inbound this portal
exists to reduce.
Invitations expire, and re-inviting issues a new one rather than extending
the old.
Give their administrator an organisation-level audit: who signed in, who
was invited or removed, who changed a role, who downloaded which document.
Support optional domain-based joining, off by default, where anyone with
an email at a verified domain can request access and an administrator
approves. Verify the domain properly and never enable it for a free-mail
domain.
And give users notification preferences, because a portal that emails too
much gets filtered and then nobody sees the important one. Worth knowing. Domain-based joining is convenient and dangerous. Off by default, verified properly, never for a free-mail domain.
Staff access, and the shortcut to never take.
Add a staff role that exists outside the organisation model and is
authenticated separately from customer users.
Never grant a staff member a membership in a customer organisation to give
them access. It works immediately, and it destroys the meaning of both the
boundary and the audit log.
Instead build view-as for support: strictly read-only, time limited,
clearly indicated in the interface throughout, and audited on entry and
exit with the reason recorded.
Give staff a view of any organisation showing the same data the customer
sees, so a support call is about the same screen.
Add health signals for the account team: last sign-in by anyone at the
organisation, active users against licensed, and features never used. An
organisation where nobody has signed in for sixty days is worth knowing
about before the renewal.
And rate limit every customer-facing endpoint. This is an internet-facing
app holding one customer's data next to another's. The step that turns a working prototype into something with your data in it.
Now connect it to the systems we actually run, so it works on real data
rather than the rows you seeded.
Anything in angle brackets is a placeholder. Swap it for whatever we use
instead, and have the app name the connection it wants rather than assume
a vendor, so changing my mind later is a config change and not a rewrite.
<Stripe>. Read invoices, payment history and plan, which is the single
most requested thing in any portal.
<Okta>. Authenticate customer users and staff separately, and support a
customer bringing their own.
<Amplitude>. Show usage against entitlement, with the metric definition
beside it.
<Zendesk>. Show the status of their open requests, so they stop emailing
to ask.
<Salesforce>. Read the account, contract dates and account manager, so the
portal shows the same thing your team sees.
<Google Drive>. Serve contracts, order forms and security documentation
that you currently email on request.
<Snowflake>. Read the usage totals behind the entitlement view, aggregated
once a day rather than counted live on every page load.
Only write where I have said to write. Everything else is read only.
Do not write a key, a token or a connection string anywhere in the code,
and do not ask me to paste one. Tell me which connections you need and I
will create each as an authentication in my Helix workspace. Reference
them by alias. Worth knowing. Every name in angle brackets is a placeholder for whatever you run. Helix holds the credential as a workspace authentication, so the app references an alias and never the secret itself.
The last step of every build: a URL, and the right people on it.
Deploy my app. Worth knowing. Your assistant runs helix deploy underneath and the app comes back as a URL. This one is internet facing with customer and staff users, so set Access Control before sharing, and expect enterprise customers to ask to bring their own identity provider.
Everything a customer sees here is assembled from systems they have no access to, which is most of the work.
Stripe
Read invoices, payment history and plan, which is the single most requested thing in any portal.
Okta
Authenticate customer users and staff separately, and support a customer bringing their own.
Amplitude
Show usage against entitlement, with the metric definition beside it.
Zendesk
Show the status of their open requests, so they stop emailing to ask.
Salesforce
Read the account, contract dates and account manager, so the portal shows the same thing your team sees.
Google Drive
Serve contracts, order forms and security documentation that you currently email on request.
Snowflake
Read the usage totals behind the entitlement view, aggregated once a day rather than counted live on every page load.
Each line says whether the app reads, writes or both. Anything not described as writing should be read only.
Named systems are the ones most teams actually run, not a list of the only ones that work. Swap in whatever you use.
Each connection is an authentication in your Helix workspace, referenced by alias. The app names the connection it wants and never holds the credential, so nothing here ends up in your code and any of it can be rotated or revoked without a redeploy.
The build gets you a working portal. It is internet facing, it holds one customer's data next to another's, and it will be reviewed by your customers' own security teams.
AI Deployment
One command takes the app from your assistant to a live address, so the people who need it get a link rather than instructions for running it locally.
App Security
Customer users and staff need separate authentication paths, and enterprise customers will ask to bring their own identity provider. Handling sign-in outside your application code is what makes that a configuration rather than a project.
App Security
Reading billing, usage and contract data from your internal systems needs real access. Granting it to the app rather than embedding keys means credentials can be rotated or revoked without a redeploy.
App Registry
Every app carries a named owner and an entry IT can see, so the tool does not become nobody's problem when the person who built it changes role.
Cost Management
Per-app spend visibility, with budgets and caps, so an internal tool cannot quietly become a line item nobody can explain.
A signed-in area where customers see their own account: invoices, usage against entitlement, who at their company has access, documents, and open requests. It differs from a consumer login in that users belong to an organisation rather than existing alone.
Enforcing the tenant boundary in a single data access layer that denies by default, from the first commit. Adding it later means auditing every query already written, and a single missed one exposes one customer's data to another.
Yes, and model it from day one. Partners, contractors, consultants and customers who acquire each other all produce that case, and a single organisation column on the user record is expensive to unpick once there is data.
Whatever your team currently answers by email. In most B2B businesses that is invoices, usage figures, who has access, and the status of open requests, roughly in that order.
Through a view-as capability that is read-only, time limited, visibly indicated and audited, using a staff role authenticated separately. Adding a staff member to a customer organisation works immediately and destroys the meaning of both the boundary and the audit log.
The prompts here are written for Claude Code, Codex or Cursor and will get you a working portal. This is the one build on this site where the boundary tests are not optional, so write them first and read the results yourself.
IT and Operations
Request access against a defined catalogue, route it to the system owner, grant it with an expiry, and produce the evidence an access review needs. Prompts to build it, and what it takes to run it.
IT and Operations
Let support answer account questions and fix common problems themselves, through named actions against production data, with every action logged. Prompts to build it, and what it takes to run it.
Last reviewed September 2026.
Helix is the governed runtime for AI-built apps. Deploy what you build, put SSO in front of it, connect it to your systems with managed credentials, and give it a named owner.
Your private status link is on its way to your inbox.