Customer portal

Hosted portal.trend.dev surface with magic-link login and multi-dashboard nav.

The customer portal is for embedders whose customers don’t log into a product of yours — no app to embed inside. Your customers visit portal.trend.dev/<your-workspace>/, enter their email, and click a magic link. Once signed in they see a sidebar of dashboards you’ve granted them.

Unlike iframe or web component mode, the portal DOES require pre-registered customers with an email allowlist — there’s no JWT for us to consult, so the registry becomes the authorization source.

Note

The portal shares the same signing keys, chart-query pipeline, and attribute injection as the iframe/component modes. What differs is auth (magic-link session cookie vs JWT bearer) and identity (registry lookup vs claims-as-truth).

Setup

1. Register the customer

In Embed → Customers → Registered, click New customer. Fill in:

  • External ID — your own customer identifier (used in the URL). Kebab-case, e.g. acme-corp.
  • Display name — what shows in the portal chrome.
  • Attributes — the JSON blob referenced from your dataset SQL as {{trend.attributes.*}}.
  • Portal emails — one email per line. Anyone whose email is on this list can log into this customer’s portal.
  • Granted dashboards — check the boxes for every dashboard this customer should see.

Only embed-enabled dashboards appear in the grant picker. Toggle Embed on a dashboard via Dashboards → ⋯ → Embed first.

2. Direct customers to the portal

Send them to https://portal.trend.dev/<your-workspace-slug>/login. Your workspace slug is derived from your workspace name at creation time; see it in Settings → Workspace.

What the customer sees

  1. Enters their email at the login page.
  2. Receives a magic-link email — one-time-use, 15-minute lifetime.
  3. Clicks the link. We set a 7-day HttpOnly cookie and land them at their portal home.
  4. Sidebar shows every dashboard they’ve been granted (embed-enabled only).
  5. Clicking a dashboard renders it in the main area with the customer’s attributes injected — same chart pipeline as iframe mode.
  6. Sign out button in the sidebar footer clears the cookie + revokes the session in our DB.

URL structure

URLWhat it is
portal.trend.dev/<workspace>/loginEmail entry form
portal.trend.dev/<workspace>/login/verify?token=…Magic-link consumption endpoint (customers reach it by clicking the email link)
portal.trend.dev/<workspace>Customer home — redirects to their first granted dashboard
portal.trend.dev/<workspace>/dashboards/<id>Dashboard render

<workspace> is your workspace’s slug. The customer’s identity is carried in the session cookie — never in the URL, so the customer’s external ID stays out of address bars, browser history, and referer headers.

Enumeration safety

The login form returns the same "Check your email" screen regardless of whether the email is registered. If someone enters a random email — or a random workspace slug — we return the same success screen without sending a link. Attackers can’t use timing or response-shape differences to enumerate valid customers.

Rate limits

We cap magic-link sends at 5 per (workspace, email) per hour to prevent mailbox flooding. Beyond that we silently drop; the login form still shows "check your email" so the ratelimit isn’t observable.

Custom domain (fully white-labeled)

Point one of your subdomains at ours and the portal serves entirely from your domain — zero trend branding in your customers’ URL bars, browser history, or referer headers.

Setup

  1. Go to Embed → Portal in your workspace.

  2. Enter your desired subdomain (e.g. analytics.acme.com) and click Save.

  3. Add this CNAME record on your DNS provider:

    TypeNameValue
    CNAMEanalytics.acme.comportal.trend.dev
  4. Wait for DNS to propagate (usually a few minutes, up to 24h with older resolvers).

  5. Back in the app, click Verify DNS. We look up the CNAME server-side and mark the domain live.

Once verified, customers visit analytics.acme.com/login, receive their magic-link email from analytics.acme.com, and see every URL rendered under your host. The portal.trend.dev/<workspace-slug> fallback keeps working for anyone with a bookmark to the old URL.

SSL / HTTPS

If you’re on our hosted infrastructure, HTTPS is automatic — we provision a Let’s Encrypt certificate for your domain the moment verification succeeds. No config on your end.

If you’re self-hosting trend, HTTPS for custom domains is your responsibility (Caddy / cert-manager / nginx-acme-companion all handle this cleanly).

Troubleshooting

If Verify keeps failing:

  • No CNAME record found — DNS hasn’t propagated yet, or you added an A record instead of a CNAME. Run dig +short CNAME analytics.acme.com locally; if it returns nothing, wait a bit longer or fix the DNS entry.
  • CNAME points at the wrong target — the record exists but doesn’t point at portal.trend.dev. Our error message includes the actual target so you can spot typos.
  • Reserved hostname — you tried to use a domain ending in .trend.dev, .vercel.app, or localhost. Pick a different one.
  • Another workspace is already using that domain — one custom domain per workspace, one workspace per custom domain. If this shouldn’t be happening, contact support.

Verification runs a live DNS lookup every time you click. If DNS is slow, wait ~30 seconds between clicks so caches don’t confuse you.

Removing a custom domain

Click the trash icon on the Portal settings page. The domain unbinds instantly — subsequent requests on that host will 404. Customers with the URL should switch back to portal.trend.dev/<your-slug> or a new custom domain.

Branding

Edit portal chrome (what your customers see around their dashboards) from Embed → Portal → Portal branding:

  • Title — the wordmark shown in the sidebar header. Falls back to your workspace name.
  • Logo URL — small icon rendered next to the title. Point at any HTTPS URL to a ~24px square PNG or SVG.
  • Sidebar color — hex color for the sidebar background. Empty = default.
  • “Powered by trend” footer — on by default. Turn off to fully white-label the portal.

Combined with a custom domain, that’s zero trend branding in your customers’ URLs and zero trend branding on the page.