Help Center
Run a public knowledge base and ticket portal that reads all of its content and configuration from erxes at request time.
For the domain modules behind it, see Frontline. For the portal credential it uses, see Client Portal.
The app
apps/help-center is a standalone Next.js 16.3 (React 19, Tailwind v4) portal (not an Nx project) with Apollo Client 4 for GraphQL. npm run dev serves it on port 3900.
It renders a support-portal landing page (ticket actions, announcements, knowledge base), article browsing and search, ticket submission/tracking, and portal-user sign-in. Every piece of content is read live from the gateway:
| Area | GraphQL source |
|---|---|
| Portal identity, toggles, theme, header/footer | helpCenterGetConfigByDomain (Frontline helpcenter module, no login required) |
| Knowledge base | cpKnowledgeBaseTopicDetail (Frontline knowledgebase) |
| Announcements | cpPostList / cpPost (CMS, portal scope) |
| Tickets | cpCreateTicket, cpGetTickets, cpGetTicket, cpTicketGetNotes, cpTicketCreateNote (Frontline tickets) |
| Forms | cpForms, cpFormDetail (Frontline forms) |
| Visitor session | clientPortalUserRegister, clientPortalUserLoginWithCredentials, clientPortalLogout |
One variable, config-driven
The only setting the app itself needs is NEXT_PUBLIC_ERXES_API_URL, the gateway base URL with no trailing slash. Everything else (portal name, app token, knowledge-base topic, ticket pipeline, labels, colors) comes from a help center record the backend looks up by the host each request arrives on.
Create the record under Frontline → Help Center and set its Website to the address the portal is served from (http://localhost:3900 for local work). The Website field is a Client Portal picker: choosing a portal also stores that portal's token on the config as erxesAppToken, which the app then sends as x-app-token for the cp* queries. Until a help center claims the request host, every page renders a "no help center configured" message naming the domain it looked for. The config's editable header fields (wordmark, section labels, search placeholder) and footer fields (logo, description, copyright, link columns) are exposed in the helpCenterGetConfigByDomain payload and edited from the Help Center drawer in Frontline UI. See Knowledge Base, Help Center & Reports.
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_ERXES_API_URL | Gateway URL, e.g. https://officenext.erxes.io/gateway |
NEXT_PUBLIC_APP_VERSION | OS (default) or SAAS. On SAAS, a <subdomain> placeholder in the API URL is filled from the request host; one container serves a help center per tenant |
NEXT_PUBLIC_ERXES_API_URL=https://officenext.erxes.io/gateway
NEXT_PUBLIC_APP_VERSION=OS
Runtime configuration and Docker
NEXT_PUBLIC_* values are inlined into the client bundle at next build, which would tie an image to one gateway. To avoid that, docker-entrypoint.sh writes public/js/env.js from the container environment before the server starts, the root layout loads that file first, and modules/apollo/utils/env.ts reads window.env at run time. next dev/next start read .env.local the usual way.
Build from the repository root (the app compiles form controls from the erxes-ui source tree):
docker build -f apps/help-center/Dockerfile -t erxes/help-center .
docker run -p 3900:3900 \
-e NEXT_PUBLIC_ERXES_API_URL=https://officenext.erxes.io/gateway \
erxes/help-center
The image uses Next's standalone output; PORT (default 3900) and HOSTNAME (default 0.0.0.0) are read at start. Behind a proxy, forward the original host; that is what the help center lookup matches on:
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
On a SaaS install, combine NEXT_PUBLIC_APP_VERSION=SAAS with a placeholder gateway like https://<subdomain>.api.erxes.io/gateway; a request to acme.help.example.com then talks to acme's gateway.
Routes
| Route | Backing data |
|---|---|
/ | Portal landing: ticket actions, announcements, KB sections |
/knowledge-base, /knowledge-base/category/[id], /knowledge-base/article/[id] | cpKnowledgeBaseTopicDetail |
/search?q= | KB articles + CMS announcements, labelled by type |
/tickets, /tickets/new, /tickets/track, /tickets/[id] | cpGetTickets, cpCreateTicket, cpGetTicket, cpTicketGetNotes |
/announcements, /announcements/[slug] | cpPostList, cpPost |
/account, /sign-in, /sign-up | clientPortalUser* mutations |
The whole app is rendered per request. app/layout.tsx exports dynamic = 'force-dynamic', so config, KB content, and announcements are fetched fresh on every hit rather than statically cached. When the app runs against an older gateway, modules/config/api.ts retries the config query without the header/footer block and then with a domain argument, so a newer portal image still works against older gateways (query).
Check it works
- Point the app at your gateway, open
/, and confirm the portal name and sections come from your help center record, not from env. - Change the help center's Website to a different domain and confirm requests on that host find the other config.
- Submit a ticket (
/tickets/new) and confirm it lands in the configured pipeline; track it from/tickets/trackand as a signed-in portal user from/tickets. - In the container, unset
NEXT_PUBLIC_ERXES_API_URLand confirm pages fail fast rather than silently hitting a stale build-time URL.
Troubleshooting
- "No help center" page: the request host does not match any help center's Website field, or the proxy dropped
X-Forwarded-Host. - KB renders but tickets/announcements 401: the config's
erxesAppTokenis stale; re-pick the portal in the Website field to re-store a current token. - Wrong tenant's content on SaaS:
NEXT_PUBLIC_APP_VERSIONis notSAAS, or the API URL lacks the<subdomain>placeholder. - Config errors after upgrading the image only: the gateway predates the header/footer fields; the app retries automatically, so upgrade the backend rather than pinning the image.