Skip to content
Plain Help Center home
Plain Help Center home

Headless Portal

When building a headless support portal, you will have access to our engineering team, who can advise and help you. These docs are primarily intended to give you a high-level overview of how the process works. They do not aim to be exhaustive or self-serve.

Headless support portals let your customers view, create, and reply to support requests – all from within your product. With Plain, you build the UI and connect it to our powerful GraphQL API. This gives you full flexibility over the experience while retaining structure and visibility in your support operations. A headless support portal gives you:

  • A fully white-labeled experience
    Match your brand, product, and UX exactly – no generic widgets or context switching.

  • One login, total security
    Users access support within your app, using the same credentials – no extra logins or shared inboxes.

  • Smarter support at scale
    With pre-filled metadata (tenants, priorities, labels), you gain structured threads that are easier to triage and report on.

Whether you’re building a customer dashboard, an admin panel, or a custom client experience, a headless portal gives you all the control – with Plain’s API doing the heavy lifting.

Portal Overview.png

How it works:

It’s very helpful to first read our data model documentation to get your bearings.

Details can vary depending on the experience you’d like to offer, but in essence, to build a portal you have four separate pieces of work:

1. Creating a tenant for each of your customers in Plain

When a customer signs up to your product you first have to create a tenant for that customer. You can read more about tenants here, but in essence each tenant in Plain should 1:1 map to your own workspace/team/org concept in your product.

This is an essential first step so that you can make sure that when someone accesses the support portal they only see their team’s support requests. Without this, it would be difficult or impossible to know which support requests belong to the customer’s workspace/team/org.

To create tenants in Plain you can upsert them. After that you can add individual customers to that tenant.

You can, if you are prototyping this, skip this step and instead just fetch support requests for the customer that is logged in. This means that if John and Lucy are both part of the same team in your product, John will only see his own tickets vs also being able to see Lucy’s latest support requests. Depending on your product this might work fine but for most B2B SaaS this is not the ideal experience.

2. Fetching threads for a customer's tenant

Once you have set tenants and threads for all support requests, you can fetch them via our API and show them to the customer.

To display a list of support requests, you will need to fetch the appropriate threads from your workspace. The recommended way to do this is to filter by tenant:

const threads = await plainClient.getThreads({ filters: { tenantIdentifiers: [{ externalId: tenantExternalId }], statuses: [ThreadStatus.Todo, ThreadStatus.Snoozed], }, });

If in Step 1 you opted not to use tenants, you can also filter by customerId to fetch just the threads belonging to a specific customer.

This will give you a list of threads – but not the content. To fetch timeline entries (i.e. the actual messages), use this GraphQL query:

query threadTimeline($threadId: ID!, $first: Int, $after: String, $last: Int, $before: String) { thread(threadId: $threadId) { title description priority status createdAt { __typename iso8601 } customer { fullName } updatedAt { __typename iso8601 } timelineEntries(first: $first, after: $after, last: $last, before: $before) { edges { cursor node { id timestamp { __typename iso8601 } actor { __typename ... on UserActor { user { fullName } } ... on CustomerActor { customer { fullName } } ... on MachineUserActor { machineUser { fullName } } } entry { __typename ... on CustomEntry { title components { __typename ... on ComponentText { text } } } ... on ChatEntry { chatId text } } } } pageInfo { __typename hasPreviousPage hasNextPage startCursor endCursor } } } }

3. Allow customers to submit new threads

To submit new support requests from your portal, you can create a contact form that creates a thread within Plain.

When you create the thread, make sure to pre-fill the customer’s tenant ID. This ensures that the support request will appear correctly in the portal. Learn more on how to create a thread here.

Although a simple form with just a message and a title works, it’s typically much more useful to ask structured questions – e.g. what the request is about, how urgent it is, or the affected product area.

4. Allowing customers to reply within a thread

Replying to threads directly from your support portal is likely the most complex part from a UI perspective – but technically, it’s as simple as calling the replyToThread mutation.

Depending on the channels you support, your UI may vary. For example, if you don't support email communication in your portal, there's no need to allow specifying Cc or Bcc fields.

Example implementation

To show you how this works, we’ve built an example using NextJS that demonstrates how to set up a headless support portal.

👉 Check out the example on GitHub →

Security & architecture

To build a support portal, you will make API calls to Plain’s GraphQL API using an API key. This API key needs fairly broad permissions to be able to read threads and customer details as well as perform other actions such as send emails.

For this reason it’s very, very important to never make API calls to Plain directly from the client. Doing so would expose your API key, which is a serious security risk.

You must always make Plain API calls from an API you control. This allows you to implement access controls so, for example, a customer can only access their own support requests.

If you have leaked an API key by mistake, immediately delete it from your workspace settings and don’t hesitate to reach out to us if we can help with any mitigation/investigations.

Need help?

There’s much more nuance to building a polished support portal – things like rich formatting, attachments, and file uploads. We’re happy to help you plan and scope the work based on your product and tech stack.

If you’re interested in building a headless support portal based on this high-level overview, please reach out to us via Plain or at help@plain.com.

The Headless Portal is available on Grow and Scale plans.