How to build a marketing analytics dashboard with Claude Code
Stop paying per client, per dashboard and per data feed to look at your own campaign numbers. Describe what a conversion means in your business, hand Claude Code a real export, and it writes the import that validates every row, the six rates with a guard on every division, the widget library people compose boards from, and access rules that answer who may build and who may only look.
Claude Code
$ Build a marketing analytics dashboard: import my real ad-platform CSV exports with every row validated and every refusal kept, compute click-through rate, cost per click, cost per thousand, cost per acquisition and both returns with a guard on every division, let me compose dashboards from a library of charts, metric cards and tables, filter the whole board by date, channel, campaign and country, and keep an administrator, an editor who builds boards and a viewer who only sees what I shared apart.
- Real export imported and checked
- Widgets, filters and permissions proven
- Ready for you to review
What a marketing analytics dashboard actually is
One screen that answers "is this working". The difficulty is never the chart, but that the answer has to be assembled from six platforms that each define the question differently, and then defended to somebody about to move a budget.
Every platform you advertise on already has a dashboard, and each one is confident. The trouble is that they disagree: the ad network counts a conversion when somebody clicked within thirty days, your analytics counts it when they landed and did something, your shop counts it when the payment cleared, and none of the three is lying. A marketing analytics dashboard is where you decide which of those you mean and then apply that decision to everything, so a number on one card can be compared with a number on another.
Next comes the plumbing, which is where the project lives or dies. Numbers arrive as exports: a CSV per platform, per account, per month, with column names that move and campaign names that contain commas. Something has to read those files, check every row against the shape you expect, load what passes, and keep what fails somewhere you can look at it. That last part is the difference between a dashboard people act on and one they quietly check against a spreadsheet.
Only then do you get to the rates, and they are simple arithmetic with one trap in each: cost per acquisition when there were no acquisitions, click-through rate when there were no impressions, return on ad spend on a day nobody spent. Guard every division and the six rates fall out in an afternoon. Skip that and your dashboard shows infinity to the person deciding next quarter’s budget.
Agree Your Definitions Before You Build
Two people looking at the same campaign will disagree about how it did, and almost always because they are counting different things rather than counting wrongly. What counts as a conversion, whether revenue is gross or net of refunds, whether a view-through counts at all, which currency, which time zone a day ends in: write those down before anything is built. They are the specification, and a dashboard that has not settled them is a machine for producing arguments faster.
Reliable Imports From Messy CSV Exports
A dashboard reading a clean table is a weekend. A dashboard reading whatever six ad platforms exported is real software: headers that change without notice, a campaign named "Spring Sale, EU" that shifts every column after it, blank rows where somebody left a total, the same day uploaded twice. Deciding what happens to those rows (kept with a reason and a line number, rather than absent from a chart) is what a marketer checks first, because the first question about any total is always whether it includes everything.
Flexible User Roles & Custom Permissions
This is the part most reporting builds discover late. A marketing dashboard is read by people who must not be able to change it, built by people who should not necessarily see every client, and administered by somebody who needs both. That is two separate questions (what may this person do, and which numbers may they do it to) and answering only the first is the most common way a reporting tool ends up showing one client another client’s spend.
The part companies rate themselves worst at
4.0 / 7
is how 308 senior marketers scored their own company at "integrating marketing technologies across other data systems in our company", the lowest integration score on the list, and identical to the 4.0 they gave it two years earlier. The same people scored themselves 4.9 at choosing which vendor to buy from. That gap is this whole project: acquiring the tool is the part this profession has got good at, and joining the numbers up is the part that has not moved in two years. Published April 2026 by The CMO Survey, in the field that January, and sponsored by Duke University’s Fuqua School of Business, Deloitte and the American Marketing Association. Deloitte sells services next door to this finding, though the report says sponsors get neither the data nor the participant list.
The CMO Survey, 35th edition (n=308, 97% VP-level or above), fielded January 2026 · checked fielded January 2026
The parts every marketing dashboard is built from
One of these six is the charts. The other five are where the build time goes, and where the credibility comes from.
CSV Import That Keeps Every Rejected Row
Every platform hands you a CSV and every CSV will disappoint you: a header renamed since last quarter, a campaign called "Spring Sale, EU" that pushes every later column one place along, the same fortnight uploaded twice by two people. Coping with that is half the job. The half that decides whether anybody trusts the dashboard is what happens to the rows that fail: written to a rejects table with the line number and the reason, so a total is never quietly short. Then a re-upload has to be a decision rather than an accident: keyed on the day, the channel and the campaign, and either skipped or allowed to overwrite, because somebody will upload March twice.
Six Marketing Metrics Calculated Automatically
Click-through rate, cost per click, cost per thousand impressions, cost per acquisition, return on ad spend, return on investment. All six are one line of arithmetic and all six divide by something that is sometimes zero: a day with no impressions, a campaign with no conversions, a channel nobody spent on. Compute them in one place with a guard rather than in each widget and you get two things: no infinity on a card in front of a client, and one definition to change when your team decides revenue means net of refunds after all.
Build Your Own Dashboards From a Widget Library
The dashboard you design in week one is not the one anybody wants in month three. Ship a small library instead (a line and an area chart for trends, a bar chart for comparing channels, a pie for a split, a metric card for a single number, a table for the detail underneath) and let people compose their own board out of it. Two consequences to design for: each widget carries its own configuration, and the layout is saved as data rather than code, because whoever arranges it will not be whoever built the app.
Every Chart Respects the Viewer’s Permissions
Once a board is composed rather than designed, the widget has to be able to answer its own question, and that answer must depend on who is asking. The instinct is to run those queries with an all-powerful server key and filter afterwards. Resist it: the function that answers a widget’s question should carry the viewer’s own credentials, so the database applies their permissions rather than the code remembering to. It is no more work up front, and it is what stops one client’s spend surfacing on another’s board the day somebody duplicates a dashboard.
Dashboard-Wide Filters
A date range, a channel, a campaign and a country, and every widget on the board has to respect all four, including the ones added after the filters were written. Set your date and campaign filters once at the top of the page, and every chart on your dashboard automatically updates in perfect sync, no complex setup required. Remember the selection between visits too: whoever opens this at nine on Monday had those same four filters set on Friday.
Control Who Can Edit vs. Who Can Only View
A viewer reads. An editor builds dashboards. An administrator manages people. That is the first question. The second, and the one that gets forgotten, is which numbers they may do it to: a freelancer building boards for four clients should see four, not nine. Answer both separately (a role, and a grant on one dashboard) and the awkward cases stop needing new code. Answer only the first, and every awkward case becomes a special rule in a screen.
Own the dashboard or rent one per client
Every product below is a monthly subscription, and the bill grows with the business it reports on. Owning the dashboard is a single purchase: the code is yours, and nothing about it is metered.
Build your own
Monthly SaaS tools trap you with recurring fees, per-seat licenses, and paywalled features. With Remixable, you pay once and own the complete, production-ready codebase forever. Launch instantly on day one, not weeks later, and add unlimited dashboards and clients without ever being forced to upgrade.
- A fifth or fiftieth client is a row in a table rather than a line on an invoice
- An eleventh dashboard is an afternoon for whoever wanted it, not an upgrade
- Every imported row is in your own database, queryable without asking anybody for an export
- What counts as a conversion is your definition to write down and change
- No tier gets restructured under you, and nothing moves behind an upgrade you did not agree to
Rent the reporting layer
AgencyAnalytics · DashThis · Supermetrics · Funnel · Data StudioWhat renting buys is the part this template deliberately does not attempt. Live authenticated connections into the ad platforms, so nobody exports a file. Somebody else’s problem when a platform changes its API. Hundreds of pre-built connectors. White-label client portals. At the top end, a data warehouse with your marketing history modelled in it.
- Authenticated connectors into the ad platforms, refreshed on a schedule. This template reads a CSV you give it
- Between 85 and 600-plus integrations already written, tested and maintained
- White-label client portals and branded reporting, which agencies are genuinely bought for
- Warehouse destinations and modelled marketing data at the enterprise end
- Four of the five advertise unlimited users, so what you are billed for is clients, dashboards, data feeds or licences
First because it states the meter more plainly than anybody else here: you buy clients. Its own feature list reads "Unlimited data sources", "Unlimited reports & dashboards" and "Unlimited staff & client users", which is the argument of this whole table in three lines, since the thing being rationed is the client, not the person. 14-day trial, no card.
agencyanalytics.com · checked August 2026
Here because the ladder is how many dashboards you may keep, which is the strangest meter to explain to somebody about to build their own. Users are unlimited on all four tiers. What you climb the ladder for is permission to have more boards. Hold that beside a build you own, where an eleventh dashboard is a row in a table. One honesty note: the page’s monthly and annual figures came back identical to our reader, so the figures above are the ones it lists plus the annual saving it advertises. We have not invented an annual per-month price.
dashthis.com · checked August 2026
The most transparent row in the set, and the one that meters most things at once: data sources, destinations and users all move together, with both billing bases published. Its page says pricing is based on "the number of data sources and destinations you connect" and that it does "not charge usage fees beyond your agreed-upon license subscription fee". Do the count before comparing: search, social, email, your shop and your analytics is five sources before anybody mentions a second ad account, and Starter allows three. Quoted in euros because that is the currency its page served us.
supermetrics.com · checked August 2026
The enterprise end of the same job, included for what it does not tell you. Both figures are floors rather than prices, nothing on the page says per what, and Business lists "Unlimited users and workspaces", so once again the seat is not the product. What you get for it is scale: 113 connectors and 13 destinations at Starter, 579 and 46 at Business, which is both the honest case for renting and the reason the floor is $300.
funnel.io · checked August 2026
Last because it is the real default: most marketing dashboards that exist are free reports in Google’s own tool, and an honest comparison starts by admitting that. The paid tier is worth knowing for its shape, quoted from Google’s own docs: "A self-service Data Studio Pro subscription is based on individual user licenses", you are "billed monthly for the number of Pro licenses in the subscription, regardless of whether or not those licenses are used", and "Every Data Studio Pro subscription is associated with one (and only one) Google Cloud project". Note the name, too. Almost everybody still says Looker Studio, and the documentation now reads "Looker Studio is now called Data Studio." The licensing shape is the part worth remembering.
docs.cloud.google.com · checked August 2026
Rule of thumb: if the numbers must arrive without anybody exporting a file, if you need forty connectors maintained by somebody else, if clients expect a branded portal, or if a platform’s API changing is a problem you want to buy your way out of, rent, and if one free report in Google’s own tool already answers your question, use it and spend the fortnight on something else. Build when the meter is the problem: when the fifth client, the eleventh dashboard or the fourth data feed is the thing standing between you and looking at your own numbers, and when what a conversion means in your business is a definition you would rather own than accept. The survey figure above is the honest reason this is worth the effort: buying another tool is the thing this profession is already good at, and it has not moved the integration score in two years.
Why build with Claude Code
Most software looks simple from the outside, but it’s mostly hidden plumbing: a database, logins, permissions, validating forms, and dozens of screens that read and write records. Building all of that yourself means being fluent across the full stack, so weeks go to parts customers never see before the first real feature works.
Claude Code removes that barrier. The whole workflow becomes a simple loop:
Describe
Say what you want in plain words in any language.
Build
It writes and edits real code across backend, auth, and UI.
Check
Run the app and see the change actually work.
Repeat
Ask for the next thing. Repeat.
No stage of that loop asks for the full-stack expertise or the months of boilerplate that stop most people, which is why one person can ship a working app in a couple of weeks.
Three hard partshandled for you
The data model, authentication, and access rules are what make software like this genuinely hard to build by hand. Describe them and Claude Code scaffolds all three. After that, the rest is mostly screens on top.
Any language is the interface
No code to write, and no English required either. Whether you need a new field, a renamed step, or an AI summary, describe it in whatever language you think in and Claude Code handles the implementation.
Whole project in context
It finds and reads the files a change touches, instead of needing you to paste them in, so each edit stays consistent with what is already there. Point it at the two or three files that matter and it stays fast.
Pay a developer, or do it with AI
The one running cost worth noticing is the model behind the written summary, and it is smaller than people expect. The rest is a database plan and hosting, and both start free.
Hire a developer
Custom build, from scratch- Developer
- ~$11k-$43k
- Supabase (backend)
- Free tier · $25/mo (Pro plan)*
- Hosting
- $0 free tier
- AI model (the written summary)
- Usage-based - cents, not dollars
- Build time
- ~215 hrs of their work
~$11k-$43k to build, then from $25/mo plus a few cents of model usage
That range is our own ~215-hour estimate priced against the rate survey linked below, which puts senior US developers at $100-$150+ an hour, specialists higher, and agencies 20-40% above the freelancers they bid against. Hence $50/hr and $200/hr as the two honest ends. Where somebody has quoted you for "a marketing dashboard", interrogate the assumption rather than the total: what do they expect to receive from each ad platform, and what will they do with the rows that fail to load? Those two answers are usually the difference between the quote and the invoice.
Build it with Claude Code
From scratch, with Claude Code- Claude Code
- $20/month (Pro) to $200/month (Max)
- Backend (Supabase)
- Free tier · $25/month (Pro plan)*
- Hosting
- $0 on a free tier
- Your time
- ~103 hrs
~$20-$200/month while you build, then whichever plan you keep using
Claude Code itself is free. The cost sits in the Claude plan behind it. The fee doesn’t shrink when you start from a template the way a per-hour developer bill would: Pro, at $20/month, covers a template import or a short build, and a from-scratch build that runs for weeks tends to need the $100-$200/month Max plan instead, because it outlasts Pro’s usage window. Either way, the template changes how many of the hours in the estimator above you actually spend, not which Claude plan you’re paying for.
* The free-tier line that matters here is not the 500 MB of database, which holds years of campaign rows because this data is small and narrow. It is the pause: a free project stops after a week of inactivity, and a dashboard that gets read at the end of each month is exactly the project that goes quiet for a week and greets somebody with an error. Pro, from $25/mo, ends the pause, keeps 7 days of backups, and raises the allowances that matter once people are downloading board exports: 250 GB of egress (then $0.09 per GB) and 100 GB of file storage (then $0.0213 per GB), the latter being where your uploaded CSVs pile up.
Either way the dashboard is yours, and the only running costs are a database plan and a few cents of model usage a month. What differs is who builds it: a developer you pay up front, or you, describing each piece to an AI tool.
Prices and rates from supabase.com, developex.com, developers.openai.com and claude.com, checked August 2026.
Decide before you build
Six things to write down before anything is built. Every one of them is a definition somebody will argue with later, and every one is cheaper to settle now than after a number has gone out in a client report.
What a conversion is, in one sentence
Write it down and make everyone who will read the dashboard agree to it. Does a view-through count. Does a repeat purchase in a week count once or twice. Is revenue gross or net of refunds. Which platform wins when two disagree, because they will. It decides what your import stores and whether two cards on one board can honestly be compared.
What your exports actually look like
Download a real month from each platform you advertise on and open the files. Their headers, their date formats, whether they quote fields containing commas, whether they append a totals row. That is the actual specification for the import. If the answer is five platforms, decide whether each gets its own shape or they normalise into one.
Wide rows, or facts and dimensions
Both shapes work. One wide table per day, channel and campaign is simple, fast to query, and awkward to extend. Separate fact tables around a campaign dimension take longer to build and absorb a new metric without migrating every screen. Pick deliberately. Wanting both means keeping two things in step.
Who builds, who reads, and which numbers
Three roles is the useful minimum: somebody who administers people, somebody who builds dashboards, and somebody who may only read what was shared with them. Then answer which dashboards each person may reach, the question that decides whether this is safe to hand to clients. With several clients, the rule that one never sees another belongs in the first migration.
What the written summary is allowed to say
A paragraph of plain English over a board is genuinely useful and the fastest thing here to get wrong. Decide that the model may describe only numbers it was given, and that any trend is computed by a query first. Handed this month’s figures alone, it will cheerfully say they are up on last month, and sound certain.
Whether anybody outside your company will see it
A board for your own team and a board for a client are the same code and different products. If clients will see it, two things move from later to now: their branding on it, and a share that grants one dashboard rather than an account. Retrofitting that is the most common rebuild in this category.
Comparing your build options
Two things cost the money here and neither is a chart: a parser that survives what your ad platform really exports, and a permission rule that names both a role and an organisation every single time. Three routes to the same product: by hand, on a UI kit that stops at a styled metric card, or through Claude Code end to end.
The charts are a fortnight, and they are the part you can picture. The quarter goes on everything you cannot: getting six platforms’ exports to agree on what a conversion is, a parser that survives a campaign name with a comma in it, deciding what happens to the two hundred rows that will not load, a query engine behind widgets somebody else assembled in an order you never anticipated, and a permission model where the person who may build a dashboard is not the person who may see the numbers on it. None of that is visible on screen, and all of it decides whether the screen is worth reading.
A kit is unusually good value on this particular product: dashboard shells, KPI cards, a chart library and a drag-to-arrange grid are exactly what kits are made of, so more of the surface arrives free here than on almost anything else you could build. What arrives with it is nothing. No kit knows what a channel is, that cost-per-acquisition needs a guard against dividing by zero, which of your uploaded rows were rejected and why, or that this viewer may read one dashboard out of nine. Every number the cards display is still yours to define, compute and defend.
Ask for one piece at a time and the migration, the query behind a widget and the widget itself arrive together as one change. The same task list as the rows above it, worked through by an agent that can open the actual CSV your ad platform produced and describe it back to you before writing a single line of parser against it.
Estimate your exact build timeframe
Plenty of marketing teams need less than this. If nobody but you will ever sign in, or one fixed board beats a library, untick those rows and watch the estimate fall.
Your estimate
103 hrs
start to finish
Based on the 6 of 6 features you’ve selected, plus ~26h of groundwork. Toggle any on the left to watch the number move, and open the groundwork row to untick what you have already, such as a database that is already running or going live if you are only building a mock-up for now.
A rough estimate, not a quote. Real time depends on how much you customize and how clean your data is.
Let’s set up the tools you need
Four things need to be on your machine before step 01, and none of it takes more than about 15 minutes. Three are ordinary installers you click through, and the fourth is an active Claude subscription. From there, you build simply by describing what you want, in your own words.
Claude Code
Your main AI assistant. Download and run the free Claude Code CLI, connect your Anthropic account or pay-as-you-go API key, and build your app in plain English. It runs in the Terminal, and if a command line puts you off, the same tool ships as a desktop app with buttons and windows.
Install Claude CodeClaude subscription
Claude Code itself is free, but the free Claude.ai plan does not include Claude Code access at all, so you need at least Pro, unless you pay as you go through a Console account instead. Pro at $20/month is enough to start with, though a long, from-scratch build tends to outrun what Pro allows in a given stretch, which is when people move up to Max, from $100/month. Every plan’s usage resets on a rolling window, so on a heavy day you may hit a limit and have to wait it out.
Compare Claude plansNode.js engine
The engine that runs your app on your own computer. You never have to learn how it works: download the version marked LTS (the most stable one), install it, and forget about it.
Download Node.js (LTS)Supabase (database)
Where your project keeps its data. Install it, then sign in once by running supabase login. Words like migrations and row-level security turn up later in the guide, and Claude Code writes those parts for you.
Install Supabase CLINothing here is worth memorizing. These four just need to exist on your machine. From step 01 on, you say what you want and Claude Code runs the commands.
Build your marketing dashboard, prompt by prompt
No code from you at all. You describe what you want and Claude Code runs the commands. Worth knowing about the order before you begin: your own export shows up at step 03, before a single chart exists. The file you actually have is the specification for the hardest part of this build, and it is always stranger than the one you would have imagined.
- 01
Get it running, and write down what a conversion is
An app booting against your own database, and a CLAUDE.md carrying the two definitions everything else in this build resolves to.
PromptSet up the projectSet up a new React 18 + Vite + TypeScript project with Tailwind and the Supabase JS client. Read VITE_SUPABASE_URL and the publishable key (the sb_publishable_… key, which replaces the older anon key) from .env, and add .env to .gitignore in the same step so neither value reaches GitHub. Add a typed Supabase client under src/lib, then write a short CLAUDE.md describing the stack and fixing the vocabulary for a marketing dashboard (organisation, data source, channel, campaign, impression, click, conversion, cost, revenue, dashboard, widget, reject) and recording two standing rules. First: a conversion means exactly one thing across this whole project, written out in a sentence I will give you, and no screen ever computes a rate for itself. Every rate comes from one place that guards its division, so a card can never show infinity. Second: every row belongs to an organisation, every access rule has to test both what the person may do and which organisation the row belongs to, and neither half is ever left out because the other one looks sufficient.
Put both in the file, not only in this message. The second rule sounds like a technicality and it is the whole difference between a dashboard you can hand a client and one that shows them somebody else’s spend, and it is the rule most easily lost three weeks later, when a new widget needs a policy in a hurry.
- 02
Model the organisation first, then everything hanging off it
The schema, with an organisation on every row from the very first migration. No sign-in and no screens yet, which makes this the cheap moment to discover your own exports carry a field nobody planned for.
PromptModel the organisation and the campaign dataWrite the data model as Supabase migrations, with no authentication yet and an organisation reference on every table from the start. Organisations. Members linking an account to an organisation. Data sources, one row per file or platform somebody imports from, carrying the import’s status and counts. Then the campaign data in two shapes so I can choose later: a wide table of one row per date, channel and campaign with impressions, clicks, conversions, cost and revenue on it, and alongside it a campaign dimension with separate fact tables for sessions, cost and conversions. Then a rejects table holding every row an import refused, with the line number, the raw row and the reason. Then dashboards, and widgets belonging to a dashboard carrying their own type, title, configuration and position. Seed two organisations with deliberately awkward data: one with a campaign whose name contains a comma, one with the same fortnight imported twice.
Ask for the second organisation now, not when you get to permissions. A single-tenant database hides every mistake you are about to make about who can see what, and a second organisation full of plausible-looking campaigns is the cheapest instrument you will build all week for finding them.
- 03
Hand over a real export, not a tidy invented one
Go and download a real month from one ad platform before this step, and put the file in the project. This is the step where Claude Code working on files on your own disk is worth the most: it can open your export and tell you what is in it before writing anything.
PromptImport a real exportA real CSV export from my ad platform is in the project. Read it before you write anything and tell me what you found: the exact column names, the date format, whether fields containing commas are quoted, how it writes a zero or a missing value, any totals row at the bottom, and anything you cannot map onto my schema. Then build the import: a private storage bucket restricted to CSV and scoped so a person reaches only their own folder, and a server-side function that parses an upload with a parser that handles quoted fields properly rather than splitting on commas, checks the required columns are present, validates every row (dates parseable and inside a period I state, numbers numeric, nothing negative that cannot be, channel recognised), inserts what passes and writes every failure into the rejects table with its line number and reason. Never drop a row silently. Handle a re-upload as a decision: key each row on date, channel and campaign, and let me choose per import whether an existing row is skipped or overwritten. Then import my real file and show me three things: the accepted count, the full reject list with reasons, and the total spend of the accepted rows so I can compare it with the same figure in the ad platform.
That last number is the point of the whole step. A total you can check against the platform it came from in ten seconds is what turns this from a plausible dashboard into one somebody will spend money on the strength of, and it is far easier to compare now than after two more layers of aggregation exist above it.
- 04
The six rates, then a library people compose from
The arithmetic first and in one place, then the widgets over it. Ask for the rates as something the database computes rather than something a chart works out, because six widgets each doing their own division is how two cards on one board end up disagreeing.
PromptBuild the rates, the widgets and the filtersFirst the six rates, computed in one place and never in a component: click-through rate, cost per click, cost per thousand impressions, cost per acquisition, return on ad spend, and return on investment. Every one of them guards its division so a day with no impressions or a campaign with no conversions returns nothing rather than infinity, and I want to see what each returns for a zero case before we go on. Then the widget library: a line chart, an area chart, a bar chart, a pie chart, a metric card showing one number, and a data table. Each widget stores its own metric, grouping and title, and a dashboard stores the widgets on it and their positions as data so somebody can rearrange a board without a developer. Then one server-side function that answers a widget’s query, and build its database client from the caller’s own credentials rather than a service key, so the database applies that person’s permissions to every widget including ones added next year. Then four filters that apply to the whole board at once (date range, channel, campaign and country) passed into every widget’s query rather than filtering in the chart, and remembered between visits.
Insist on seeing the zero cases before moving on. Every one of these six rates divides by something that is legitimately zero on a quiet day, and the first time anybody notices is when a client asks why their cost per acquisition is infinity. It is a ten-minute conversation now and an embarrassing one later.
- 05
Sign-in, three roles, and the rule with two halves
Your database now holds what you spend and what it earned, and three kinds of person who should not have the same power over it. This is the step to read slowly.
PromptAdd auth, roles, and the access rulesAdd Supabase Auth with email and password: sign-up, login, logout, a persisted session, and a profile row per account. Joining an organisation happens on the way in, and a person cannot choose or change which organisation they are in afterwards. Add a role enum of 'viewer', 'editor' and 'admin' in a roles table kept out of the account’s own record, plus a SECURITY DEFINER function that reads it, and give roles an organisation, so somebody is an editor of one organisation rather than an editor everywhere. Then row-level security on every table, and write every policy as two clauses joined: what this role is permitted to do, AND that the row belongs to the organisation this person belongs to. Never let a role check stand alone as if it were a boundary. On top of that, a per-dashboard grant table so a viewer can be given one specific dashboard, checked alongside the role rather than instead of it, and reachable from an invitation so the grant can exist before the account does and transfer when it is accepted. Then prove six things by querying as a signed-in account: the second organisation’s campaign rows are unreachable, its dashboards are unreachable, an editor of one organisation cannot update a dashboard belonging to the other, an admin of one cannot grant themselves access to the other’s dashboard, a viewer cannot create a widget, and a viewer cannot open a dashboard nobody shared with them.
The third and fourth checks are the ones nobody thinks to run, and they are the reason this step exists. Both are performed by an account behaving entirely legitimately right up to the final clause, which is exactly what a rule that checks the role and forgets the organisation cannot tell the difference between.
- 06Destination
A summary that can only say what it knows, then reconcile
The written paragraph at the top of the board, the ceiling on what it may spend, the exports, and the part most builds skip: check one channel against the platform it came from before anybody makes a decision from it.
PromptAdd the summary, the cap, and reconcileAdd the written summary at the top of a board, built so it cannot invent anything. Compute first, with queries: this period’s totals for each metric, the same totals for the previous period of equal length, the change in each, and the same per channel and per campaign. Send only those computed figures to one server-side ai function, and instruct the model to describe what it was given without inventing a comparison, trend, projection or cause that is not in the numbers, and to say when a change is too small to matter. Print the figures it was given underneath and name the two periods. Then cap it before I ship: record every call with the account, the feature and the tokens used, and enforce a per-account hourly limit and a project-wide monthly limit I can change without redeploying, refusing politely when either is reached. Then exports: the board as a PDF, and the rows behind any single widget as CSV. Finally, reconcile with me: take one channel for one month, put your figures beside the same figures in the ad platform they came from, and account for every difference, including the rows sitting in the rejects table.
Do this before anybody else sees the dashboard, on a month you already know the shape of. Every difference will be one of three things (a rejected row, a definition you and the platform disagree about, or a date landing in another time zone) and all three are cheap now and expensive in front of a client.
Ironclad role security and dashboard access control
Marketing dashboards hold sensitive financial and performance data. Here is how your system manages user permissions, isolates company data, and securely shares individual reports.
Roles live in their own table, checked by something that outranks the caller
The tempting shortcut is a `role` column on the account’s own profile row, which hands every user the power to promote themselves in one edit. Keep roles in a separate table instead, and have your access rules call a small function that reads it, one that runs with the database’s own authority rather than the caller’s, so the rule can check a role the caller is not allowed to change. Three roles is the shape this product wants: an administrator managing people, an editor building dashboards, and a viewer who reads.
A role is not a boundary: every rule needs both halves
Worth a paragraph, because it survives code review and it is the standard way reporting tools leak. A rule saying "an editor may update a dashboard" answers what this person may do and nothing at all about whose dashboard it is. Both halves belong in the same rule: this person’s role permits the action, AND this row belongs to their organisation. Read each policy back asking which organisation it tests. If the answer is none, you have written a permission rather than a boundary.
Then the second axis: a grant on one dashboard
Roles alone cannot express the case this product runs into immediately: a person who should see exactly one board out of nine. That wants its own table: a row saying this account may read that dashboard, checked alongside the role rather than instead of it, so somebody’s access is the union of what their role allows and what they have been given. It also has to work before the account exists, since people are invited by email and accept later, so the grant hangs off the invitation and transfers when accepted.
The query behind a widget runs as the person looking
Widgets somebody else assembled ask their own questions, and the safe way to answer them is to build the server function’s database client from the viewer’s own credentials rather than an all-powerful key. Then the database enforces every rule above on every widget automatically, including the widget added next month by someone who never read this page. Server-side keys that bypass those rules belong only in jobs that genuinely outrank the caller (parsing an upload, sending an invitation) and each of those has to check the caller’s role itself, because nothing else will.
What a row may become, not just which rows you may touch
A rule that lets somebody edit their own record still has to say what that record is allowed to turn into. If the column deciding which organisation somebody belongs to is editable by them, every rule that resolves through it is decoration. The same applies to joining: a rule permitting somebody to add themselves to a team has to constrain which team, or it is not a rule. Constrain the destination as explicitly as you constrain the actor.
Where the keys live, and what leaves your database
Two secrets matter. The database’s service key belongs only in server-side function secrets and never in the browser, since it ignores every policy above. The model provider’s key belongs in the same place, for a plainer reason: whoever holds it can spend your money. Requiring a signed-in user before a model call is a floor, not a ceiling. A per-account cap on how often anybody may regenerate a summary is what turns it into a limit, and it belongs on day one rather than after a month of unexplained usage.
Review the access rules (row-level security policies) on every table. For each one, tell me in simple terms who can view, add, edit, and delete records, confirm that people can only reach their own data while the right roles can reach more, and flag anything left open that shouldn’t be.
Run this before launch to make sure nobody can see data they shouldn’t.
Prove all of it by querying as a signed-in account rather than reading the code back. Make a second organisation with its own numbers and check, one query at a time, that it cannot be reached, including the two that look legitimate right up to the last clause: a viewer opening a dashboard nobody shared, and an editor updating a board belonging to somebody else entirely.
What speeds the build, and what slows it
Speeds the build
- A CLAUDE.md notes file that spells out your setup and preferences
- Asking for a plan first on anything that touches several files
- One change per request, small enough to describe in a sentence
- Pointing it at the two or three files that matter
- Running the app and checking each change before the next
- Saving a working version (a git commit) after each step, so you can undo
Slows the build
- Vague prompts like “make it better”, which leave it guessing what you meant
- Asking for a whole feature in one giant prompt
- Dumping the entire project into the chat at once
- Skipping the notes file, so it forgets your conventions each session
- Accepting changes without running or reading them
- No saved versions to roll back to when something breaks
Git: what it is, and why you need it
Before you build anything, meet the one tool that makes building safe. You need no coding background for it: Git remembers every version of your project, so you can try things, break things, and get back to a working state in seconds.
What Git actually is
Git is a quiet recorder that runs alongside your project. Each time you save your work it keeps a full snapshot, so the entire history of your project lives on your computer, not just whatever the files look like right now.
Why you need it
Claude Code runs inside a permission mode you choose, either asking before each change or working more freely once you trust it. Either way, experiments sometimes still break things. Git is what makes that safe: there’s always a working version to return to, so you can try bold changes without the fear of losing what already works.
A commit is a save point
Each commit is a snapshot with a short note, like “added the home page”. Make one after every working step and you can jump back to any of them later.
GitHub’s beginner guide to GitUndo anything, safely
If a change breaks something, you roll back to the last good commit instead of unpicking it by hand. It’s the safety net that makes bold experiments with Claude Code low-risk.
GitHub is Git’s home online
Git lives on your computer. GitHub is a free, private cloud copy of the same project. It’s your backup if your laptop dies, and the place Claude Code can always get back to. Keep it private, and never commit secret keys or passwords.
Create a free GitHub accountYou rarely type git commands
You do not have to memorize any of it. Ask Claude Code to “commit this” or “undo the last change” and it runs the git steps for you. Prefer clicking to typing? Claude Code’s own desktop app, which you install separately, shows each change side by side before you keep it, and GitHub Desktop gives you plain buttons for saving and rolling back.
Get the Claude Code desktop appWhere to host your application
Hosting gives your app a home on the internet so anyone can open it via a web link. Choose a service below to make your site live. (Your database, logins, and business records are stored separately in Supabase, covered below).
| Host | Best for | Notes | Free tier |
|---|---|---|---|
| Vercel | One-click deploys | Connect the repository and every push publishes itself, with nothing for a Vite project to configure. Settle which plan you belong on before a client sees it: Hobby is licensed for personal, non-commercial use, and a dashboard you report to a client from is commercial no matter how small, so Pro, at $20/user/mo. | Pro from $20/user/mo (Hobby is non-commercial) |
| Netlify | Drag-and-drop or Git | Connect the repository, or drop the built folder on the page and be live in a minute. Add the redirect rule it asks for. On this app that rule is not a nicety: the whole point of sharing a dashboard is sending somebody a link straight to it, and without the rule that link returns a not-found page. | Free tier |
| Cloudflare Pages | Clients in several countries | The board is served from close to whoever opened it, which is the right call when the people reading it are spread across time zones. Worth pairing with a decision about which time zone a "day" ends in, since that is what they will disagree about. | Generous free tier |
| GitHub Pages | Not really this app | Free publishing from a GitHub project after one routing setting. Listed to be ruled out: free means a public repository, and this code sits beside what you spend and what your clients earn. | Free from a public repo only |
| Firebase Hosting | Teams already on Google | One round of setup, then one command per release. Nothing about the dashboard argues for it. The argument is that your logins are already Google’s. | Free Spark tier |
| AWS Amplify Hosting | Teams already on AWS | Publishes from the AWS console and wants the same rewrite rule for a shared link to resolve. Picked when AWS is on the invoice already. | Free tier (build + hosting) |
| Surge | Publish from the terminal | One command sends the built folder online, with no repository involved. Fine for showing a colleague a draft board. Wrong for anything a client has the link to. | Free - unlimited publishing |
| DigitalOcean App Platform | DigitalOcean users | Builds and serves inside the account you already have, one fewer supplier to explain. | Free - 3 static sites, 1 GB/mo transfer |
All eight serve a dashboard well enough that speed is not the deciding question. Three others are: whether the plan permits commercial use, since Vercel’s Hobby tier does not and client reporting is commercial. Whether it publishes from a private repository, given what this code sits beside. And whether a deep link resolves for a browser that has never seen your site, because a shared dashboard is a link somebody sends.
One thing will catch you on launch day, and it is not the host. A fresh deploy correctly shows zeros until a file has been imported, so the first hour of production usually goes on establishing that the deploy is fine and nobody has uploaded anything yet.
Keep your data in Supabase
Your campaign rows, the dashboards and widgets somebody composed, the rejects from every import, your uploaded files, and the server-side jobs that read them. This is also where the work that has to outrank the person who triggered it belongs.
| Service | Best for | Notes | Free tier |
|---|---|---|---|
| Supabase | Data, auth, files, server jobs | Campaign rows and dashboard layouts both live in Postgres, accounts come from its auth service, uploaded CSVs go into a private bucket, and its edge functions carry the jobs that need more authority than the person who asked: parsing an upload, sending an invitation, calling the model behind the written summary. Setting it up means opening a free project and handing the app the project URL and the publishable key. The one to add afterwards is the model provider key, which goes in the function’s secrets and nowhere near the browser. | Free tier, then usage-based |
What to let a model do here, and what to compute first
A marketing dashboard is where the temptation to let a model narrate is strongest, because plain-English summaries of numbers are genuinely valuable and completely trivial to fake. One rule keeps all five of these honest: compute the figure with a query, then let the model choose the words.
Say only what it can actually know
Start here, before adding anything. A summary panel handed this period’s totals and nothing else cannot tell you they rose, and cannot project next week, but asked for insight it will do both, fluently and with a number attached. Better instructions will not help here. Compute last period with a query, pass those figures in, and the model finally has something real to describe. Anything it was not given, it may not mention.
Rewrite the written summary so it can only describe numbers it was given. First compute, with queries: this period’s totals for each metric, the same totals for the previous period of equal length, the change in each as an absolute and a percentage, and the same per channel and per campaign. Send only those computed figures to my ai function, and instruct it to describe what it is looking at without inventing any comparison, trend, projection or cause that is not in the numbers I sent, and to say when a change is too small to be worth a sentence. Print the figures it was given underneath the summary, name the two periods being compared, and if a metric has no previous period to compare against, have it say so rather than skip it.
Explain a movement after you have found its cause
The question a marketer actually asks is not what changed but why. That is a query (which campaigns and channels account for the difference between two periods, in order of contribution) and once you have the answer, a sentence explaining it is exactly the right job for a model.
Add a "why did this move" action on any metric card. Compute the answer first with queries: the campaigns and channels contributing most to the change between the two periods, with each one’s absolute contribution and its share of the total movement, and whether the movement came mostly from cost, from volume or from conversion rate. Then send only those computed contributions to my ai function for two or three sentences in plain language. Show the contributing rows underneath, and where a single campaign accounts for most of the movement, have it say that plainly rather than listing three things as if they were comparable.
Flag the rows that look wrong before they reach a chart
The dangerous import is the one that succeeds. A fortnight uploaded twice, a cost column in the wrong currency, a conversion count with an extra digit: all load cleanly and all move a card. Statistics can find the outliers. A model is useful for saying which of them look like mistakes rather than a good week.
After every import, run a check for rows worth a second look, computed with queries: amounts far outside the usual range for that campaign, cost-per-acquisition or return-on-spend figures orders of magnitude away from that channel’s norm, dates outside the period I said I was importing, days that already had data before this upload, and any metric that moved more than a threshold I set. Then send the flagged rows to my ai function for one line each on why it might be a mistake, and show me the list with an accept or investigate action per row. Nothing is edited or deleted automatically, and the flagged count sits beside the import summary where the reject count already is.
Ask your own numbers a question in words
A board can only answer the questions it was built to answer. The ones that turn up later (did the sale week pay for itself, which channel quietly got more expensive since spring) each arrive once and do not deserve a widget of their own.
Add a panel where I type a question about my own campaign data in ordinary words. My ai function turns it into a read-only query, runs that query under my own access rules so it can never reach another organisation’s rows, and uses the model only to word the answer. Under every answer, show me the query that ran, the rows it returned, and the period and filters that were in effect. Two hard rules: when a question needs data I do not import, it says so rather than estimating, and it never answers from what it already knows instead of from a query. It writes nothing, ever.
Put a ceiling on what it can spend
Requiring somebody to be signed in stops strangers spending your model budget and does nothing about the person who discovers the regenerate button. Every feature above is a request somebody can repeat, so the cap belongs in the same place as the key.
Add a spending ceiling to my ai function before I ship any of the features above. Record every call with the account that made it, the feature it came from, and the tokens it used. Then enforce two limits I can set without redeploying: how many calls one account may make in an hour, and a total for the whole project per month, refusing politely and telling the person when either is reached, rather than failing silently or charging on. Show me current usage against both limits on an admin screen, and log a refusal the same way as a call so I can see when a limit is doing something.
Each prompt above picks the model that fits the job. As a rule of thumb, that is Haiku for high volume, Sonnet for everyday writing, and Opus for deeper reasoning. Model names move faster than this page does, so check the current list in the Claude docs (linked in the references below) before you build. Send every one of these prompts through that same ai function, so one key and one set of rules governs all of them.
Get a head start with our template
Every route above begins with an empty folder. There is one that does not. This dashboard is built, runs, and already knows what to do with a messy export, so the weeks that a parser, a widget library, a query layer and two kinds of permission would have cost you turn into one upload and a look at the reject list.
Marketing Analytics Dashboard
The exact marketing dashboard this guide builds, packaged so you can open it, point it at your own backend, and make it yours from there. A marketing analytics tool where you bring in your data, assemble dashboards from a library of widgets and charts, and let AI point out what stands out. It turns scattered campaign numbers into a clear picture your whole team can read.
The key benefits of starting with a template
The import validates every row and keeps what it refuses, the six rates are computed with guards, dashboards are composed from a widget library, and each widget’s query runs under the permissions of whoever is looking. That is most of the saving. The sharing, the invitations and the notifications are done too.
Building the core from scratch
~103 hrs
Opening the template, already built
~1 hr
~102 hrs of building you skip
Two different measurements on purpose. The larger figure is what it costs to build this application. The ~1 hr is what it costs to adopt the finished one: connect your own database, put one real export through it, and settle who may build a board against who may only open one. Longer than most templates in this catalogue take, because this is one of the few that shows you nothing until real data has been through it. What neither figure includes is the argument about what a conversion means in your business, since that costs the same whichever route you take.
An import that already copes, and keeps its refusals
Upload a CSV and a server-side function parses it, checks that the required columns are present, validates every row (dates parseable, numbers numeric and not negative where that makes no sense), writes what passes into the campaign tables and every failure into a rejects table with its line number, its raw data and the reason. Re-uploading the same fortnight is a decision rather than an accident: rows are keyed on date, channel and campaign, and you choose per import whether a repeat is skipped or overwritten.
Two data shapes, both wired up
A wide table of one row per day, channel and campaign carrying impressions, clicks, conversions, cost and revenue, and a canonical star schema alongside it: a campaign dimension with separate fact tables for sessions, cost and conversions. Two import functions, one per shape. The wide table is the quick path. The star schema absorbs a new metric without touching every screen. Having both makes the choice in decision 03 above a preference rather than a rebuild.
Six widgets, and the editor that composes them
A library of line, area, bar and pie charts, a metric card and a data table, each one configurable, arranged into saved layouts by anybody with the editor role. Behind them a function answers each widget’s own query, building its database client from the viewer’s own credentials rather than a service key, so every access rule below applies to every widget automatically, including one added months later. Four filters cross the whole board at once, remembered between visits: date range, channel, campaign and geography.
83 live access policies, over two permission axes
Three roles from a real database enum (viewer, editor and administrator) kept in their own table behind checks running with the database’s authority rather than the caller’s, and 83 live row-level security policies over 24 tables. Then the second axis, which is what makes this usable with clients: a grant on a single dashboard, attachable to an invitation so it transfers when the account is created. Somebody can be given one board without the run of the place. How far the roles reach is worth knowing before you go live: read the access section above, because on a deployment holding more than one client’s numbers there are policies you will want to narrow.
The written summary, and the honest note about it
A server-side function sends the current metrics to OpenAI and returns a short plain-English summary for the top of the board. It requires a signed-in user before it spends anything, tells a rate limit apart from an out-of-credit error, retries a provider outage three times with a growing pause, and takes its model from an environment variable so swapping it is configuration rather than a code change. What it is given is the current totals and the filters in effect (no history) so treat any comparison or projection in its text as a turn of phrase rather than a finding. The first AI prompt above is the version that computes the comparison before mentioning it.
A codebase your AI tool can keep extending
Types on everything, one hook per thing that gets queried, folders named after features rather than file extensions, and comments where the reasoning would not survive a cold read. The widget layer is where that pays off: a seventh kind of widget is the change you are most likely to want, and the six that exist show where a new one plugs in.
From founders who build on our templates
As an agency, clean handoffs are everything. This template is so well-architected that we can restyle the interface, connect the client’s data, and hand over a finished product, without wasting billable hours untangling someone else’s mess.
Matt GrahamFounder & CEO, RapidDevCommon questions
You upload files, and that is the first thing to know before comparing it with anything on the market. There is no authenticated connector to an ad platform: you export a CSV from each one, upload it, and a server-side function validates and loads every row. That is the honest trade. The products that do connect are the ones metered per client or per data feed, and maintaining connectors against platforms that rename fields is most of what that fee pays for. A connector to one specific platform is a well-defined addition, and it is the thing most people want second, after a month of their own numbers has actually landed.
Every row that fails is written to a rejects table with its line number, the original row and what was wrong with it, and you are told how many failed as soon as the import finishes. Why that matters: a board silently short of two hundred rows still draws a confident chart, and nothing about it looks wrong. So read the reject list after every upload as a habit. In practice most entries turn out to be one date format or one renamed column, which is a five-minute fix the moment you can see it. One thing to know as it ships: the rejected rows are kept and counted, but the download link offered for them does not work, because the file is written to a private bucket and linked as though it were public. The rows are in the database. Fixing the link is a signed URL.
Trust the numbers it quotes and not the story it tells about them. As it ships, the summary is sent the current totals and the filters in effect, and nothing else, no previous period and no history. The metrics it repeats are therefore real. Any comparison, trend or projection in the same sentence is the model being fluent, because it has no data to derive one from and the example in its instructions encourages it to try. That is a genuine limitation, stated here rather than buried: treat the paragraph as a readable caption, and use the first AI prompt on this page to replace it with a version that computes the comparison first and may only describe what it was given.
No, and a table in the schema makes it look as though it might. The exchange-rate table has access rules on it and nothing in the application reads it, so every figure you see is in whatever currency your imported rows were in. For most teams advertising in one currency that is a non-issue. If you spend in three, the honest description is that this is an addition rather than a setting: convert on import or at query time, store which rate you used, and mark any total that mixes them. That last part is what stops an awkward conversation about a number being slightly off.
Yes, and it is one of the two things this template was shaped around. Access is the union of a role and a set of grants: a viewer can be given one specific dashboard, by email, before they have an account at all. The grant rides on the invitation and transfers when they accept. Before you point a client at it, read the access section above and narrow what you find there: some rules as shipped grant an editor or an administrator reach across dashboards without checking which organisation the dashboard belongs to, which is harmless on a single-company deployment and not what you want when two clients share one database.
Very little, and it is the one running cost that is genuinely usage-based. The summary sends a few hundred tokens and gets a short paragraph back. At the published price of the small model it ships with, that is a fraction of a cent per refresh, so a thousand refreshes in a month costs cents rather than dollars. Two caveats worth more than the arithmetic: nothing in the template caps that spend, so a per-account and per-month ceiling is the sixth AI prompt above and worth adding before you ship, and the model is chosen by an environment variable, so pointing it at something larger changes the sum entirely.
No, though you will type the occasional command: installing Claude Code, starting the app, applying a database change. The setup section above lists what you need, with a link for each, and once it’s on your machine Claude Code runs most of those commands for you.
Claude Code turns the real code into an app you can publish, with a database and user accounts. An Artifact is a one-file preview, good for a quick look but not for going live.
Claude Code’s plans reset on a rolling window rather than billing per token, so a heavy day of building can bump into a limit. You either wait for it to reset or move up a plan. Max gives more headroom for a long, from-scratch build. Nothing you’ve already built is lost either way, so the work only pauses.
References
Sources checked August 2026- 01Highlights and Insights Report, 35th edition (marketing technology performance, n=308). cmosurvey.org (fielded January 2026)
- 02Pricing (per-client plan, unlimited users, add-ons), AgencyAnalytics. agencyanalytics.com
- 03Pricing (dashboard tiers, unlimited users), DashThis. dashthis.com
- 04Pricing (data sources, destinations and users; both billing bases), Supermetrics. supermetrics.com
- 05Pricing (two “starts from” floors, connector counts), Funnel. funnel.io
- 06Pro subscription overview (per-user licences, one Cloud project), Data Studio - formerly Looker Studio. docs.cloud.google.com
- 07Pricing (Pro plan, free-tier limits, project pausing), Supabase. supabase.com
- 08API pricing (per-million-token rates for the small models). developers.openai.com
- 09Web developer hourly rates 2026 (freelance and agency benchmarks). developex.com
- 10Row Level Security, Supabase docs. supabase.com
- 11Storage access control, Supabase docs. supabase.com
- 12Edge Functions secrets and environment variables, Supabase docs. supabase.com
- 13Plans and pricing (Pro, Max), Claude. claude.com
- 14What is the Max plan?, Claude support. support.claude.com
- 15Set up Claude Code, Claude docs. code.claude.com
- 16Models overview, Claude docs. platform.claude.com
This guide is general information, not advice about how to measure your marketing. What counts as a conversion, and which platform’s figure is authoritative when two disagree, are decisions for your own business, and a number on a dashboard is only as good as the definition underneath it. Third-party prices, plan terms, and market rates are quoted from the sources above and were last checked on the date shown. Vendors change them without notice, so confirm before you budget. Build hours and the cost estimates derived from them are our own estimates, not quotes. Claude, Claude Code, and the Anthropic API are products of Anthropic. Verify current capabilities and pricing before relying on them.