How to build a marketing analytics dashboard with v0
The screens are the part of this build v0 gets to fastest: a widget library, a board editor and six chart types are exactly its strength. The work is making sure the half underneath arrives too: an import that validates every row, six rates that cannot divide by zero, and access rules that answer who may build and who may only look.
v0
$ Build a marketing analytics dashboard: a library of charts, metric cards and tables somebody composes boards from, four filters across the whole board, six rates computed once with guarded division, a CSV import that validates every row and keeps its refusals, and three roles with per-dashboard sharing on top.
- Board and widget library built
- Real data, rates and permissions behind it
- 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 v0
v0 starts with the screens: forms, tables, dashboards, whatever the interface needs, generated in React and Next.js from a plain description. That’s the part most people notice first, because it’s fast and it looks finished immediately.
The backend isn’t automatic in the same way. v0 can write it too, meaning Next.js routes and server logic that read and write real data, but you ask for it, usually once the UI already exists and there’s something real to connect it to:
Sketch
Describe the screen or component you want.
Preview
See it rendered live, and select any part of it to adjust directly.
Connect
Add a database and the routes that read and write to it, once the UI needs somewhere real to save.
Iterate
Prompt again for the next screen or the next piece of logic.
The order matters here more than with a full-app builder: v0 gets you a finished-looking interface fast, and the data underneath doesn’t show up until you ask for it and give it somewhere to live.
One clickconnects a database
Supabase, Neon, and Upstash are integrations you add from the project menu once a screen needs to hold onto something real, and v0 provisions the credentials and writes the routes that use them.
Live preview is the feedback loop
Every prompt updates the working interface right there in the chat, so you see the actual screen changing instead of imagining it from a description.
Design mode edits without a prompt
Select an element in the live preview, adjust its style directly or type a plain-language instruction, and v0 applies the change back to the real source code as a new version.
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 v0
From scratch, with v0- v0
- $0 ($5/mo of credits) to $30+/month (Plus)
- Backend (Supabase)
- Free tier · $25/month (Pro plan)*
- Hosting (Vercel)
- Free on Vercel’s Hobby plan
- Your time
- ~103 hrs
Free to try the idea, ~$30+/month on Plus while you build a real one, then whichever plan you keep using
v0 bills in dollars of credit rather than a flat fee, so cost tracks usage: importing the pre-built template can fit inside Free’s $5 a month, while a from-scratch build runs into Free’s 7-messages-a-day cap quickly and usually needs Plus, at $30 per user per month with no annual discount shown. Because v0 generates the interface and the backend as separate steps rather than one pass, expect more prompts to reach the same result than a tool that writes both together.
* 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, v0.app, v0.app and vercel.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
Of everything in this catalogue, a marketing dashboard is the build whose surface area is most obviously screens (six widget types, an editor, a filter bar) which is why the UI-kit row below sits closer to the by-hand row here than anywhere else. Three routes: by hand, on a kit, or through v0 from the screens inward.
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.
Describe a screen and it appears, refine it in the chat, then keep going until there is a database, an import and a set of routes behind it. The same task list as the rows above it. What it changes here is which end you start from, and on this build that is genuinely useful, because whether a board is readable is a question about layout that nobody can answer from a schema.
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
v0 runs entirely in the browser too, so there’s no download and no terminal. Before step 01: sign up, pick a plan, and know that a database is one click away once a screen actually needs to save something. GitHub is the last piece, and it’s what turns the chat into a project you can hand to someone else.
v0 account
Sign in with a GitHub, Google, or email account and you land in a chat where you describe the interface you want. There’s nothing to install.
Sign up for v0v0 subscription
Free includes $5 of credits a month, capped at 7 messages a day, which is fine for trying a few screens and thin for a real build. Plus is $30 per user per month, billed monthly only (v0’s pricing page shows no annual option), and it’s the realistic floor once you’re iterating past a handful of screens.
Compare v0 plansDatabase (Supabase, Neon or Upstash)
Where your project keeps its data, added when a screen needs one. v0 generates the interface first. A database is a one-click integration you add from the project menu once a screen needs somewhere real to save to, with Supabase, Neon and Upstash as the options, and connecting one lets v0 write the routes that use it.
Connect a database in v0GitHub connection
Not needed to build anything, since v0 keeps its own history of every change. Connect a repository from the chat’s Git panel and v0 also commits every code-changing message to its own branch, ready to merge as a pull request. That is the step that turns the chat into a project other tools, and other people, can use.
Connect v0 to GitHubThe database step is the one worth not skipping once your screens need real data. Everything else here takes a couple of minutes, and after that you’re just describing what you want.
Build your marketing dashboard, prompt by prompt
The order below starts where this tool is strongest (the board itself) and then deliberately turns around. Read step 03 before you enjoy step 02 too much: a widget library over invented numbers is a mockup, and the thing that turns it into a dashboard is a real export with commas in the campaign names.
- 01
A board on screen, and two rules written down
Start with what you can see, and fix the two definitions in the same breath so they are not retrofitted later.
PromptSet up the projectStart a React and Tailwind project for a marketing analytics dashboard: a shell with a sidebar, a filter bar across the top and a grid the widgets will sit in. Use placeholder numbers for now and mark clearly in the code that they are placeholders. Then record two rules for everything that follows. 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.
Ask for the placeholders to be marked as placeholders. On a build that starts with the screens, invented numbers are the thing most likely to survive into something somebody screenshots for a client, and a comment in the code is the cheapest way to make sure they get removed rather than forgotten.
- 02
The widget library and the editor, while it is cheap
The step to spend real time on, because this is what people will judge the dashboard by and this is what this tool is best at. Six widget types, and a board somebody who is not you can rearrange.
PromptBuild the widgets and the filtersBuild the widget library: a line chart, an area chart, a bar chart, a pie chart, a metric card showing one number with its change, and a data table with sorting. Each widget stores its own metric, grouping and title, and a dashboard stores the widgets on it and their positions as data rather than in code, so somebody can add, move, resize and remove widgets and have the layout persist. Then four filters across the whole board at once (date range, channel, campaign and country) held above the widgets and passed into each one rather than applied inside a chart, and remembered between visits. Make the empty state of every widget explicit and different from zero: a widget with no data yet must not look like a campaign that spent nothing.
That last sentence is the one to insist on, and it is specific to this kind of application. Nothing and zero look identical on a chart, they mean completely different things to somebody deciding a budget, and getting the empty state right now is far easier than explaining the difference to a client later.
- 03
Turn around: real data, from a real export
Download a real month from one ad platform before this step. Everything above this line is a mockup until this prompt has run, however good it looks.
PromptImport a real exportConnect a Supabase project, then replace every placeholder number with real data. First the schema, with an organisation reference on every table: organisations. Members. Data sources carrying an import’s status and counts. A wide campaign table of one row per date, channel and campaign with impressions, clicks, conversions, cost and revenue. A campaign dimension with separate fact tables for sessions, cost and conversions. A rejects table with the line number, the raw row and the reason. Dashboards and widgets. Then the import. Here is a real CSV export from my ad platform. Read it first and tell me the exact column names, the date format, whether fields containing commas are quoted, how it writes a zero or a missing value, and anything you cannot map. Then build a private bucket restricted to CSV and scoped per person, and a server-side route that parses an upload with a parser that handles quoted fields properly rather than splitting on commas, checks the required columns, validates every row, inserts what passes and writes every failure to the rejects table. Never drop a row silently. Key rows on date, channel and campaign so a re-upload is a choice between skipping and overwriting. Then import my file and show me the accepted count, the full reject list, and the total spend of accepted rows so I can compare it with the ad platform.
Expect this step to change the screens you built in step 02, and let it. Real campaign names are longer than invented ones, real channel lists are untidier, and a table that looked balanced with six tidy rows will need work with forty real ones. That is the step doing its job, not going wrong.
- 04
The six rates, in one place, behind a query that respects the asker
Move every calculation out of the components built in step 02 and into one place, then put a single query route behind the widgets. This is the least visual step in the build and the one that decides whether two cards can ever disagree.
PromptBuild the rates and the query routeMove all arithmetic out of the components and compute the six rates in one place: click-through rate, cost per click, cost per thousand impressions, cost per acquisition, return on ad spend, and return on investment. Every one 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. Then one server-side route that answers a widget’s query, taking the widget’s stored metric and grouping plus the board’s four filters, 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 go through the widgets from step 02 and make sure not one of them still calculates a percentage, a ratio or a total for itself.
That last instruction is the whole reason this step is separate. Building screens first means arithmetic ends up in components, which is fine until two components disagree in front of somebody senior, and the cheapest moment to pull it all out is now, while you still remember which chart did what.
- 05
Three roles, and grants on single boards
The permission model, and the one part of this build that cannot be judged by looking at it. Note that the roles here are about who may edit a board, so they change what step 02’s editor is allowed to do.
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.
Then go back to the editor from step 02 and hide what a viewer cannot do. The database refusing an action is what keeps you safe. A button that does not appear is what stops a client emailing to ask why the dashboard said no, and you want both, in that order.
- 06Destination
The summary, a ceiling on it, and one channel reconciled
The written paragraph at the top of the board, the cap on its spending, the exports, and the check against the platform the numbers came from.
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 route that calls the model, and instruct it 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.
One thing to watch on a build that started from the screens: the summary is a visual element with a factual job, so it is unusually tempting to tune how it reads. Tune the layout as much as you like and leave the instruction alone. The sentence forbidding it to invent a trend is doing more for this dashboard than any amount of typography.
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.
Paste this into the chat before launch so v0 checks 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
- One screen per message, checked in the live preview before you ask for the next one
- Adding a database as soon as a screen needs to remember something, so v0 wires it up as it builds
- Clicking the thing you want changed in Design mode, rather than describing where it sits
- One feature per chat, so its history reads as a straight line you can walk back
- Sending a finished chat over to GitHub before starting the next feature, rather than stacking several together
Slows the build
- Asking for the whole app in one message instead of one screen at a time
- Building screens for data the database does not hold yet
- Describing which button to change when you could just click it
- Letting one chat run for weeks, so going back to an older version undoes everything built after it
- Approving several messages in a row without looking at the preview after each one
Connecting GitHub (Optional)
Every change in v0 is saved automatically without any technical setup. You only need to link GitHub if you want a private copy under your own control or plan to hand the codebase over to external developers.
Every message is a version
Each time a message changes your code, v0 saves it as a new version. Restoring an older one adds it back as the newest version instead of branching, so the history stays one straight line.
Versions, v0 docsUndo from the chat itself
Scroll back through the conversation and click the revert arrow on any earlier reply, or open the version number in the top right to jump straight to a specific one.
Connecting GitHub creates a real repository
From the chat’s Git panel, connect an existing repository or create one. v0 never writes straight to your main branch: every code-changing message is committed to its own working branch first.
GitHub integration, v0 docsA pull request is how it gets merged
When you’re ready, publish and open a pull request from that working branch into your base branch, review it like any other, and merge it. Starting a new chat picks up a fresh branch for the next round of changes.
This can also be v0’s deploy path
If the branch your pull requests merge into is also the linked Vercel project’s production branch, merging one triggers a production deployment. If it isn’t, the merge just follows whatever branch behavior that Vercel project is already configured with.
GitHub integration, v0 docsWhere 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.
v0 doesn’t ship its own model for these prompts, so you bring an API key for whichever provider you want, and v0 writes the route that calls it. Start with a cheap model while you’re still iterating on the prompt itself, then swap in a stronger one for the version that ships, and keep every AI feature behind that one route so there’s only one key to rotate.
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. You describe the screen you want in the chat and v0 generates the React and Next.js code behind it. Sign up, pick a plan, and connect a database once your screens need real data. The setup section above walks through each one.
Both, but not automatically at the same time. v0 generates the interface first. The backend (a database and the API routes that use it) is something you ask for once a screen actually needs to save or load real data.
Free plan credits reset monthly, and the 7-messages-a-day cap resets every day. If you hit either limit mid-build, what you’ve already built stays put. You wait for the reset or move up to Plus to keep going right away.
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
- 13v0 homepage. v0.app
- 14Pricing (Plus, Business), v0. v0.app
- 15Pricing details (Free tier, credit rollover), v0 docs. v0.app
- 16Databases (connecting Supabase and others), v0 docs. v0.app
- 17Full-stack apps, v0 docs. v0.app
- 18GitHub integration, v0 docs. v0.app
- 19Deployments, v0 docs. v0.app
- 20Design mode, v0 docs. v0.app
- 21Versions, v0 docs. v0.app
- 22Pricing (Hobby plan), Vercel. vercel.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. v0 is a product of Vercel. Verify current capabilities and pricing before relying on them.