Create a custom view with Claude
A custom view is a dashboard authored in Paid using natural language with Claude, instead of writing SQL or HTML by hand. There are two kinds:
- Per-customer view: scoped to one customer at a time. Once published, you can embed it in your own app for that customer to see.
- Organization-level view: not scoped to any single customer. It’s for your own team’s internal use, viewed on the Paid platform only, and is never embeddable. It has no publish step: it’s usable by your team as soon as it’s saved.
Both kinds are created the same way: you describe what you want to Claude, which writes the queries and the dashboard’s code for you over the Paid MCP server, saves the view to Paid, and lets you preview it before publishing.
Each view is made of one or more named SQL queries over your analytics data, plus the code that displays them: a self-contained package of HTML, CSS, and JavaScript that Paid renders as the actual interactive dashboard. You never write this code yourself. Claude generates it, and Paid renders exactly what Claude generated, both in preview and once published.
Custom views are experimental. Routes, token claims, MCP tools, and the SDK surface may change without notice and are not covered by the v2 backwards-compatibility guarantees.
Step 1: Connect Paid to Claude
Custom-view authoring runs through the Paid MCP server. If you haven’t already, connect it to your MCP client.
For Claude Code:
For Claude Desktop, Cursor, Windsurf, and other clients, see the MCP quickstart for the exact configuration. Your API key authenticates and scopes every tool call to your organization.
Step 2: Ask Claude to build a dashboard
Describe what you want in plain language. We recommend starting the request with “Use Paid MCP” or “Using Paid” to improve accuracy. You can also mention the kind of view you want directly (per customer or organization level). For example:
Using Paid, build a per-customer view that shows each customer’s monthly signal volume, total delivered value, and a usage trend over the last three months. Make it interactive with a daily/cumulative toggle.
Behind the scenes, Claude follows a fixed authoring flow:
- Loads Paid’s authoring rules: the requirements every query and every dashboard must meet, plus a starting template to build from.
- Explores your analytics data to find the right tables and columns.
- Writes one or more named SQL queries, plus the code that visualizes them.
- Previews the exact dashboard code it’s about to save, rendered the way Paid does, so what you preview and what gets saved never drift apart.
You can iterate conversationally: ask Claude to add a chart, change a metric, or adjust the layout, and it updates the dashboard.
Once you’re happy with it, ask Claude to save the dashboard to Paid. Claude shares a preview link so you can open it directly, and every view gets a stable displayId (e.g. view_5oi3qvfaFLK). What happens to its status depends on the kind of view:
- A per-customer view is saved as a draft. Publish it before it can be embedded (see Step 4).
- An organization-level view is saved already published, with no draft state and no publish step at all: it’s never embeddable regardless of status, so there’s nothing a publish step could change. Your team can open and use it in Paid the moment it’s saved.
Step 3: Preview the view
Open the view using the preview link Claude gives you, or find it in the Paid dashboard under Custom views.
Every view has a timeframe selector in Paid, so you can filter the data by date range. For a per-customer view, you can also choose which sample customer to preview data for. For an organization-level view, only the timeframe selector is shown, since there’s no customer to pick.
The preview uses the exact sandbox the published view uses, so what you see is what gets rendered for real: for a per-customer view, that’s what your customer sees once you embed it.
In the Paid dashboard, how picking a range behaves depends on whether the view is actually live to a customer right now, not on its status alone:
- On a draft, or on an organization-level view (always published, but never embeddable), the range you pick saves immediately, since neither one is showing that range to a customer.
- On a published per-customer view, picking a range only changes what you see in the preview. Your embedded customers keep seeing the previously published range until you explicitly publish the new one, which the preview panel prompts you to do with a confirmation. The date range dropdown marks whichever range is currently published, so a pending, unpublished change is easy to spot. If you reload the page before publishing it, the pending range is discarded and the preview reverts to the published one.
This staging is a property of the dashboard, which is what gives you a chance to review a range before your customers see it. Changing a view’s period straight through the API, or by asking Claude to change it, has no preview step: it updates the stored range at once, for every kind and status of view.
If the result needs refinement, continue the conversation with Claude to iterate on the dashboard. From the view’s page in Paid, the Edit menu also lets you copy a prompt to continue in Claude or another assistant, or edit the SQL and dashboard code directly in a modal.
Step 4: Publish
This step only applies to per-customer views. Only published per-customer views can be embedded. A draft is treated as not found on the public embed surface, so a draft’s existence is never revealed to an end customer.
An organization-level view has no publish step: it’s saved already published (see Step 2) and stays that way, since it’s never embeddable regardless of status.
When you’re happy with a per-customer view’s preview, publish it. Either:
-
Click Publish on the view’s page in the dashboard, or
-
Ask Claude to publish it, or
-
Call the API:
Publishing is idempotent: republishing an already-published view is a safe no-op. If the view has a range picked in the preview that hasn’t been published yet, publishing the view for the first time makes that range live immediately.
You can also take a published per-customer view offline by unpublishing it from its page in the Paid dashboard. Unpublishing takes it off the embed right away: every embedded instance stops resolving, exactly as a draft would. The view itself isn’t deleted and you can republish it at any time. Unpublishing is available in the Paid dashboard only. It isn’t exposed through the API, the MCP tools, or the CLI.
Once a view has been saved to Paid, whether it’s still a draft or already published, you can go back to Claude, make changes, and save it again.
Once published, head to Embed a custom view to drop it into your app.
Author without sending customer data to Claude
By default, previewing a view lets Claude read real, customer-scoped data to render it, and that data passes through the model. If your customers’ data must never reach Anthropic, author with a restricted API key that can manage custom views but cannot read customer data.
Create the key in the Paid dashboard under Settings, then API keys. Turn on restricted mode and grant:
- Custom views: read and write, so the key can author, preview, save, and publish views.
- Analytics: none, so the key cannot read customer data.
- Every other scope: none.
Use that key for the MCP connection in Step 1. Everything else about the flow is the same: you still describe the dashboard to Claude and iterate conversationally.
With this key, Claude can still author a correct dashboard because authoring only needs the schema: the table and column names and types, which contain no customer data. Reading the actual rows is blocked, so any attempt to fetch customer data returns 403 INSUFFICIENT_SCOPE.
When Claude previews the view, it renders synthetic sample data generated from the shape of your queries and labels it clearly as sample data. No query runs against your data, so nothing about your customers enters the conversation.
Your real numbers appear only when you open the saved view in Paid or embed it in your app. In both cases the data is fetched by your own Paid session or a per-customer embed token, never by the authoring key and never through Claude.
The restricted key is the enforcement boundary. Because it lacks the Analytics scope, the data endpoints reject it no matter what any tool tries to do, so the guarantee does not depend on the model’s behavior.
The authoring contract
You don’t need to know these rules to use a custom view (Claude follows them automatically), but they explain why a per-customer view is safe to embed for any of your customers:
- Per-customer isolation happens in the database, not in the query. For a per-customer view, the author writes queries that select across your whole organization; Paid scopes each query down to the one customer viewing it, at run time, in the database. An organization-level view isn’t scoped this way: it queries and displays data across your whole organization directly, which is why it stays internal. See the analytics schema reference for the tables available to queries.
- The dashboard’s code is self-contained and sandboxed. It runs in a network-less, opaque-origin iframe: no fetch, no CDN. All CSS, JavaScript, and charting libraries are inlined (budget: 512 KB). The dashboard receives its data via a message, never over the network.
- The timeframe is stored on the view, not baked into the SQL. Queries use
{period_start:DateTime}/{period_end:DateTime}placeholders instead of a hardcoded range, so the range can change at any time without re-authoring anything, and the dashboard always receives data already filtered to it. In the Paid dashboard you can also pick a different timeframe (and, for per-customer views, a different sample customer) whenever you like: on a draft, or on an organization-level view, that choice is saved right away, since neither one is showing that range to a customer; on a published per-customer view it stays in your preview until you confirm publishing it, so your customers keep seeing the last range you published. Updating the period through the API or through Claude skips that preview step and takes effect immediately. - Filters are declared on the view, then driven per request. A view can declare named filter parameters (up to 5), each optionally with the exact values it accepts, for example a
regionfilter. Queries reference one as a{filter_<name>:String}placeholder; an unprovided filter binds the empty string, so the({filter_region:String} = '' OR ...)pattern makes “no filter” mean “everything”. Values are passed per request by whoever renders the view, typically your app around the embed; an undeclared name or a value outside a declared list is rejected with400. Like the timeframe, a filter narrows data within the viewing customer and never widens access.