Getting started
Analytics lets you design complete analytics dashboards visually — no hand-coding required. Open a browser, choose a data source, drop it onto the canvas, and export a production-ready file in minutes.
Your first sign-in
A welcome screen greets you (by name, when you're signed in as a real user). Its two big cards are the tours:
- Take the guided tour — spotlighted walkthroughs of the real app. It opens a picker of five topics — Getting started, Quick analysis, Build a dashboard, Prep data (Jobs) and Connections & Datasets — each a couple of minutes, with a Back button if you change your mind. The last two are separate walks, not one errand: prepping data is the Jobs tour, connecting it is the Connections-and-Datasets one. Sample packs add their own rows below those five — a pack that carries a tour joins the picker the moment you install it and leaves again when you remove it, so the list is as long as your workspace is furnished (which packs those are is noted under Sample packs). Getting started walks the left rail from the top down, group by group — Workspace, then Build, then Manage — so the tour and the rail you're looking at stay in the same order; if you have packs installed it also splices in one stop per pack, right after the welcome. Every tour brings you back to the welcome screen when it finishes, so you can take another or head off and explore.
- Take a quick tour — a short step-by-step carousel right in the dialog: what the app is, the three places you build (Quick Views, the View Builder, the Dashboard Builder), where everything you save lives (Views, Dashboards, Repository and Home), exporting a dashboard, and bringing your own data in through Connections, Datasets and Jobs — so between them the cards name every section on the rail. It's pack-aware: a workspace set up with sample packs gets one extra step per installed pack, each introducing its pack by name — so two packs mean two extra cards, not one.
Below the tours, smaller shortcuts jump straight into a section (New View, Build a dashboard, Bring your data), and a clearly-labeled Skip the tour button lets you go explore on your own. The welcome shows only once; revisit it anytime from Settings → Presentation, whose Welcome tour row carries a Take the tour button.
The left rail — three groups

- Workspace — the things you have: Home, Views, Dashboards, Datasets, Connections, Repository.
- Build — the places you make them:
- Quick Views — the fast one-chart builder
- View Builder — the full drag-and-drop canvas (shelves, pivots, calculations). Its saved results are what the Views section above lists.
- Dashboard Builder — where dashboards are assembled and exported (a.k.a. Studio, described below; visible to developer and admin accounts — a viewer opens dashboards through the read-only viewer route instead). The Dashboards section above is the catalog of what it produces.
- Manage — Jobs, Admin (admin accounts only:
who can sign in, and whether they're an admin, developer, or viewer — each role a superset of
the one below; its Section access card turns any of the other rail sections off
for the viewer role, and Home and Settings always stay — Settings is where a viewer signs out),
and the workspace-backend indicator:
- Local — your workspace lives only in this browser
- Connected — every change mirrors to your backend automatically
- Reconnecting… — a sync failed and the app is retrying on its own; click the indicator to jump to the backend card in Settings
- Pinned at the bottom, below the groups and always available: Settings — one page of cards, from your account and the workspace backend through appearance, mode and presentation to your dashboard defaults, the sample packs and settings backup; it is also where you sign out — and Help, this guide, opened inside the app rather than in a new tab.
Collapse the rail to icons-only with the toggle at its bottom. Your browser's Back/Forward buttons step through sections instead of leaving the app — Back closes an open editor, a zoomed View, or slideshow first.
Two rail items worth calling out
- Home — quick-create cards, your recent and pinned dashboards, and a small rotating tip card (click its → arrow for another tip).
- Repository — every data source and dashboard in one searchable page. The search also matches a dashboard by its columns (the card shows which column hit), so a schema change is one search from every affected dashboard. A Group chip strip narrows the grid; hovering a card reveals ✎ edit / 🗑 delete — the same editors the builder's Data panel uses, so the two views always agree.
The top bar

The top bar stays put across every section:
- Left — the name of the section you're in.
- Center — Search (⌘K / Ctrl-K anywhere): the command palette jumps to any command, dataset, or dashboard.
- Right — What's new (release feed, dotted when unread), What's next (near-term roadmap), the dark-mode toggle, the fleet app switcher, and New.
On phones the bar keeps the essentials — section name, search, app switcher, New — with the rest tucked into the rail drawer and the ⋯ More menu.
On a phone, the toolbar lives in ⋯ More
Narrow screens don't have room for the full button row, so the app hides it rather than crushing it — and that includes the builder's own toolbar, which on a laptop sits along the top of Studio. Nothing is lost: every one of those buttons has an entry in ⋯ More that does exactly the same thing. Wherever this page tells you to click a toolbar button, this is the route your phone has instead:
- ⋯ More → Undo and ⋯ More → Redo — the ↶/↷ buttons.
- ⋯ More → Open… — the dashboard picker (its footer still offers Open file… and Import from URL…).
- ⋯ More → Save and ⋯ More → Save as….
- ⋯ More → Duplicate — copy the dashboard you have open.
- ⋯ More → Export… — opens the very same export menu described under Exporting dashboards, with every format.
- ⋯ More → Close — leave the builder and go back where you came from.
- ⋯ More → What's new and ⋯ More → Send feedback — the two top-bar icons a phone drops.
Keyboard shortcuts are unaffected — if your phone or tablet has a keyboard, ⌘ S / Ctrl S and friends still work.
Quick import — file to dashboard in one drop

Home's Quick import card is the fastest way in with your own data. Drop a
.csv, .json, or .xlsx (Excel — its first worksheet is read) onto it (or click to browse) and the app:
- Profiles every column — recognizing map fields, dates, measures, and categories from names and values — and saves the file as a real dataset (a "Quick imports" file connection is created for you the first time; no setup).
- Auto-builds a real dashboard — a headline KPI plus whatever mix of bar, donut, line, and table Views the data supports, opened right in the builder. It gets as adventurous as your data allows: a map, treemap, slope chart, or ensemble view joins only when the data genuinely supports it (a map needs a real geo column; a slope chart a before/after measure pair; an ensemble a multi-provider series column). A time View only appears with a real date column, and category Views cap to their top values.
- Waits for you to Save — the quick-built dashboard shows "Unsaved — Save to keep" under its title, and opening or starting another dashboard warns before discarding it.
Files stay under 2 MB and inline (fully offline); for bigger files, host them and use the DuckDB (remote file) connector. The same affordance lives on the builder's empty canvas — an Import a file button, or drop the file straight onto the canvas.
New dashboard from Dashboards: a "+ New dashboard" button on the Dashboards page's own toolbar opens Studio on a fresh blank dashboard, next to the view toggle/Select/Compare — the same starting point as the topbar's "New ▾" menu or Repository's "+ New ▾ → New dashboard", just reachable without leaving the page.
Export/Import repository: tucked behind the Dashboards page's More (⋯) menu — handy for moving work to another browser/device, or as a manual backup:
- "Export dashboards…" opens a picker to choose which dashboards to export (defaults to all — Select all / Clear to narrow). The download is one JSON file with the chosen dashboards, the data sources you authored that they use (the bundled catalog ships with the app, so it isn't duplicated), and the relevant pins + workbooks.
- "Import repository…" merges an exported file back in — additive, never deleting anything already here. A data source with the same id is overwritten by the imported version; dashboards merge by id, keeping the newer duplicate.
Workbooks
File dashboards into named collections on the Repository page:
- Type a name next to "+ Workbook" to create one; the chip strip above the Dashboards grid then filters to that workbook (or "Unfiled").
- Each dashboard card gets a Workbook dropdown to assign or move it.
- Hover a workbook chip for ✎ rename (Enter saves, Escape cancels) and ✕ delete — deleting a workbook never deletes the dashboards inside; they just become unfiled.
- Once one workbook exists, the same chip strip appears on Home above Pinned/Recent. If an installed sample pack contributed dashboards, a "Sample packs" chip joins the strip — Home's "Sample dashboards" card jumps here with it pre-selected.
Folders (alongside workbooks)
Dashboards also file into folders — the same nested "/"-path folders your datasets, connections, and jobs use (e.g. "Finance/2026"):
- Once one dashboard is filed, a "Folders" chip strip appears on the Dashboards page — it composes with the workbook chips, so you can narrow by both.
- List rows show a folder badge and a folder button opening the picker (browse, search, or create a nested folder); tile cards carry the same chip — it reads the folder name, or "Add to folder" when unfiled.
- Folders and workbooks are independent — a dashboard can live in both. Folders are the organize-at-scale navigation; workbooks stay a lighter cross-cutting grouping.
- Workbooks can go into folders too — hover a workbook chip and use its folder button to file the whole workbook.
Select multiple / bulk actions: click Select on the Dashboards page to enter multi-select mode — a checkbox appears on every tile/row, and tapping anywhere on a card or row toggles its selection instead of opening it. A bar above the list shows how many are selected with Select all / Clear, a Move to folder… button that files every selected dashboard in one go (the same folder browser the per-row folder button opens — pick a folder, create a nested one inline, or choose "No folder" to un-file them), and a Delete button that removes every selected dashboard after a confirmation (the toast that follows carries an Undo button for a few seconds). Sample-pack dashboards can be selected and deleted individually like any other — removing one just drops it from your workspace; remove and re-add the sample pack in Settings to restore the full set. Click Select again (now labeled Cancel) to leave select mode.
Compare dashboards: the Repository page's "Compare dashboards…" button opens a picker for any two of your saved dashboards — a live preview of each renders side by side (the same render every export/preview uses, so it's a genuine "which of these looks better" comparison, not a thumbnail), with a plain-English summary of what differs between them underneath. This is distinct from a dashboard's own Version history (below), which only ever compares a dashboard against its own past checkpoint — Compare dashboards is for two different saved dashboards, e.g. judging two drafts against each other.
"Changes since you were last here": a recent-dashboard card on Home or Repository shows a small "N changes since you were last here" note whenever the dashboard has moved on since the last time you actually opened it — added a View, changed the accent color, whatever Version history's own plain-English diff would say. Hover the note for the actual list of changes. It clears the moment you reopen the dashboard (that becomes the new "last here"), so it only ever nudges you toward what's new since your last visit, not a running history.
.html, or a full bundle (.html + the editable .studio.json spec) — host it anywhere static pages live.Auto-build: click New ▾ → Auto-build to instantly scaffold a complete dashboard from any set of queries — KPIs from KPI data accesses + a chart per chartable DA, all in one click. Edit from there.
Install it: the Studio is an installable, offline-capable app — most browsers offer "Add to Home Screen"/"Install app" from the address bar. Once installed (or even just visited once), a background service worker caches the app shell so it keeps working with no connection; it always prefers a fresh copy over the network first, falling back to the cached one only when offline. If that cached copy ever gets stuck — an update banner that keeps coming back however often you reload — Settings → App → Hard reset throws it away and fetches everything fresh, without touching anything you have made. "Clear local data" (⋯ More menu) also clears this cache, but it wipes your workspace with it, so reach for the hard reset first.
Home — instant analytics
Home is where the app opens. The top of the page is a row of quick-start cards — one per way to begin — and everything below them is your own content, live.
The quick-start cards
Under the “Welcome back” greeting Home lays out a card per starting point. Clicking one takes you straight there; nothing is created until you save.
- New View — pivot and chart with shelves in the View Builder.
- New Quick View — the fast one-chart builder (Quick Views).
- New dashboard — a blank canvas in the Dashboard Builder.
- New connection — connect to your own data.
- New dataset — build a dataset on a connection you already have.
- Quick import — drop a CSV, JSON or Excel file and get a dashboard immediately (see “Quick import” above).
- Sample dashboards — curated dashboards from your installed sample packs. This card appears only while an installed pack actually contributes examples, so a workspace with every pack removed does not advertise a section with nothing in it.
- Take the tour — replay the guided walkthrough of the builder.
A viewer-role account sees only the cards it can act on: New dashboard, Quick import, Sample dashboards and Take the tour all route into the builder, which viewers never enter, so Home leaves them out rather than offering a dead click.
A rotating tip sits under the cards; the › button on it steps to the next one.
Your content, below
Each content section is hidden while it is empty — except Dashboards, which always renders, so a brand-new workspace still has somewhere to land:
- Featured dashboards — click the small house button on any dashboard card to feature it. Featured dashboards render on Home as real, scaled-down live previews (the actual dashboard engine in a view-only frame, not a static thumbnail); one click opens them in the builder. The most-recently-featured one gets top billing as a full-width hero card.
- Pinned Views (★ in Quick Views) render the same way — live charts that open back into their builder.
- Favorite datasets & connections (the ★ toggle on their catalog rows) show as compact cards — click one to jump into its editor.
- Frames render in each dashboard's own Appearance (Light or Dark, as its author set it — or your theme, if its author chose Auto) and hydrate lazily, so offscreen cards cost nothing. The full-page viewer is the one place that follows your theme instead — see "Open in viewer" below.
- Each Home section — Featured, Pinned Views, Favorite datasets & connections, Examples and Dashboards — has a ▲▼ control in its heading; reorder them to taste and the order sticks between visits. Moving a section swaps it with its nearest section that is actually on screen, so an empty one never blocks the move.
- The Dashboards section holds your own dashboards in two strips: Pinned — the ones you pinned from a dashboard card, which is a different thing from the Pinned Views section above — and Recent dashboards, the ones you opened or edited most recently.
- Once you have workbooks (Repository → Workbooks) a chip strip appears above the sections and narrows the Dashboards section to one of them: All, a chip per workbook, and Unfiled for everything not in one, each with its own count. It filters the view only — filing a dashboard into a workbook stays Repository's job.
- Examples shows sample dashboards from the packs you actually have installed (Settings → Sample packs) — removing a pack removes its cards. With more than one pack installed the cards group under a heading per pack, each with its own "+N more" into Dashboards; a single pack shows as one flat strip.
- Clear recents — the small button on the "Recent dashboards" heading empties that strip for you, on this device. Nothing is deleted: every dashboard stays in Dashboards and Repository, pinned cards stay put, and any dashboard you open or edit afterwards shows up in Recents again.
In Simple mode the app boots to Home whenever you have featured content, and to Quick Views when you don't; your own last-used section always wins.
Open in viewer (the eye icon)
Every dashboard card or row carries a small eye icon — it opens the dashboard read-only, full-page, in a new tab: the same live, interactive renderer (filters, cross-filter, provider toggles all work) with none of the builder chrome. Handy for sharing with someone who should read a dashboard, not edit it.
Light or dark in the viewer follows you, not the author. A dashboard's Appearance setting (Dashboard inspector → Appearance: Light, Dark or Auto) decides how the dashboard looks as a finished piece — but inside this app the viewer renders it in your light/dark theme instead, because a dark app wrapped around a light dashboard just looks broken. Nothing about the dashboard is changed: switch your own theme and the viewer follows on the next open. Files you hand out are untouched by this — a downloaded Dashboard (.html) and a PDF always look exactly the way the author set Appearance, wherever they're opened, so what lands in someone's inbox or on a website is predictable — unless the author picked Auto, which is the one Appearance that asks the file itself to adapt (see "Appearance" below). The builder's live preview also keeps showing the authored Appearance, so you can see what you're making.
How dashboard filters find their data: a filter applies to a View when that View's dataset either declares the filter as a query parameter (live SQL engines substitute it server-side) or simply has a column with the same name — the rows are then filtered to the picked value. A filter named Since … keeps everything from the picked value onward (so "Since year 2020" shows 2020 to today, not just 2020). This works the same on live data, file datasets, and sample data.
- A private dashboard's viewer link only works for its owner (or an admin).
- A viewer-role account clicking any dashboard lands here directly — the builder isn't open to them at all.
The viewer's top bar
Left to right it carries a back link to Analytics, the dashboard's title, a Viewer — read-only badge that says plainly what this page is, and these actions:
- Save a copy (offered to everyone, viewers included) forks the dashboard into a brand-new one you own, without touching the original.
- Export (also offered to everyone) downloads what you are looking at — the three formats are below.
- Edit in Dashboard Builder — a developer or admin also gets this button, which jumps straight into the builder on that exact dashboard. A viewer-role account never sees it.
On a phone the bar goes icon-only. At 640px and narrower the bar has no room for all of that, so the Viewer — read-only badge is dropped and every button loses its text label — Save a copy, Export and Edit in Dashboard Builder become their icons alone, each keeping its name as its tooltip and its screen-reader label. Nothing moves into ⋯ More here: that is the builder's toolbar (the toolbar lives in ⋯ More), and the viewer has no ⋯ menu at all.
That Export button offers three of the formats Studio does: Dashboard (.html) — a self-contained file you can drop on any static host or open offline — PDF (print), and the Editable spec (.json). The office formats and the bundle are the builder's Export ▾ menu only (see "Exporting" below). A sample or demo dashboard exports with its data baked in, so the download is complete on its own; a dashboard backed by a real data source keeps querying that source live, in the viewer and in the exported file alike.
Quick Views (Explore) — dataset-first Views
Quick Views (left rail, Build group — the section previously named Explore) is the fastest path from data to chart, built for non-experts:
- Pick a dataset on the left — your workspace datasets first, sample data below.
- See its rows as a table, choose a chart, adjust the column mapping — the live preview is the real dashboard renderer, so what you see is exactly what a dashboard shows.
- Give it a name and Save View.
The dataset picker is a real navigator, not a flat list: datasets group into their folders (nested via "/"), sample data groups by set (collapsed once you have data of your own), and every branch collapses with a click. Searching flattens the tree to plain matching rows — finding something by name never depends on knowing where it's filed.
- Views are reusable. Saved Views appear in the left list here, in the builder's Data panel under Views (click + Add to dashboard or drag one onto the canvas), and — when pinned with the ★ — as cards on Home.
- Adding a result to a dashboard is unambiguous. The Result step's + New dashboard button drops your chart into a brand-new dashboard; Existing dashboard… opens a picker of your saved dashboards and adds it to whichever one you choose. Either way it's saved as a View first if you haven't already.
- Self-contained. A View embeds its data access, so it keeps working (with sample rows) even if the original workspace dataset is later deleted.
- Every scale of chart. The chart chips include the everyday types plus the US choropleth map (county / state / USDA district / watershed / congressional district / ZIP code) and the Ensemble common-estimate chart. Quick Views deliberately offers only the handful of per-chart options that decide what the chart is, and they sit right under the column mapping: Region scale and Renderer on a map (Built-in vs Interactive GL — pan & zoom at any polygon count), and Reference series on an Ensemble chart (a series drawn for context that never joins the estimate). Each is saved with the View. Every other option a chart type has — a GL map's zoom/pan cluster and which corner it docks in, colour classes, value formats, a View's height — lives in the Dashboard Builder's inspector, so save the View here and open it there when you want to tune it.
- Workspace datasets run live for the table preview and the chart; if a live run fails, Explore falls back to typed sample rows and says so.
- Rollups (group-by aggregation). Under the column mapping, the Rollup control lets you aggregate the value column with Sum, Mean, Median, Min, Max or Count, grouped by two dimensions at most (Group by / Then by) — so a detail dataset becomes a chart of totals or averages without building a Job first. It starts on No aggregation (raw rows), which charts the dataset as it comes. The rollup is saved onto the View, so it re-aggregates the same way on Home, in any dashboard, and in the exported HTML. It is not offered on every chart. A rollup aggregates one measure by category, so the control appears on the single-measure category charts — Bar chart, Line / area, Donut / pie, Treemap and Table, and is hidden on Scatter / bubble and Heatmap (pivot), which position more than one measure at a time and would collapse if one were grouped away, and on Map (US choropleth) and Ensemble (common estimate), which do their own aggregation. Those four chart their rows as they arrive; roll them up in a Job first if you need totals.
- In Simple mode, Quick Views is the default section on first open when you have no featured content on Home; your own last-used section always wins.
- New dataset without leaving Explore. The + New button next to the dataset search opens the same dataset editor the Datasets section uses — save it and Explore selects it immediately, ready to chart.
View Builder — pivot and crosstab, no code

Views (left rail, Build group) is Quick Views' power-user sibling: a visual pivot/crosstab query builder.
The dataset navigator (left pane)
- Workspace datasets group into their folders (nested via "/", each branch collapsible, sorted alphabetically); sample data groups by set and collapses once you have datasets of your own.
- Search flattens the tree to plain matching rows; every row shows an icon and the dataset's name over its connection (full name in the hover tooltip).
- Hover a workspace dataset for ✎ edit / ⧉ copy / ✕ delete, and + New opens the same dataset editor the Datasets section uses — your new dataset is selected the moment you save it.
Fields and shelves
Click a dataset and its columns appear as chips — # marks a numeric
field, a a text one. Click a chip (or drag it) onto the Columns
shelf and the result table renders live in the middle. Numeric fields aggregate automatically as
SUM — the little aggregation badge on the shelf chip switches to AVG, MIN, MAX,
MEDIAN, or COUNT. With only dimension fields on the Columns shelf you get a plain selection of
those columns; add a measure and the dimensions group it. Some numbers aren't quantities — a
FIPS code, a year, an ID — so the aggregation dropdown also offers CATEGORY,
which groups by that field instead of summing it; id-like numeric columns (ending in
_fips, _id, or plain id) default to Category the first
time you drop them, rather than SUM.
When you save, the Title on dashboards field names the View where it sits on a dashboard — it defaults to (and keeps tracking) the View's name until you set your own.
Drop a field on the Rows shelf (or use a chip's ⇄ button) and the table pivots into a crosstab:
- Rows fields run down the side; the first non-aggregated Columns field spreads across the top; measures fill the cells.
- A Total column aggregates each row (COUNT of rows when no measure is picked).
- Workspace datasets run live through their connection where possible; otherwise the result carries the same sample rows badge used everywhere else, so shape-preview data can never be mistaken for yours.
- Shelf pills are draggable: drag one onto another shelf to move it (converting automatically — a measure dropped on Rows loses its aggregation, a field dropped on Filters becomes a filter), or onto a sibling pill to reorder within a shelf.
The chart strip above the result switches how it draws: Table (the full pivot), Bars, Stacked bars, Line, Stacked area, Donut, Heatmap, Sankey, Map, Scatter, Quadrant, or KPI. Charts draw your computed numbers through the real dashboard renderer — Bars and Donut chart the first dimension against the first measure (COUNT of rows when no measure is picked), and Heatmap lights up when you have a field on Rows plus a plain field on Columns, drawing exactly the crosstab as a color matrix. A chart that needs something you haven't added yet is simply disabled, with the reason in its tooltip.
Sankey reads the same two shelves as the Heatmap, in flow terms: the field on Rows is where the value comes from, the plain field on Columns is where it goes, and the measure beside it is the width of the ribbon. So "which agency paid which contractor, and how much" is a Rows field, a Columns field and a SUM — the same pivot the Heatmap draws as a colour matrix, drawn as flows instead. Filter the measure to keep it readable: a diagram with a few dozen ribbons is a picture, one with several hundred is a hairball.
The chart canvas fills to the bottom of the screen by default (like the datasets pane next to it) and is resizable: drag the slim bar under it to make it taller or shorter, or the bar on its right edge to make it narrower or wider. The size you set sticks across visits; double-click a bar to snap that direction back to automatic (fill to the bottom / full width). On a phone the canvas just fills the width — no drag bars.
The chart fills the canvas. Drag the canvas taller and the chart is redrawn at the new height rather than leaving an empty band underneath — it redraws when you let go of the bar, not on every pixel of the drag. The canvas is a preview viewport, though, not part of the View: it is one setting for this browser, shared by every View you open here, so saving a View keeps that View's own height and never picks up the size you last dragged. To change the height a View is stored and exported at, set it where every View's height lives — Height (px) in the dashboard builder's Inspector, or by dragging the View's bottom edge there.
The Datasets pane resizes the same way: drag its right edge anywhere from 200 to 480px when long dataset names get cut off — the width sticks across visits. The chevron in its header collapses the whole pane to a slim vertical strip (more room for the canvas); click the strip to bring it back.
Drop a file to start instantly. Drag a CSV, TSV, JSON, or Excel (.xlsx) file anywhere onto the View Builder: it becomes a real dataset (it appears in your Datasets list like any other) and the builder inspects the columns and opens the most interesting view on the canvas — a map for geographic ids, a trend line for time data (color-split when a small category column exists), bars for categories, or the raw table when nothing pairs up. The shelves arrive pre-filled, so keep editing from there or hit Save View.
Switching datasets never loses your work. Each dataset keeps its own work-in-progress draft: click another dataset and your shelves, filters, calculations, and chart pick are stashed; click back and they return exactly as you left them (drafts survive a reload too). Datasets carrying a draft show a small dot in the pane, so you can build on several and walk through them one by one. The Clear canvas button at the right end of the chart strip resets just the current dataset to a clean slate.
KPI is the odd one out: no dimension at all, just one rolled-up headline number (Save adds it to a dashboard's KPI row, not its View grid). Put any field on a shelf — a measure aggregates across every row, or a plain field falls back to COUNT — and it collapses the whole filtered result down to a single value, the same grand-total rollup the Table chart type would show with nothing on Rows and one measure on Columns.
Scatter needs two measures instead of one: put a dimension on Rows or Columns plus two or more numeric measures on Columns, and it plots one point per dimension value — the first measure on the x-axis, the second on the y-axis. It's the same one-dimension basis Bars and Donut use, just widened to carry two measures instead of one, so it's disabled until a second measure lands on the shelf. With Scatter picked, a Trend line checkbox appears next to the chart types: it draws the least-squares regression line through the points (the dashed line), and it saves with the View like any other setting — a View authored with one opens here with the box already ticked.
Quadrant takes the same two measures and splits the plot into four labelled zones, colouring each point by the zone it lands in — the "where does this one sit?" read that a plain scatter leaves to the eye. It splits at the midpoints here; the thresholds and the four zone labels are edited in the Dashboard inspector, and a View that already has its own keeps them when you open and update it from the builder.
Map draws a US choropleth from your geographic field (region ids —
FIPS county code, state FIPS or postal code, USDA district, watershed/HUC8, congressional
district, or ZIP) against the first measure — the geo-looking field wins the map's id role from
either shelf, so a year sitting first doesn't steal it. The Region scale picks
itself from that field's name and values (a state_fips column opens as States, a
huc8 column as Watersheds) — the Auto entry in the Region-scale
control next to the chart types shows what it inferred, and picking a scale there overrides it.
Renderer and styling still live on the saved View itself like any other map. Put a field on the
Color shelf and the map switches to per-region series instead: a
long table of region × series × value that plugs straight into the Studio ensemble channel, so an
Ensemble chart's provider toggles elsewhere on the same dashboard re-color it live.
Line, Stacked bars, and Stacked area all draw multiple series when the shelves support it: pivot a field on Rows against a plain field on Columns (a crosstab) with one of the three picked, and every Columns value draws its own line / bar segment / band against the Rows field. Or skip Rows and put two or more measures on Columns — each measure draws as its own series, grouped by your one dimension. Anything richer (two Rows fields, or a crosstab with more than one measure) still falls back to a single series on the first dimension and measure — the same one-dimension basis Bars and Donut use.
The Filters shelf narrows the source rows before anything computes — the pivot, every chart, and the row count in the status line all see the same filtered set. Add a filter by dragging a field there or with the + Add filter picker: a text field filters by a searchable value checklist (All/None + pick what you want), a numeric field by a min–max range (leave a bound blank for open-ended). A freshly added filter shows all and changes nothing until you edit it; active filters highlight their chip and the status line reads "N of M source rows (filtered)".
The Color shelf makes color a first-class encoding, not just a chart-type default: drop a category there (or pick one from + Color by…) and Bars gets a distinct color per bar instead of one flat color — the same per-category coloring Donut already draws by default. With nothing on the Columns shelf, dropping a field on Color also splits a Line chart into one line per category, standing in for a Columns split field. Alongside it, a palette picker swaps the chart's color family — the same presets the main Dashboards builder's "Series palette" control uses — so a View can preview in a different color scheme without leaving Build.
Calculated columns: the + calc… chip at the end of the
selected dataset's column list opens an editor with a fresh blank row ready, where you define new
columns from formulas — =[revenue] / [acres], =([a]+[b])*100, or the
running helpers =pctChange([sales]) and =movingAvg([sales], 3). It's the
same safe formula language the Studio data-source editor uses. A calculated column appears in the
outline with an = marker and from there behaves like any other field — put it on a
shelf (numerics default to SUM), filter by it, chart it. To change one, click the
✎ on the column itself — it opens the same editor with that formula
focused, and it stays available after the column is on a shelf. Renaming a column there keeps it
on the shelves and filters that use it. A calculated column can't shadow a real column's name, and
deleting one cleanly removes it from any shelf or filter that used it.
Save View (top right) saves the result as a View with whichever chart type is selected — filters and calculated columns included — and it appears in the Views section like any other. Opening it from there returns it to Build with its dataset, shelves, filters, calcs, and chart restored (Views made in Explore keep opening in Explore). The Save dialog offers a ✨ name-suggest button and a Folder field — type a new folder, pick one from the list of folders already in use, or click Browse to navigate the existing folder tree, the same picker every other Folder field in the app uses.
A saved View Builder View shows its real computed result everywhere it's used: dropped on a dashboard, pinned to Home, or opened in Quick Views, the app re-runs the View's dataset, filters, and calculations and renders the actual numbers — not placeholder sample rows.
Sample packs
Sample packs (installed from Settings) are ready-made demo content you can install or remove. A pack can add dashboards, datasets, connections and jobs — sample content, never your real data — and Remove takes back exactly what Install added. Everything a pack adds is filed in the pack's own folder (Conservation Insight, Data Management, Market Coverage, Federal Contract Awards, Campaign Finance, Where America Moved) across every section, and its raw sample tables only appear in the dataset pickers while the pack is installed — an uninstalled pack leaves no trace. The packs are the only sample-content switch there is — Settings used to carry a separate "Sample content" mode toggle that hid everything at once, and it is gone: remove the packs you don't want (or all of them, for a completely empty workspace) and their dashboards, datasets and raw sample tables go with them. Nothing you made is touched.
Where a pack's data comes from is on its card. Every pack states its source in one line under its description. Conservation Insight and Data Management read "Data: synthetic — generated in the app, not real observations", because that is exactly what they are. A pack built on a real public dataset names the dataset, its licence and the date it was retrieved instead, and repeats that line in the subtitle of every dashboard it installs, so the attribution travels with the work even after you export it.
Removed the wrong pack? Undo. Remove shows its toast with an Undo button for a few seconds. One click restores every dataset, View, dashboard, connection and job the pack owned — under the same ids, with any edits you'd made to them intact — and marks the pack installed again.
- Conservation Insight is an illustrative sample for a county-level cover-crop and conservation-tillage ensemble use case. Installing it seeds two connections (a demo file source and a demo Supabase repo), eight datasets — one of them a raw provider CSV to try a mapping workflow on — a county-to-state rollup job, four pinned per-practice Views built in the View Builder (open one from the Views list and it lands on the shelves — adoption by year, split by provider, filtered to its practice), and six dashboards in a Conservation Insight folder: a featured multi-View dashboard pairing the practices with a county choropleth, a Watershed Map (a full-width HUC8 choropleth), CRD Cover Crop Data, OpTIS Cover Crop Trends, a Provider Ensemble reference, and the Conservation System Metrics wheel — all live on Home the moment you install it. All of its data is synthetic and labeled as illustrative. The featured dashboard also demonstrates two different filtering styles: a plain filter bar (Practice, Since year) that narrows individual Views, and a linked cross-filter — click a provider on any adoption-trend chart's legend and every choropleth on the dashboard re-colors together. Installing it also adds eight extra showcase dashboards (a practice-adoption scorecard, a crop/practice flow view, a watershed-scale adoption view, a program cost-share ROI view, a provider-agreement-over-time view, a county-level outlier-detection view, a year-over-year practice-switching view, and a richtext-led narrative overview rolling all of it up, each with their own filter bar too, save the overview) to both your Dashboards screen and Home's sample gallery — removing the pack takes all fourteen dashboards back out again, so the workspace doesn't get cluttered for everyone else. The pack also carries its own guided tour, which appears in ⌘K → Interactive tutorial once it is installed.
- Market Coverage is the pack built on real data: US Census county business counts and demographics, embedded in the app rather than fetched. It seeds one connection over three datasets — the two committed Census extracts covering 1,813 counties (who lives there; the businesses already trading there) and the output of the prep job that joins them into a restaurants-and-grocers-per-10,000-residents rate — plus four pinned Views (supply, demand, the two plotted against each other, and the shortlist itself) and three dashboards in a Market Coverage folder: the whitespace maps (restaurant supply by county, its grocery twin, and income plotted against supply), the demographics behind them (income, age, education), and a shortlist of counties with the income but not the restaurants. The shortlist's two rules are ordinary filters on the View — open it in the builder and you can move them. The pack also carries its own guided tour, which appears in ⌘K → Interactive tutorial once it is installed.
- Federal Contract Awards is the pack about where public money goes:
USASpending.gov's record of federal contracts obligated in fiscal year 2025, embedded in the
app rather than fetched. It seeds one connection over five
datasets — the totals for the 25 largest awarding agencies (99.97% of the
year's contract dollars), what each of them bought by NAICS industry, who each of them paid,
contract spend by congressional district, and the output of the prep job that
joins a vendor to its agency's total so one contractor's haul reads as a share of the agency
that paid it. The two flow tables carry the agency code and not its name on purpose:
bringing the readable name and the agency total across is exactly what the job is for. On top
of them sit three dashboards in a Federal Contract Awards
folder: Where the Money Goes (two sankey flow diagrams — agency to contractor and
agency to industry — plus the contractors that took a tenth or more of the agency that paid
them), Who Spends It (the 25 agencies by size, and the very different ordering you
get when you rank them by how much of their spending reached a small business), and
Where the Work Lands (the congressional-district map of place of performance, with
the ranked list beside it — federal contract money is concentrated enough that a map's
evenly-spaced colour classes put most of the country in one band, which the dashboard says
out loud rather than dressing up). Four pinned Views come with them — the two
flows as real sankeys, each agency's small-business share, and every district by dollars and
per resident — and they are ordinary View Builder Views: the billion-dollar floor on a flow is
a filter chip you can drag off, and the two derived numbers are calculated columns whose
formulas are on the shelf. Every
filter behind the numbers — contracts only, one closed fiscal year, the top 25 agencies and
the top 12 industries and vendors within each — is written down in
tools/pack-extract/contractawards.mjsand in the pack'sSOURCE.json. - Campaign Finance is the pack about who funds federal politics: the
Federal Election Commission's own record of itemized individual contributions in the closed
2023-24 election cycle — $6.5 billion of them — embedded in the app rather than fetched. It
seeds one connection over nine datasets — giving by donor
state, the fifty largest recipient committees, every donor state each of those committees drew
from, the top 200 occupations and the top 200 employers, twenty-four months of giving by kind
of committee, contributions banded from under $50 to the per-election limit, and the outputs of
two prep jobs: the first joins a donor state to the committee that received it, so
one state's giving reads as a share of that committee and as the share that came from outside
the state the candidate is running in, and the second keeps the flows of $200,000 or more —
97.7% of the money and all fifty committees — because a View reads a dataset's first 2,000 rows
live and the full table is 2,658, so the pack trims by a rule you can open rather than losing
the tail silently. The flow table carries the committee id and
not its name on purpose: bringing the readable name, the party, the seat and the committee's
own total across is exactly what the job is for. On top of them sit three
dashboards in a Campaign Finance folder: Who Funds
Whom (the donor-state-to-committee sankey, how much of each Senate candidate's itemized
money came from the state they are running in, and the pairs where one state supplied a fifth
or more of a committee), Where the Money Comes From (the donor-state map, with the
small-gift and max-out shares as calculated columns you can open and edit), and Who Gives
It, and How (occupation and employer, a marimekko of cheque size against the kind of
committee that received it, and the twenty-four-month arc of the cycle). Four pinned
Views come with them — the flow as a real sankey, each Senate campaign's
own-state share, giving by donor state on the map, and every state with both derived shares
as a table — and they are ordinary View Builder Views: the $10M floor on the flow is a filter
chip you can drag off, and the small-gift and max-out shares are calculated columns whose
formulas are on the shelf. The pack also carries its own guided tour, which appears in
⌘K → Interactive tutorial once it is installed.
Two things to read it with, both of them in the data rather than around it.
First, it is itemized giving: a committee reports a donor once their cycle total
passes $200 and reports everything below that as one unitemized lump, so the small
contributions here are small gifts from donors who crossed that line — not the small-dollar
donor universe, which this source simply does not contain. Second, every recipient committee
is counted and not just the campaigns, which is why
committee_typeexists: the two sides raised through different plumbing — joint fundraising committees that transfer onward on one, earmarked money itemized straight against the campaign on the other — and filtering to candidate committees would have shown a landslide that did not happen. No donor names and no addresses are in the pack: every table is an aggregate, and the FEC's bulk file carries no street address in the first place. Every filter behind the numbers is written down intools/pack-extract/campaignfinance.mjsand in the pack'sSOURCE.json. - Where America Moved is the pack about migration, and about the money that
moves with it: the IRS Statistics of Income record of where American households moved between
filing years 2022 and 2023, embedded in the app rather than fetched. Because the IRS matches
each year's returns to the previous year's, it can publish something no single-county table
can — not just how many households left one county for another, but the aggregate income that
left with them. It seeds one connection over seven datasets —
every county's arrivals and departures (3,087 of them), the same for the 50 states and DC plus
the households that stayed, the 300 largest state-to-state corridors, the biggest
county-to-county corridors out of each state, and the outputs of three prep jobs:
the first derives each county's net position in households and in dollars and sets the average
income of its arrivers against its leavers, the second joins each corridor to the state it
left, so a corridor reads as a share of everyone who departed that state and its movers' average
income can be set against the people who stayed put, and the third keeps the 1,782 counties
where at least a thousand households arrived or left — 96.5% of every move in the extract —
because a View reads a dataset's first 2,000 rows live and the county table is 3,087, so the
pack trims by a rule you can open rather than letting a national map stop halfway across the
country. On top of them sit three dashboards in a Where America
Moved folder, split by grain rather than by topic because the two grains measure
different universes: Who Is Winning Households (county grain — the net-migration
choropleth, the arrivers-versus-leavers income gap as its twin, and the counties at both ends
of both), The Corridors (state grain — the state-to-state flow, the corridors carrying
a sixth or more of the state they leave, and the biggest county-to-county moves), and
Did the Money Move With Them (state grain — net adjusted gross income by state as a
calculated column you can open and edit, and the households on each corridor set against the
ones who stayed put). Four pinned Views come with them — net migration by
county, the corridors as a real flow, the income that changed state, and every corridor's
movers against the stayers of the state they left — and they are ordinary View Builder Views:
each names its grain in its own title, the corridor floor is a filter chip you can drag off,
and the net-income column is a calculated column whose formula is on the shelf. The pack also
carries its own guided tour, which appears in ⌘K → Interactive tutorial once it is installed.
Three things to read it with, all of them properties of the source rather than the
extract. First, the unit is a tax return, not a person: a "move" here is a household
that filed from a new address, and the pack counts US moves only, so a coastal county is not
credited with arrivals from abroad it has no measured departures to balance. Second, income is
aggregate adjusted gross income in thousands of dollars, exactly as published — every
column carrying it is named with a
_agi_ksuffix and nothing rescales it. Third, the two grains deliberately do not add up: a county's total counts every US move including moves within its own state, while a state's total counts only moves across state lines, so summing a state's counties will not reproduce its state row. Every filter behind the numbers is written down intools/pack-extract/countymigration.mjsand in the pack'sSOURCE.json. - Data Management & Governance is the built-in generic showcase gallery (Data Governance, Data Platform Operations, Product Delivery, Finance, Marketing, Incident Response, Compliance, Data Quality, Pipeline Observability, Storage Footprint, Cost & Sustainability, and the Interactive Feature Tour) folded into a toggleable pack, installed by default so the gallery looks exactly as it always has. Unlike Conservation Insight, this pack adds no connections, datasets, or jobs — Install/Remove shows or hides those twelve gallery entries and adds or removes them from your Dashboards screen, so turning it off just declutters both for a pitch that wants to stay focused on Conservation Insight alone.
Jobs — prep & rollup
Jobs (left rail) prep one dataset before it's charted: rename or cast a column, derive a new one from arithmetic on two others, filter rows, roll everything up with a group-by aggregate, and join or union in a second dataset. Run a job and the result is saved back as an ordinary dataset — ready for Quick Views, the builder's Data panel, or any dashboard — and re-running the same job updates that dataset in place, so an annual data refresh is a single click.
- Steps run in order and can be reordered or removed; Preview runs the source dataset live and shows the pipeline's output before you save.
- A source field list sits above the steps, showing every column on the source dataset with a best-effort type icon and color (Numeric/Date/String, guessed from the column name) — see what's available before you start picking columns.
- Sample rows and an output preview sit below the field list: a small live sample of the source dataset's real rows, and an approximate preview of what the output looks like after the current steps run — both update instantly as you edit steps, no Preview click needed. Join/union steps only know the linked dataset's column names here, so their preview rows are honestly approximate; click Preview for the authoritative result. Editing any step afterwards clears that real Preview result (it's now out of date), so you only ever see one preview at a time — hit Preview again for a fresh authoritative run.
- Column fields are dropdowns — group-by, rollup-metric, join/union key, and the Filter / Rename / Cast column fields are all picked from each step's real, known incoming columns rather than typed by hand, so a mistyped or wrong-case column name can't slip through. A dataset that hasn't been queried yet is probed live in the background the moment you open the job editor, and the dropdowns fill in as soon as its columns are known; a column you'd already set stays selectable even if the list has since changed.
- The same filter operators as everywhere else — a Filter step offers
= equals, ≠ not equals, > greater than, ≥ greater or
equal, < less than, ≤ less or equal, contains (text),
starts with and in date range (relative): the identical list, wording and
behaviour you get from a dataset's own
output filter rules. Equals compares as numbers when both sides are numbers (so
1.0matches1) and as text otherwise; the greater/less-than operators order numbers numerically and text alphabetically; contains and starts with ignore capitalisation. - Dates compare as dates — write a date as
2024-06-01(or a full timestamp like2024-06-01T09:30:00Z) and both sides are compared chronologically rather than as text, in filter rules and in sort rules alike. A plain date means the whole day, so= 2024-06-01matches every row stamped that day whatever the clock says, and≤ 2024-06-01includes all of it. Values without a timezone are read as UTC on both sides, so what a filter matches never depends on where the reader is. Anything that isn't a real calendar date in that shape keeps comparing as a number or as text, exactly as before. - Pick a date, or a moving window — on a date column the value box is a real date picker rather than free text, so you never have to remember the format. For a window that should still be right tomorrow, choose the in date range (relative) operator: the value box becomes a list of ranges — today, yesterday, the last 7 / 30 / 90 / 365 days, this or last week / month / quarter / year, and year to date. The range is saved as the rule itself, so a dataset filtered to "the last 30 days" means the 30 days ending on the day it is opened, on the reader's own calendar (weeks start Monday). A row whose value isn't a date is left out of a date range.
- Filter values suggest themselves — once a Filter step's column is picked, its field's known sample values (drawn from the same live source-row sample as the previews above) appear as a dropdown of suggestions in the value box; you can still type any value by hand (needed for greater/less-than comparisons), the suggestions are just a shortcut when the column has a small, known set of real values.
- A small diagram under each rollup, join, or stack (union) step shows the operation visually — columns going in, the operation in the middle (group-by columns for a rollup, the join key/type for a join, the other dataset for a stack), and the columns that come out — so the shape of what you're building is visible at a glance, not just the raw fields.
- Multiple rollup levels — add a second (or third) aggregate step to roll up an already-aggregated result again (e.g. sum acres by county+year, then average that across years for a county-only figure); each aggregate step's group-by/metric dropdowns show the columns as they exist at that point in the pipeline (the previous step's output), not the original source dataset's columns.
- Rollup metrics include sum, average, count, median, and a weighted mean — the honest way to aggregate a percent metric from county up to State, USDA Crop Reporting District, or HUC8 watershed: a flat average of percentages misrepresents counties of very different size, so pick an acreage (or similar) column as the weight.
- Join adds a second dataset's columns onto matching rows by a key column (inner drops unmatched rows, left keeps them with blanks); added columns that collide with an existing name get an auto suffix, or set a prefix yourself.
- Union stacks a second dataset's rows onto the pipeline's existing schema — the normalize-and-stack case for combining several differently-shaped sources (e.g. five providers with five different raw column names) into one common table. Each output column maps from a column in the other dataset (or falls back to a same-name match, else blank).
- Add unique row ID stamps a stable, unique id onto every row currently in the pipeline — place it after a join, union, or aggregate step to key the rows at their final grain (an aggregate collapses many source rows into fewer output rows, so an id added before it wouldn't match the shape you actually save).
- Custom SQL runs an arbitrary query against the pipeline's rows so far
(table
t), via an in-browser DuckDB engine loaded the first time a job uses it — useful for anything the built-in steps don't cover directly. - Refresh reminder (weekly/monthly/quarterly/yearly, optional) flags a job ⏰ Refresh due on the Jobs list once it's overdue against its last run — a hint, not real scheduling (there's no server to run a cron; the app is static and client-side), so it only shows up next time the list is open. Handy for the annual-refresh case: set it once and the list tells you when it's time to click Run again.
The builder
The workspace is divided into three resizable panes. Each pane can be collapsed to a labeled rail by clicking the ‹ / › collapse button.
Polecat apps switcher: the app bar (top of the screen, just left of New ▾) carries a waffle (3×3 grid) button that opens the Polecat suite switcher — one-click jumps to the other apps in the family (Chat, JobTracker, AutoSelector, Relay, Games, Manager, Model Server), with this one highlighted as the app you're already in. It's the same switcher every polecat.live app carries, themed to whichever Studio look you're using.
What's new: the What's new button in the top bar — present on every section, not just in the builder — opens the full release feed in a right-hand pane: every version, newest first, with live search (matches highlighted) and Central-time stamps. A small dot on the button means there are releases you haven't seen yet; opening the pane clears it. Click the button again, the ✕, Escape, or the backdrop to close it. On a phone the top bar gives that icon's room to the section name, so the route there is ⋯ More → What's new — see On a phone, the toolbar lives in ⋯ More.
Three panes
- Data (left) — All data accesses organized by group. Search by name, table, or column keyword. Drag any card onto the canvas to create a View. In Advanced mode the header's + New ▾ adds a workspace dataset, a connection, or a dashboard-only query of your own.
- Live preview (center) — The real dashboard rendered in an iframe. This is byte-identical to what the export produces — no approximation. Click a View to select it; drag the title bar to reorder; drag the right edge to make it wider, or the bottom edge to make it taller — as tall as the whole screen if you like. The height sticks and carries into the viewer and every export.
- Inspector (right) — Contextual editor for whatever is selected. When nothing is selected, shows the dashboard-level controls. Click a View → View inspector. Click a KPI tile → KPI inspector. Click a filter → filter inspector. Click the title banner itself → Header inspector.
The builder opens clean: both side panes start closed so the dashboard gets the full width — pop them open with the edge chevrons when you need them. Prefer them always open? Flip Open the builder with side panes in Settings and every visit starts that way.
The View inspector is context-aware: interaction sections like Drill-through, Detail drawer, Cross-filter, Conditional formatting, and Color scale only appear when the selected chart type actually supports them (e.g. Conditional formatting shows for Bar/Donut/Treemap/Lollipop but not for Table or Line — clicking a table row instead opens the Detail drawer). This keeps the inspector focused on settings that do something for the current chart.
Renaming a dashboard: the dashboard title in the toolbar above the live preview renames in place — click it and it becomes a text field (Enter or clicking away commits, Escape cancels). The Dashboard inspector's Title field is the same value, edit whichever is closer. The Title (the display name shown in the dashboard's header/banner) and the File name (stem) (the lowercase-with-dashes name used for exported files, e.g. my-dashboard.html) are two independent fields right below it, so you can rename either without the other changing.
Header logo: the Dashboard inspector's Header logo field (below Subtitle) uploads a PNG/JPG/SVG (up to 200KB) that replaces the default "P" mark in the dashboard's banner, in both the live preview and the exported Dashboard Framework — a per-dashboard brand mark, separate from the app-wide rail branding in Admin. Leave it blank to keep the default mark.
Header link: the Header link URL field right below it makes the logo + title in the banner clickable, opening that URL in a new tab — handy for linking back to a company site or portal. Leave it blank to keep the banner as plain, non-clickable text.
Appearance (Light · Dark · Auto): the Appearance picker in the Dashboard inspector decides the light/dark of this one dashboard — in the live preview and in everything it exports. Light and Dark bake that choice in: the file looks the same everywhere it's opened, which is what you want for a deliverable that lands in an inbox or on a website. Auto (match the reader) bakes no theme at all — the dashboard follows whoever opens it: the page it's embedded in when it can read that page's theme, and otherwise the reader's own system light/dark setting. It's exactly the same file either way; it just decides when it opens. New dashboards start on Light — Auto is something you turn on deliberately.
Show dashboard header (embed mode): the Header toggle in the Dashboard inspector. Turn it off and the entire title banner and description bar disappear from both the live preview and the exported .html, leaving just the KPIs and Views — a clean, chrome-free dashboard you can drop straight inside your own page. Turn it back on and the title, subtitle and description return, all still editable in the inspector.
The header as an object: click anywhere on the title banner in the live preview (not on one of its buttons) to select it, the same way clicking a View or KPI tile does — the Inspector switches to a dedicated Header view with quick Title/Subtitle/Alignment fields and a ✕ button that hides the whole banner (the same effect as the Show dashboard header toggle above). Double-clicking the title or subtitle text directly still edits it in place without needing to select first. Logo, link, and light/dark stay in the Dashboard inspector (click "‹ Dashboard" at the top of the Inspector to get back there).
Alignment: in that same Header view, the Alignment picker (Left / Center / Right) moves the brand mark, title and subtitle within the banner. Left is the standard flush-left layout. Center and Right shift that block toward the middle or the icon cluster on the right — the info/print/waffle icons always stay pinned to the far right either way.
Title size: right below Subtitle, the Title size picker (Default / Small / Large / Extra large) resizes just the banner title text — handy for a dense multi-KPI ops board (Small) or a single-hero exec dashboard where the title should dominate the banner (Extra large). Leave it at Default to keep the standard weight/size.
Subtitle style: next to Title size, the Subtitle style picker (Normal / Italic / Bold / Bold italic) adds emphasis to just the banner subtitle — e.g. Italic for a light tagline, Bold for a subtitle that should read almost as loudly as the title.
Card style: below Subtitle style, the Card style picker chooses the render mood for every chart card and KPI tile on the dashboard. Raised (default) keeps the standard shadow, glass-edge highlight, and hover lift. Flat / minimal strips all three for a quieter, editorial look — same data and layout, just a calmer surface treatment for a boardroom setting. Sketch / hand-drawn swaps the shadow for a dashed border and a wobbled, asymmetric radius for a whimsical, hand-drawn mood.
Share this dashboard
Copy shareable link (in the Dashboard inspector) encodes the whole working
dashboard — Views, KPIs, filters, style — into a #share=… link, no file, no server.
Opening it in any browser reopens the exact dashboard in the builder — handy for handing a
work-in-progress to a teammate.
- Share just my changes appears once you've sent a full link and keep editing — a much shorter link carrying only what changed since. The recipient must already have the dashboard from a previous link; without that base it shows a clear error.
- Different from the Shareable link section above it (shown when the
dashboard has filters): that copies a short
#filterId=valuehash meant for an exported file, not the builder.
Version history
Every Save (topbar button, ⋯ More → Save on a phone, or Ctrl/⌘+S) keeps a timestamped checkpoint of the whole dashboard — up to the last 10 per dashboard, stored locally on this device:
- Restore any entry (after a confirm) — the restore itself becomes a new checkpoint, so restores can always be undone too.
- ⇄ Compare to current opens a plain-English change list (title/style fields, and which Views/KPIs/filters were added, removed, or changed) so you can see what a restore would actually undo first.
- Separate from in-session Undo/Redo (lost on reload) and the single unsaved-draft autosave — this is a real time-travel timeline.
Dashboard theme

At the top of the style section, the Dashboard theme picker swaps the whole look — background, View, text, brand, and all ten chart series colors — in one click, for the preview and the exported dashboard alike. Every curated theme's series palette is validated for contrast (WCAG AA) and colorblind safety in both light and dark mode:
- Polecat — the warm terracotta/plum house look; the default for new dashboards.
- Classic Blue — the original look, unchanged.
- Fleet Modern — a cooler dark/light system aligned with the rest of the Polecat fleet.
- High Contrast — true black/white extremes with solid borders, for maximum legibility.
- Editorial — paper-and-ink boardroom mood; a quieter "print report" feel.
- Neon — synthwave: near-black Views with electric cyan/magenta accents; the header stays near-black in both modes for a consistent "neon on black" identity.
- Conservation — the CTIC olive/field-green + deep-pine look, on natural paper-sage light surfaces and a deep forest-green dark mode; made for the Conservation Insight sample pack and any conservation-agriculture dashboard.
- Custom — author your own. Pick Background, View, Text, and Brand for light and dark (8 fields); everything else (borders, subtle fills, sidebar, grid lines) is derived automatically, with a live warning if a text/background pair falls below the 4.5:1 AA contrast minimum.
Good to know:
- A custom theme travels with the dashboard — Save, Export, and Share all carry it.
- Theme presets (below the color editor) save the current custom theme under a name to reuse across dashboards.
- Accent color, Header background color, and Series palette still layer their own overrides on top of whichever theme is active.
Header background color: further down, below Accent color, the Header background color picker sets a flat fill for the banner itself (the default is a navy gradient) — text color auto-adjusts for contrast (dark text on a light pick, white on a dark one), so it's always readable. This is separate from Accent color, which only tints the bottom border plus chart/button accents throughout the dashboard. Click "Reset to default" to go back to the standard navy banner.
Edit JSON spec (power users)
The Edit JSON spec… command palette entry (⌘K / Ctrl K) opens the whole working dashboard as raw, editable JSON — Views, KPIs, filters, style, everything. Useful for bulk edits, scripting a change outside the app, or seeing exactly what the builder tracks.
- Apply validates the JSON is well-formed and spec-shaped (a
widgetsarray and acda.dataAccessesarray) before applying — an invalid edit shows an inline error and changes nothing. - A checkpoint is saved to Version history right before your edit lands, so any hand-edit is one restore from undone.
Build progress & Checks
The top of the dashboard inspector shows a small ring + checklist tracking five well-rounded-dashboard milestones — a real title, a View, a KPI, a filter, and a touch of your own style. It's purely encouraging (an empty dashboard is perfectly valid) and collapses to a one-line "nice work" note once every milestone is met.
The Checks section right below it is different — it only flags real problems:
- Missing data bindings, an invalid file-name stem, and similar genuine issues.
- A data access declared but not used by any View or KPI — leftover config from an earlier draft doesn't go unnoticed.
- Collapsed, its header still shows a glanceable summary — "all clear," or "1 error, 2 warnings".
- The first time a dashboard reaches zero Checks issues, a small one-time celebration plays — the same delight moment a first export gets. (All six direct-query connectors have a real live-query runtime after export, so none of them trigger a Checks warning.)
Drag and drop
- Drag a DA card from the Data panel → drop on the canvas to add a chart View.
- Drag a View's header grip → drop between any other Views to reorder (across rows).
- Drag the right edge of a View → resize its column span (1–4 columns).
- Double-click a View title on the canvas to rename it inline.
Auto-arrange: the Layout field in the Dashboard inspector (next to Grid columns) has an Auto-arrange button that reflows the existing Views for you — chart types with a lot of horizontal detail (Table, Text/annotation, Sankey, Chord, Calendar heatmap) get a full-width row of their own, everything else defaults to a single column, and Views sharing a tag are grouped together. A quick starting point for a first draft; drag-resize still works normally afterward.
Undo / Redo
Every structural edit (add, remove, reorder, resize, rename) is snapshotted. Use Ctrl Z / ⌘ Z to undo, and Ctrl Shift Z / ⌘ Shift Z — or Ctrl Y / ⌘ Y, the Windows alias for the same action — to redo. The ↶/↷ buttons in the topbar work too (they only show while Studio is open; on a phone they are ⋯ More → Undo and ⋯ More → Redo). Text-field edits are not tracked individually (to avoid flooding the history).
Sample dashboards
The bundled sample dashboards — a curated, most-spectacular-first gallery spanning operations, engineering delivery, finance, marketing & growth, reliability, governance, compliance, quality, pipelines, storage, and cost, together covering all 54 chart types at least once (the newest — the Metrics wheel — in the Conservation pack’s own System Metrics dashboard) — live in two places: Home's sample gallery (click a tile to open it in the builder) and the Dashboards section under the Sample packs chip. A great way to explore what's possible before building your own. (The old Studio "Examples ▾" toolbar menu is gone — same dashboards, better homes.)
+ Import from URL… (in the Open-dashboard picker's footer) loads a dashboard
from any public link to a .studio.json file — a GitHub raw link, a gist, a static
host. A plain client-side fetch, no backend, no account:
- Publish a
.studio.jsonanywhere public, share the link, anyone can import it — a simple community template exchange. - The URL can also point to an index of templates — a JSON array (or
{"templates":[...]}) of{title, url, description}entries — and the modal shows a browsable list to pick from. Good for publishing a whole template pack from one link.
Slideshow & story mode
⋯ More → Present → Slideshow cycles through every View one at a time, full-screen — great for a stakeholder walkthrough:
- Navigate with ◀/▶ or the arrow keys; Escape or × exits.
- Each slide renders through the exact export pipeline — quality identical to what you'd ship.
- Its neighbor Focus mode collapses the builder panes so the live preview fills the screen — no cycling, just a distraction-free canvas; exit the same way.
Slide caption (View inspector, below Note) adds narration that appears only in Slideshow — one beat of the story per slide (e.g. "Revenue held steady, but churn ticked up in March"). Distinct from Note, which is stakeholder context shown everywhere; Views without a caption simply show no caption bar.
Turn on Slide emphasis (right below Slide caption) to have that View's slide play a brief zoom + glow entrance the moment it appears — a light touch of choreography that draws the eye to the beat that matters most, replaying each time you return to that slide. Once it's on, Pan X / Pan Y sliders appear too — drag them off-center to anchor the zoom toward a specific spot in the chart (a spike near the edge, say) instead of dead center.
Dashboard filters
The Filters section of the Dashboard inspector (◈ +) adds a dropdown filter chip above the Views. Click + to add one, then click the chip in the live preview (or its row in the list) to open the Filter inspector:
- Label — the chip's on-screen name.
- Parameter id — the
{{param}}key any dataset/query references to react to the current pick. - Options query — which data access supplies the dropdown's choices, with Value column / Text column picking what's stored vs. displayed.
- 'All' label and Default value — the always-present "everything" choice.
- An Options preview shows the live choice list right in the inspector.
Cascading filters: if a filter's Options query is itself a parameterized data access (it takes its own ${param}), its choices refresh automatically from whichever upstream filter supplies that parameter — e.g. a Region filter narrows a State filter's options to just that region's states. The Filter inspector notes when a filter is wired this way.
Template variables
Write {{key}} anywhere in the dashboard's Title/Subtitle
or a View's Title/Note, then define matching entries in the
Dashboard inspector's Template variables section — each substitutes its saved
value in the live preview and every export.
- A
{{key}}with no matching variable stays literal text — a typo is obvious, never silently blank. - Turn one spec into a reusable template:
{{region}} — Weekly Ops Reviewfilled in per deployment instead of hand-editing. - Save current as… names the current variable set; Apply reuses it on any dashboard built from the same template.
Built-in dynamic tokens
These need no variable — write them into a dataset's SQL/query (or a title) and they resolve fresh on every run, so relative-date filters stay current on their own:
- Days —
{{today}},{{yesterday}},{{tomorrow}}, and any offset like{{today-30}}/{{today+7}}. - Periods —
{{week_start}}/{{week_end}}(Monday-start),{{month_start}}/{{month_end}},{{quarter_start}}/{{quarter_end}}(calendar Q1–Q4),{{year_start}}/{{year_end}}. - All render as ISO dates (
YYYY-MM-DD);{{now}}renders a full ISO timestamp for datetime comparisons.
So WHERE order_date >= '{{today-30}}' is always "the last 30 days." The
Date token button on the dataset builder's query field drops any of these at
your cursor; defining a real parameter or variable of the same name overrides a built-in.
Ensembles & scientific honesty
When several sources estimate the same thing — several vendors' models, several surveys, several labs — the Ensemble (common estimate) chart and the Map (US choropleth) chart are built around one rule: the common estimate is the product, not a leaderboard. The sections below explain the design choices behind that rule and how to read (or build) an honest ensemble dashboard.
The common estimate is bold; providers are supporting evidence
On both charts, the combined value — computed across whichever sources are currently toggled on — always renders as the boldest, most prominent element: a thick line on Ensemble, the fill color on the map. Which combination that is, is yours to set, and the two charts ask it differently: the Ensemble chart's Common estimate option offers Median (recommended) and Mean, while the map's Combine duplicate rows by option — how several rows for one region become one color — offers Median (common estimate), Mean, Sum, Min, Max and Last. Both default to the median, and the median is what this page's honesty argument is written around: it is the choice a single outlying provider cannot drag. The bold line is named by the chart's Estimate label option — Common estimate unless you change it — and that name is what the legend, the hover tooltip and the downloaded data all print, so what you read follows the setting instead of assuming a median.
Individual providers draw thin and muted (hollow dots, low opacity) — present so you can inspect the underlying evidence, but never competing visually with the estimate itself; the Show provider series option hides them when you want the estimate alone. Toggling a provider off (click its chip under an Ensemble chart, unless the Provider on/off toggles option is off) removes it from the combination everywhere it's used, including any map on the same Ensemble channel option — both charts have that option, and the map also needs its Provider / series column mapped. Both recompute and re-render live from the same reduced set.
The agreement band is a confidence signal, not decoration
The translucent band around the Ensemble chart's estimate line — drawn by the Agreement band (confidence) option, which can also turn it off — spans the full range of the toggled-on providers at each point. Read it literally: a tight band means the sources agree (high confidence in the estimate there), and a wide band means they don't (treat that point with more caution). It is not a stylistic flourish — it's the chart's way of showing uncertainty honestly instead of hiding it behind a single confident-looking line.
A reference series is context, never the estimate
Both charts support an optional reference series — the Ensemble chart's Reference series (excluded from the estimate) option (e.g. an independent census or ground-truth figure) — drawn as hollow markers, visually distinct from the provider lines and the estimate. A reference series is deliberately excluded from the combination — it's there to let a viewer compare the ensemble's estimate against an outside benchmark, not to be folded into the estimate itself.
No-data is shown, never guessed
The choropleth renders regions with no data as a hatched "No data" texture rather than leaving them blank (easy to misread as zero) or interpolating a value (which would misrepresent coverage). Combined with the provenance popover below, a viewer always knows exactly how much of the map is real data versus gaps.
Checking the honesty story: the ⓘ Sources popover
Both charts have an ⓘ Sources button next to their legend — a popover answering "how much of this can I trust?":
- On the map — what fraction of regions actually carry data (and, with a provider column mapped, each provider's own region count).
- On the Ensemble chart — how many providers are selected, how many points have every selected provider reporting (full coverage — stricter and more honest than the agreement band alone), each provider's point count, and the reference series' count with a reminder that it never joins the estimate.
- Last updated — when the data comes from a saved workspace dataset, the dataset's own timestamp (bumped automatically by a re-run prep job), so a chart is honest about its freshness, not just its providers.
The popover stays open while you read it (unlike a hover tooltip) and works identically in an exported dashboard.
Handing off the underlying numbers
Both charts' Download data button (the same on-canvas control every View has — see "Downloading a View's image or data" below) exports exactly the selection currently shown — the map's region id/value pairs post-aggregation, or the Ensemble chart's long-format label/provider/value rows for the toggled-on providers plus the computed estimate (under whatever name the Estimate label option carries) and any reference series — instead of the raw bound query. Re-download after toggling a provider and the file reflects the new selection — a quick way to hand a stakeholder the same numbers the chart is drawing from.
Worked example: install the Conservation Insight sample pack (Settings → Sample packs) for a complete, ready-made illustration of everything on this page — four pinned Ensemble Views (one per conservation practice) and a featured dashboard pairing them with a provider-aware county choropleth, all built from synthetic, clearly-labeled sample data.
Chart types
Maps
54 chart types are available, grouped by purpose. Select the chart type in the View inspector after clicking a View on the canvas. Every card below is titled with the name the picker itself prints — the same name the ⓘ docs link on a gallery card opens it by, and the same name a View's chart-type filter pill carries — so a name you read here can be typed straight into the gallery's Search chart types… box and will find its card.
Not sure where to start? Once a View has a query bound, a "Recommended for this data" strip appears above the full gallery with 1–3 suggested types (e.g. Line for a date column + a number, Donut for a low-cardinality category) — each button explains its "why" on hover and switches the chart type on click.
Once you're in a chart type's own options (below the gallery), common toggles like Sort by value, Show legend, Smooth curve, Show data points, Show value/label, Show %, and Rotate labels carry a small glyph next to their label — hover it (or Tab to it) for a one-line explanation of what the toggle actually changes. Sort by value, Smooth curve, Show legend, Show data points, Show value/label, and Rotate labels go a step further with a genuine Off → On picture — two tiny side-by-side diagrams showing exactly what the toggle redraws, not just a sentence about it.
Comparison
Composition
Trend
Flow
Single value
Distribution
Detail
Content
Simple mode shows only the 15 most universally understood chart types. Switch to Advanced mode (⋯ More → Simple mode off) to unlock all 54 types plus specialist annotation tools.
Data sources & data accesses
Every chart View is bound to a data access (DA) — a named query. The Data panel (left pane) lists everything you can build from, in four groups: This dashboard's datasets (the queries the spec owns), Datasets (the workspace's shared catalog), Views (your saved charts), and — in Advanced mode, once you've authored one — My queries at the bottom. (The old "Samples" group of built-in demo queries is gone: sample content comes only via sample packs now, installed from Settings, so there's one source of truth.)
The This dashboard's datasets group collapses by default once it gets long (more than six cards), so the pane leads with an overview instead of a wall of cards — click a group's header to expand or collapse it any time, and your choice is remembered from then on. Each group also carries its own small glyph next to its name (a cube for this dashboard's own datasets, a database for the shared workspace catalog, a trend line for saved Views, and a code bracket for your own authored queries), so the pane is scannable by "what kind of thing is this" at a glance.
Select a View on the canvas and the Data panel shows you which dataset it reads from — that dataset's card is ringed in the list, and the same happens when you select a KPI. If the card's group is collapsed, or it's scrolled out of sight, the pane opens the group and brings the card into view; select a View with no query bound to it (an annotation, say) and nothing is highlighted at all.
Using catalog queries
- Drag any DA card onto the canvas to create a chart View pre-bound to that query.
- Or select an existing View and use the Query picker in the inspector to switch its DA.
- The Query preview section in the View inspector shows the SQL snippet, column headers, and 3 sample rows so you can verify the binding without leaving the builder. If that sample has any blank/missing values, a column that's the same value on every row, or duplicate rows, a small note appears right below the table calling it out — a quick data-quality check before you build a chart on top of it.
- When that View reads a workspace dataset, the same section carries an Edit this query link. It opens the ordinary dataset editor on top of the dashboard — change the SQL, hit Preview to see the rows it returns now, and save. Your dashboard is still there behind it, with the same View selected, and the pane repaints against the query you just saved. A View that carries its own inline rows (a sample-pack View, or a dashboard-only query) has no dataset to open, so it shows no link. If the new query stops returning a column a shelf was using, saving says so by name rather than leaving you with an empty axis.
- Because that editor is now one click from a canvas, it warns you before a save that could break something you can't see. If you change what a dataset runs — its query, the connection behind it, a parameter default, the file it reads — on a dataset that dashboards are bound to, and you haven't pressed Preview on what you're about to save, a note appears above the buttons: why it's unproven (never run here, or the last Preview failed and what it said) and what it would reach, counted and named —
3 Views in 2 dashboards read this dataset — …. The dashboard you have open counts too, even if you've never saved it. Preview, then save runs the query and, if rows come back, completes the save in the same tap; Save anyway goes ahead regardless (there are good reasons to save a query you can't run right now); Keep editing puts you back in the query box. Saves that can't break anything are never questioned — renaming, refiling, retagging or re-describing a dataset, a save you've already previewed, and any dataset no dashboard reads yet. - For any chart bound to a single value column, an Insight section appears right below it with a short, plain-English narration of the View's own sample data — overall trend direction, the single biggest point-to-point move, any outlier more than 2 standard deviations from the mean, and (when the data supports it) a repeating pattern detected via autocorrelation on the trend-removed series, e.g. "It also shows a repeating pattern roughly every 4 points." A Scatter / bubble View gets a different read instead: the Pearson correlation between its bound X and Y columns (strength + direction, e.g. "a strong positive correlation"). It's computed entirely client-side (pure JS math, no API key, no network call) and updates automatically as you change the query or column mapping. When the narration flags an outlier or a biggest single move, an Add callout at "…" button appears below it — one click drops the View's Callout arrow overlay (see below) right on that point instead of eyeballing the x%/y% sliders yourself.
Authoring new data sources (Advanced)
In the Data panel's header, click + New ▾ → Dashboard-only query… to open the data-source builder. (The + on the This dashboard's datasets group header is the quick path: it drops a blank query straight into the Inspector's editor instead.) Supported types:
- SQL — Write a SELECT query against the built-in sample engine (or use the visual SQL Builder: FROM / JOIN / WHERE / GROUP BY / ORDER BY / LIMIT). Columns come from your
ASaliases ("Detect from query" pulls them automatically), and deterministic demo rows are generated from them so every dashboard stays fully demoable offline. - DuckDB (remote file) — query a Parquet/CSV file directly over HTTP, no backend (see below).
- SQLite (remote .sqlite) — query a
.sqlitefile directly over HTTP, no backend (see below). - Snowflake — query a live Snowflake warehouse via the SQL API, straight from the browser (see below).
- Databricks — query a live SQL warehouse via the Statement Execution API, straight from the browser (see below).
- BigQuery — query a live dataset via the jobs.query REST API, straight from the browser (see below).
- Generic SQL/HTTP — POST/GET a JSON API that runs SQL and returns rows, straight from the browser (see below).
Browser-native connectors: no backend required
Two source types query a file hosted anywhere over plain HTTP — S3, any static host — entirely inside your browser, with no proxy, no saved credentials, and no server connection. Both intercept the query engine's disk reads and turn them into HTTP Range Requests, so they only transfer the bytes a query actually touches. Both are flagged with a small "Browser-only" badge on their source-type card in the New Source builder, and a failed Test connection surfaces a plain-English hint (CORS/Range support, a slow/unreachable host, a 404) instead of a raw browser error. Pick whichever fits your workload:
- DuckDB (remote file) is the columnar / analytics engine — best for aggregations and dashboards over a Parquet or CSV file, via DuckDB-Wasm.
- SQLite (remote .sqlite) is the relational / indexed-lookup engine — best for point lookups and small joins over a
.sqlitefile with real indexes, via sql.js-httpvfs. An indexed query only fetches the handful of ~4KB pages it needs, even from a multi-GB file.
DuckDB (remote file):
- Set the File URL (and optionally the format — auto-detected from the extension) and click Test connection & detect columns. This lazy-loads the ~3–5MB DuckDB engine the first time it's needed, registers the file, and runs a schema probe — the base app stays small until you actually use this connector.
- The optional Query field runs SQL against the file, aliased as the view
t(e.g.SELECT region, SUM(revenue) FROM t GROUP BY region). Leave it blank forSELECT * FROM t LIMIT 200. - The Data preview's Run live button queries the real file the same way — no server required.
SQLite (remote .sqlite):
- Set the File URL and optionally a Table name, then click Test connection & detect columns. Leave the table blank and Studio auto-picks the first table in the file; the test lists its columns via
PRAGMA table_infoand a 5-row sample. - The optional Query field runs SQL against the opened database (any table in the file, not just the detected one). Leave it blank for
SELECT * FROM <table> LIMIT 200. - The Data preview's Run live button queries the real file the same way — no server required.
Both connectors run genuinely live everywhere: the builder itself (Test connection, Run live, the live-preview iframe), the offline sample preview used while you're building, and — a downloaded/deployed dashboard .html export queries the real file directly too, no server involved (the export bundles the small connector façade automatically whenever a dashboard uses one).
Snowflake connector
The Snowflake source type queries a live warehouse directly from the browser via Snowflake's SQL API — no backend or proxy, but unlike the file connectors above it's credential-based and flagged with a "Needs token" badge instead of "Browser-only". Two things to set up before it will work:
- A token, never a password. Generate a Programmatic Access Token (or use an OAuth access token) in Snowflake and paste it into Access token; pick the matching Token type. Studio never asks for or stores a Snowflake username/password.
- CORS allow-listing. Snowflake blocks cross-origin browser calls by default — a Snowflake admin must add this app's origin to the account's
ALLOWED_HTTP_ORIGINSnetwork policy before Test connection will succeed. Until then, requests fail with a clear CORS hint rather than a stuck spinner.
Fill in Account identifier (e.g. xy12345.us-east-1), Warehouse/Database/Schema and an optional Role, write a Query, then click Test connection & detect columns — it runs SELECT 1 to validate everything and, once you have a real query, detects output columns from the live result. The Data preview's Run live button queries the warehouse the same way.
Databricks connector
The Databricks source type queries a live SQL warehouse directly from the browser via Databricks' Statement Execution API — no backend or proxy, same credential-based shape as the Snowflake connector above and flagged with the same "Needs token" badge. Two things to set up before it will work:
- A personal access token, never a password. Generate one under Databricks' Settings → Developer → Access tokens and paste it into Access token. Studio never asks for or stores a Databricks username/password.
- An allowed origin. Databricks workspaces don't emit permissive CORS headers by default — check with your workspace admin that this app's origin is allowed, or requests will fail on CORS with a clear inline hint rather than a stuck spinner.
Fill in the Workspace host (e.g. dbc-a1b2c3d4-e5f6.cloud.databricks.com, no protocol), the SQL warehouse id (from the warehouse's Connection details), and optionally a Catalog/Schema, write a Query, then click Test connection & detect columns — it runs SELECT 1 to validate everything and, once you have a real query, detects output columns from the live result. The Data preview's Run live button queries the warehouse the same way; statements that start out PENDING/RUNNING are polled automatically.
BigQuery connector
The BigQuery source type queries a live dataset directly from the browser via Google's jobs.query REST API — no backend or proxy. It's credential-based like Snowflake/Databricks above, flagged with the same "Needs token" badge, but Google's API already sends permissive CORS headers for this endpoint, so there's no admin allow-listing step — the token itself is the only gate.
- A short-lived OAuth 2.0 access token, never a service-account key file. Paste it into Access token; Studio never asks for or stores a downloadable key.
Fill in the Project id (e.g. my-analytics-project) and optionally a job Location (e.g. US, needed for datasets outside the default region) and a Default dataset (so unqualified table names resolve against it), write a Query, then click Test connection & detect columns — it runs SELECT 1 to validate the project/token and, once you have a real query, detects output columns from the live result. The Data preview's Run live button queries the dataset the same way; jobs that don't complete immediately are polled automatically.
Generic SQL/HTTP connector
The Generic SQL/HTTP source type is the escape hatch: it POSTs (or GETs) a SQL string to any JSON API you point it at — an in-house query service, a serverless function, or a provider not yet covered by a named connector — straight from the browser, no backend or proxy. Flagged with a "Needs endpoint" badge (not "Needs token" — auth is optional and entirely up to your endpoint).
- Fill in the Endpoint URL, pick a Method (POST sends a JSON body, GET appends a query-string param), and optionally an Auth header (the full header value, e.g.
Bearer …— sent as-is, nothing is stored beyond your browser). - Param name (default
sql) is the JSON key or query-string key that carries the query text — match it to whatever your endpoint expects. - Write a Query, then click Test connection & detect columns — it sends
SELECT 1and detects output columns from the live response. The Data preview's Run live button queries the endpoint the same way. - The response can be shaped three ways: an array of row objects (
[{"col":1,...}]), the same nested under{"data":[...]}, or an already column/row-shaped{"columns":[...],"rows":[[...]]}.
Live after export, credentials prompted at open
Like DuckDB/SQLite, the four credential-based connectors above (Snowflake, Databricks,
BigQuery, Generic SQL/HTTP) query live from an exported/deployed .html too — but
their secret is never baked into the exported file:
- Export strips the secret out of the data access before the file is written.
- The first time the exported dashboard actually needs to query, it prompts once (a plain browser prompt naming the connector and data access) and keeps the value in memory only for that page load — never disk, localStorage, or the file.
- Every non-secret field (account, host, project, warehouse…) still travels with the export.
Workspace connections: every connector the wizard offers
The Connections section manages saved, credentialed connections that workspace Datasets query through. (Every password-style field in the app — sign-in, here, the backend wizard, Admin's Add-user form, the provision-secret prompt — has an eye toggle to reveal what you typed before saving.)
A connection is reusable: save it once and any number of datasets read through it — unlike the dashboard-only data accesses above, which belong to the one dashboard that declares them. Six connectors can be used either way, so Snowflake, Databricks, BigQuery, DuckDB (remote file), SQLite (remote .sqlite) and Generic SQL / HTTP are described in their own sections above and are also on this list — same backend, saved for the whole workspace instead of one dashboard.
+ New in the Connections section opens a picker with all of these, in this order. Three carry a workspace-capable badge: those can also host the whole workspace, not just answer queries (see Choosing a workspace backend below).
- Turso workspace-capable — SQLite over HTTP. Creates every object it needs from the browser, and runs SQL datasets for dashboards.
- Supabase workspace-capable — Postgres behind a REST API; reads and writes run from the browser, after the one-time SQL script.
- Firebase workspace-capable — Cloud Firestore document store. Schemaless — collections appear on first write — and access is governed by your own security rules.
- PostgreSQL (PostgREST) — any Postgres exposed through PostgREST; table + query-string datasets, optional JWT auth. Detailed below.
- CSV / JSON file — drop a local file and chart it; the data rides inside the dataset, so it works offline and travels with your workspace. Detailed below.
- Google Sheets — a link-shared Sheet, straight from the browser: tabs plus an optional query language, no OAuth needed. Detailed below.
- Snowflake — a Snowflake warehouse via the SQL API v2; needs a programmatic access token and this origin CORS allow-listed on the account.
- Databricks — a Databricks SQL warehouse via the Statement Execution API; needs a token and this origin allowed on the workspace.
- BigQuery — a BigQuery dataset via the jobs.query REST API; needs a Google OAuth access token.
- Amazon Redshift — a cluster or Serverless workgroup via the Data API, SigV4-signed from the browser. Detailed below.
- DuckDB (remote file) — a Parquet/CSV file straight from S3/HTTP via DuckDB-Wasm; the file needs CORS + Range requests.
- SQLite (remote .sqlite) — a
.sqlitefile over HTTP Range Requests; indexed lookups without downloading the whole file. - Generic SQL / HTTP — POST/GET a JSON API that runs SQL and returns rows: any in-house query service, or a provider without a dedicated adapter yet.
The Local (this browser) store is deliberately not on that list: it is where a workspace lives, not somewhere a dataset connects out to.
Workspace connections: PostgreSQL (PostgREST)
The PostgreSQL (PostgREST) adapter connects to any Postgres exposed through a PostgREST deployment — self-hosted or managed — straight from the browser:
- PostgREST URL — its root answers with the OpenAPI document, which is exactly what Test checks.
- Bearer token (optional) — a PostgREST JWT; blank = anonymous access.
- Schema (optional) — sent as
Accept-Profilefor multi-schema deployments.
Datasets on this connection are table-based, like Supabase: a table or view
name plus an optional raw PostgREST query string
(select=region,total&order=total.desc&limit=200) — with
{{parameters}} allowed in both, so template variables flow into filters like
region=eq.{{region}} at run time.
Connection-bound datasets are live-after-export too
A dataset built on a saved Connection — any of the connectors listed above — follows the same contract:
- Export resolves the connection at export time and carries its non-secret fields (database URL, PostgREST URL/schema, sheet URL, Redshift region/cluster/workgroup) into the file.
- The credential is stripped — prompted once the first time the exported dashboard actually queries live, never embedded.
- No prompt at all for anonymous access: a PostgREST connection with no token, a link-shared Sheet with no OAuth token, or a dropped file (its content already rides inside the export).
- Redshift is the one exception to "one prompt" — SigV4 signing needs the access key ID + secret (and optional session token), so it prompts once per credential field.
Workspace connections: Amazon Redshift
The Amazon Redshift adapter queries a provisioned cluster or a Serverless workgroup directly from the browser via the Redshift Data API — no backend or proxy:
- Credentials — Redshift's API has no bearer-token mode; every request is signed with AWS Signature Version 4. Set an Access key ID, Secret access key, and (for temporary/STS credentials) a Session token.
- Target — set the AWS region and Database, then either a provisioned cluster (Cluster identifier + Database user, using temporary credentials — no stored DB password) or a Serverless workgroup (Workgroup name alone). An optional Custom endpoint supports VPC PrivateLink.
- Execution is asynchronous (fire, then poll), like Databricks' Statement Execution API — polling and result pagination are handled for you.
Browse a connection's schema
For Snowflake, Databricks, BigQuery, Amazon Redshift, PostgreSQL (PostgREST), DuckDB and SQLite connections, the Connections editor has a Browse schema button next to Test connection: fill in credentials, click it, and every table the connection can see lists out, each expandable to its columns and types (a filter box narrows long lists by name).
- Read-only — it reads live metadata and never changes anything: an
information_schema.columnsquery for the SQL warehouses (BigQuery qualifies by project and region when no default dataset is set), PostgREST's own OpenAPI document, aDESCRIBEof the registered file for DuckDB, orsqlite_master+PRAGMA table_infofor SQLite. - Generic SQL/HTTP doesn't show the button — it's an arbitrary JSON API with no reliable catalog to introspect.
The same schema browser is one click away while building a dataset, not just when editing the connection: the Dataset editor's "Browse schema" button (shown for the same set of connections, once one is picked) opens the identical table/column tree — but here every table and column is clickable. Picking a table fills the Table field on a PostgreSQL/PostgREST-shaped dataset; picking a table or column drops its name straight into the SQL query at the cursor for every other kind. No more copying names back and forth from a separate tab.
Studio's own New data source builder (the query editor behind the Data panel's + New ▾ → Dashboard-only query…) has the same button under its Query field for the credentialed kinds — Snowflake, Databricks, BigQuery, DuckDB (remote file) and SQLite (remote .sqlite). Fill in the credentials (or the file URL), click Browse schema, and click any table or column to insert its name into the SQL at the cursor. The built-in sample-engine SQL kind and the Generic SQL/HTTP kind have no catalog to introspect, so they don't show the button.
The same builder also runs a live sanity check over the SQL as you type: an unclosed string literal or quoted identifier, unbalanced parentheses, a statement that doesn't start with SELECT/WITH, or a declared column chip the query never mentions each surface a small warning on the line under the query field (and clear the moment you fix them). It's a lightweight shape check across all the supported engines, not a full SQL parser — the Preview and Test connection buttons remain the way to prove a query actually runs.
Writing SQL: highlighting, column help, and what actually gets checked
Every SQL box in the app is a real editor rather than a plain box — the Dataset editor's SQL field, all seven per-adapter query fields in the New data source builder (the built-in SQL engine, DuckDB, SQLite, Snowflake, Databricks, BigQuery and Generic SQL/HTTP), and the Jobs Custom-SQL step. Keywords, strings, numbers, comments and {{parameter}} placeholders are coloured as you type, in both themes and every palette.
- Completion — type a couple of letters and a short list appears at the cursor: the columns your last Preview returned, the tables and columns Browse schema has loaded, this dataset's own declared parameters, then SQL keywords and the common functions. ↑/↓ to move, Enter or Tab to take one, Esc to dismiss, Ctrl+Space to summon it deliberately — or just tap an entry on a phone. It only offers what the app genuinely knows: with no Preview run and no schema browsed, you get keywords and functions and nothing invented.
- After a dot, it narrows to the table — type
orders.and the list becomes orders' columns and nothing else: no keywords, no functions, no columns borrowed from another table. Aliases work too, because the editor reads your own query — writeFROM orders oando.offers the same list, which is what you want the moment a join is involved. Taking an entry replaces only the part after the dot, so the qualifier you typed stays as you wrote it. If the name in front of the dot isn't one the app can resolve — an alias for a subquery, a table nobody has browsed — the list simply doesn't open, rather than falling back to every column it has ever seen. - Checking, and its limits — a line under the field names anything structurally wrong: an unterminated quote, an unclosed
/*comment, a(that never closes or a)with nothing to close, a statement that doesn't start with SELECT or WITH. It is the same check the dashboard-only query builder has always shown, not a second opinion. It is not a SQL parser and never says a query is valid — the app talks to seven engines with seven dialects, so a tick that was sometimes wrong would be worse than no tick. It stays silent when it has nothing to say, and Preview remains the only proof a query runs.
Each surface offers what it honestly knows, and no more. The New data source builder's boxes offer the column chips you've declared or detected, whatever Browse schema has loaded, and the query's own parameters — a brand-new source with nothing tested and nothing browsed gets keywords and functions and invents nothing, because those fields are filled in before a connection exists. The Jobs SQL step is the opposite case: it runs against the rows your pipeline has produced so far, loaded into a DuckDB table named t, so it offers exactly those incoming columns and that one table, and keeps up when you change a step above it — and because it knows that table's shape exactly, t. narrows to it. Qualified completion needs a table that can say what it holds, which in practice means one Browse schema has loaded, or the Jobs step's t.
Workspace connections: Google Sheets
The Google Sheets adapter charts a sheet shared as "anyone with the link can view" straight from the browser — no OAuth, no API key:
- Paste the sheet's URL (or just its id) into the connection.
- Datasets on it pick an optional Tab (first tab when blank) and an optional
Query in
Google's query language
— columns address as
A, B, C…(e.g.select A, sum(B) group by A), with{{parameters}}allowed so template variables flow into clauses likewhere A = '{{region}}'. - Header labels become column names, dates render formatted, and a private sheet gets a friendly "share the link" hint instead of a raw error.
Private sheets
Fill in the optional OAuth access token field — a short-lived Google OAuth
2.0 token scoped to spreadsheets.readonly (e.g. from the
OAuth Playground),
the same "paste a token, never a key file" shape as BigQuery.
- With a token set, the connection uses the Sheets API v4 — the Tab reads as a plain values range (header row → column names).
- The full gviz query language doesn't exist server-side in v4, so the Query field runs a
small local subset instead —
select,where(= != < <= > >= contains, joined withand),order by,limit, andoffset— applied client-side to the fetched tab, columns still addressed asA, B, C…. Anything beyond that subset (group by,pivot, aggregates) needs the link-shared/gviz path — leave the token blank for full server-side query support. - An expired or scope-mismatched token surfaces a clear message, not a raw 401/403.
Workspace connections: CSV / JSON files
The CSV / JSON file adapter is the fastest way from a file on disk to a
chart: add a file connection (nothing to configure), create a dataset on it, and drop a
.csv, .tsv, .json, or .xlsx onto the editor's drop zone (an Excel file's first worksheet is read — values only, dates as Excel serial numbers).
- The file's data is stored inside the dataset — fully offline, no re-picking after a reload, and it travels with your workspace (including to a remote backend).
- CSVs handle quoted fields and sniff comma/semicolon/tab delimiters; numeric cells become real numbers so charts can aggregate; JSON expects an array of row objects.
- Files cap at ~2 MB — bigger data belongs in the DuckDB (remote file) connector, which streams only the bytes a query touches.
Calculated columns
Every data source has a Calculated columns section (Advanced mode): give a
Name and a Formula that references other output columns in
square brackets, with + - * / ( ) supported. Examples:
=[revenue]-[cost]— a margin;=[revenue]/1000— rescale a value.=pctChange([revenue])— percent change vs. the previous row, in percentage points (12.5= +12.5%; the first row is blank — no prior row).=movingAvg([revenue], 5)— a trailing 5-row average (defaults to 3; partial windows at the start average whatever's available, same convention as the line chart's own moving-average overlay).
A bad reference — unknown column, divide-by-zero, malformed syntax — shows an inline error naming the problem instead of failing silently. Once valid, the calculated column is a real, bindable column everywhere a View or KPI picks columns from this source, computed live in the offline preview — and for the six direct-query connectors (DuckDB, SQLite, Snowflake, Databricks, BigQuery, Generic SQL/HTTP) it's appended to the live query result in exported/deployed dashboards the same way.
Data source freshness
Any live-capable data access (DuckDB, SQLite, Snowflake, Databricks, BigQuery, Generic SQL/HTTP, or a dataset bound to a workspace connection) shows a "Last verified live …" / "Never verified live" badge in the Data preview:
- Stamped locally each time a live query actually succeeds — an expired token or a dark endpoint doesn't stay silently trusted just because it worked once.
- A successful Test connection & detect columns counts too — no full query needed just to refresh the badge.
- The same badge appears on the source's card in Dashboards and in the builder's Data panel — only for live-capable kinds; a sample-engine SQL source stays badge-free rather than showing "Never verified live" everywhere.
Joins and compound DAs
A Compound data access — either a join (left/right DA + key columns) or a union (stack rows from N DAs) — is computed in the builder over the member DAs' rows. The builder no longer authors new ones: joins and unions belong in Jobs (left rail), so the Data panel's old ⧈ Join entry is gone. Any dashboard that already has a compound DA still renders it and still edits it in the Inspector.
Dataset lineage
The Datasets catalog badges each row with "↪ N dashboards" when one or more saved dashboards reference it — hover the badge for the list of names. It's a blast-radius view: before deleting a dataset, you can see what depends on it. Deleting one that's in use warns you which dashboards reference it — they don't break, but they fall back to the self-contained copy of the query that was saved into them the last time they linked to it, rather than continuing to follow live edits to the dataset.
Filtering datasets and connections
The Datasets list narrows by four cross-cutting pill strips at once, plus the search box:
- Adapter — every Postgres/Snowflake/file/etc. connection grouped together.
- Connection — one specific connection (just "Prod DB" out of several Postgres ones).
- Type — SQL query / Table / Collection / File / Sheet.
- Tag — free-form labels on the dataset.
Pills in the same strip are OR'd; different strips are AND'd; a "Clear" chip resets everything. The Connections list has the same adapter and tag pills — give connections Tags (comma-separated, in the wizard) to slice a growing catalog by "prod" vs. "staging" without a new connection per environment.
Select multiple / bulk actions on datasets, connections, jobs & the Repository
Click Select on the Datasets, Connections or Jobs page to enter multi-select mode — a checkbox appears on every tile/row, and tapping anywhere on a card or row toggles its selection instead of opening it. A bar above the list shows how many are selected with Select all (everything the current search/filters show) / Clear, a Move to folder… button that files the whole selection in one go (the same folder browser every Folder field uses — pick a folder, create a nested one inline, or choose "No folder" to un-file), and a Delete button that removes every selected item after a confirmation (see Undo below; datasets referenced by a saved dashboard, connections referenced by a dataset, are called out by count in that confirmation, same as deleting one individually — deleting jobs in bulk keeps their output datasets, same as deleting one). Click Select again (now labeled Cancel) to leave select mode. The same pattern first shipped on Dashboards. Repository — the searchable catalog across every object kind — has its own Select button too: a selection can mix dashboards, datasets, connections, Views and jobs together; one Move-to-folder choice refiles them all, and the delete confirmation names how many of each kind you're about to remove. The Views page has the same Select button and bulk bar — select any number of saved Views, then move them to a folder or delete them together.
Layout. Every catalog page opens as the compact list — the densest way to scan a lot of items, and the shape the search, sort and filter controls above it are built around. The Tile view button in the page header swaps it for a grid of cards (dashboards show their thumbnail there), and your choice is remembered per section on this device from then on. Dashboards used to be the one page that opened as tiles; all six now start the same way, and Home still greets you with dashboard thumbnails either way.
Sorting. Every catalog page — Dashboards, Views, Datasets, Connections, Jobs and the Repository — has a sort control beside the layout toggle: Newest first (the default; Dashboards labels that same option Last updated), Oldest first, Name A–Z / Z–A, plus per-section extras (Dashboards by workbook or folder, Datasets by adapter or connection, Connections by adapter, Views by chart type, Jobs by last run — the Repository has none). Your choice sticks per section on this device. On Datasets, Connections and Views a pinned item stays at the top whatever the sort; a dashboard's pin sends it to Home rather than to the top of this list, and Jobs and the Repository have no pin at all.
Searching. Every catalog page searches the same way. Type as many words as you
like: every one has to turn up somewhere on the item — all of them, not any of them — in any
order and across any field, so crops 2024 finds a dataset named "Cover crops" filed in
the "2024" folder, even though those two words never sit next to each other. Wrap words in
"double quotes" when you do mean the exact phrase: they then have to sit together, though
"together" is measured across the whole item rather than within one field, so "crops 2024"
also matches where the name ends in "crops" and the folder beside it begins "2024". Case never matters.
An empty box matches everything, so emptying the search is how you get the whole list back. The
Clear chip above the list means show
everything: it drops the facet pills and empties the search box, and it appears whenever
either one is narrowing the list. Five pages have a Clear chip — Dashboards, Views, Datasets,
Connections and Jobs; the Repository is the exception, and Un-picking a
pill below has the way back there.
What counts as a word. Spaces are the only separator, and everything else you type
stays part of the word it is touching. That is what lets you search for a name that contains
punctuation — q2.2024 finds "Revenue q2.2024" — but it also means a comma you added out of
habit goes looking for itself: crops, 2024 hunts for the literal crops, and
comes back empty on the very dataset crops 2024 finds. Quotes work the same way and only
in pairs, so a single unmatched " is just another character in the word beside it —
cover "crops searches for cover and for "crops, quote mark
included, and finds nothing. If a search you are sure about comes back empty, a stray comma or an
unclosed quote is the usual reason.
What each page searches. Every page searches the item's name and its folder. On top of that: Dashboards add the description and, as a fallback when nothing else matches, the column names its charts are bound to (the matching column is named right on the card); Views add the chart type; Datasets add the description, the owner, its tags, its column names and the query text (or the table or collection it points at), plus the name of the connection it reads; Connections add the adapter, its tags and the rest of its settings — never a password or token; Jobs add the source dataset and the output dataset; and the Repository adds the one-line summary each row shows under its name.
…and every other search box too. The same rules run behind every search field in
the app, not just the catalog pages: the builder's Data panel (all three of its groups —
Datasets, Views and My queries), the
Inspector's search, the chart-type gallery, the command palette (⌘K), the What's-new
feed, the folder picker, the Quick Views pane, the View Builder's datasets pane and its value filter,
the auto-build set list, the Open a dashboard picker and the "add to dashboard"
picker that shares its list, and a connection's schema browser. Words in any order, quotes for an
exact phrase, case-insensitive — wherever you type. The command palette layers its own ranking on top
(a command whose name starts with what you typed comes first), and the What's-new feed
highlights each word it matched. Two boxes work differently, both deliberately: a table View's own
Filter rows box matches one literal string across the row's cells — so
crops 2024 finds a row only where those two words sit side by side — because that table
travels inside every exported dashboard and carries its own self-contained rules rather than the
app's; and this Help page's own search box is part of the page you are reading rather than the app.
Filtering with pills. Above each list sits a row of filter pills, each carrying a count beside its label. Picking none of a facet's pills means all of them, so the pills only ever narrow — and pills are listed in the order you read them, alphabetically by their label and numerically where numbers appear (so "Q2" comes before "Q10"). Two strips keep an order of their own instead: Dashboards' workbook pills stay newest-first, and the Repository's type pills sit in the fixed order the app declares them in.
What the number on a pill counts. Every pill has one, and it counts everything on the page that the pill covers — not what you can currently see. The number is worked out before the search box and before every other pill, its own strip included, so it never moves as you filter: narrow a list down to two rows and a pill above them can still read 40. That is the useful number, because it tells you what picking that pill would give you rather than what you already have. The counts also overlap wherever a facet takes more than one value, so a strip's numbers can add up to more than the list itself — a dataset tagged both #finance and #eu is counted under both tag pills. The pills that mean everything — All on Dashboards' workbooks and on the Repository's types, All folders on a Folders strip — carry the whole list's count, which is again what picking them gives you.
When a pill goes away. On most strips a pill exists only while an item sits behind it — the adapter, connection, tag, dataset-type, chart-type and folder pills are built from the items themselves, so the last one to leave takes its pill with it, and a pill never reads zero. Three strips keep a pill you can empty out, and each of them earns it: Unfiled stays at the end of a Folders strip even when everything is filed; on Dashboards, a workbook you made keeps its pill after the last dashboard leaves it — that pill is how you file one back in — and so does that strip's own Unfiled; and the Repository always shows all five type pills, so an empty workspace shows five zeros rather than an empty strip. Either way, a filter can never keep narrowing the list from something you can no longer pick: when the last item behind a pill you had ticked goes away, the app drops that filter for you and puts the list back.
Which pills take more than one. Some facets are multi-select — tick two pills and the list shows anything matching either: Datasets by adapter, connection, type or tag; Connections by adapter or tag; Views by chart type. One pill at a time: the Folders strip, on each page that has one; Dashboards by workbook, the strip that also offers Sample packs and Unfiled; and the Repository by type, one pill per kind of row it lists.
How the strips combine. Within one strip the pills are alternatives: tick a second pill on a multi-select strip and the list grows, because a row need only match one of them — and a row carrying several values of that facet needs only one of its own values ticked, so a dataset tagged #finance and #eu survives a pick of either tag. Between strips it is the other way round: every strip you have picked from has to be satisfied at once, and the search box counts as one more of them — adapter Postgres, tag #eu and the word revenue together show only the rows that answer all three. A strip you have picked nothing in narrows nothing, which is what keeps the pills a narrowing tool rather than a mode; it is also why a combination can legitimately come back empty while every pill above it still carries its own full count.
What a pill says. Each pill is labelled with the app's own name for the thing it narrows to, so the strip reads the way the list under it does. Adapter pills (Datasets and Connections) carry the adapter's name and its colour dot — the same names the connection wizard offers. Connection pills, on Datasets, carry the connection's own name; on both of the Datasets strips, a dataset with no connection behind it lands under No connection. Tag pills keep the hash you write a tag with, so a tagged item filters under #finance. Datasets by type has one pill per kind of dataset: SQL query, Table, Collection, File and Sheet. Views by chart type prints the chart's own name from the gallery — Bar chart, Line / area — with one label the gallery does not hold: a KPI tile's pill reads KPI. The Repository's type pills are the kinds of row it lists: Dashboards, Datasets, Connections, Views and Jobs. Dashboards by workbook opens on All, then Sample packs whenever an installed pack brought dashboards of its own, then one pill per workbook you have made, and Unfiled for the rest.
Un-picking a pill. The multi-select facets toggle — click a pill you have already ticked and it un-ticks. The single-select strips do not: clicking the pill you are already on leaves you on it, so each of those strips opens with a pill that means everything — All on Dashboards' workbooks and on the Repository's types, All folders on a Folders strip. That pill is the way back, and on the Repository it is the only one: it is the one catalog page with no Clear chip, so empty its search box yourself.
The Folders strip. Single-select — All folders, one pill per folder in use, and Unfiled for everything filed nowhere — and it only appears once something has actually been filed. Five pages have one: Dashboards, Views, Datasets, Connections and Jobs. The Repository is the exception: it groups its rows into a nested folder tree instead, so there is no strip to pick from.
Changed your mind? Undo. Every Delete — bulk or a single row — shows its toast with an Undo button for a few seconds, and one click restores everything just removed, under the same ids, exactly as it was. That now covers every destructive button in the app, including three that used to only ask once and forget: the View Builder's "Delete dataset" (which also brings back the shelves/filters/calculations you had in flight on it, and re-selects it if you were building on it), the Quick Views sidebar's delete (which reopens the View if it was the one you had open), and Remove on a sample pack. The restore is workspace-backend safe: an undone delete propagates as a re-creation, so the items come back on your other devices too.
Filing a dataset, connection, job or View into a Folder
Datasets, connections, jobs, saved Views, and (via Repository quick-edit) dashboards all have a Folder field — one home for the item (e.g. "Finance"), distinct from Tags (which can be many). Three ways to fill it:
- Type a new name, or pick an existing one from the suggestions.
- Browse — every Folder field has a Browse button opening a real folder-tree picker: breadcrumb into a folder, search every filed path, or create a new (even nested) one inline, then "Use" it.
- Nest with
Parent/Child(e.g. "Finance/2024") — the "/" only affects how Repository's tree groups it; everywhere else it matches as one whole name.
Once anything is filed, the Datasets / Connections / Jobs / saved-Views lists grow a folder chip strip (All folders / each folder / Unfiled) above their other filters, and each row wears a small folder badge.
Every Datasets, Connections and Jobs row carries its own controls
The catalog pages aren't only lists to scan — most of the day's work happens on the row itself, without opening anything. Each row ends in its own cluster of controls; clicking anywhere else on the row opens the item in its editor, so the controls never fire by accident. They're the same on the compact list and on a Tile view card.
- Datasets — Run re-runs that dataset's query against its connection right there and reports the row count (or the error) in a toast, so you can check something still returns data without leaving the list. Edit opens the dataset editor. Delete (the ✕) asks first, naming any dashboards that depend on it, and its toast carries Undo. Beside them sit two toggles: Pin (the ★) keeps the dataset at the top of the list whatever the sort — most recently pinned first — and private (the padlock) hides it from everyone but you (see marking something private).
- Connections — Test tries the connection with its saved credentials and updates the row's own status dot right where you're looking, which is the fastest way to tell a broken credential from a broken query. Edit reopens the connection wizard on that connection, and Delete (the ✕) asks first and offers Undo — it also counts the datasets pointing at it. The same Pin and private toggles ride alongside.
- Jobs — Run runs the whole pipeline and writes its output dataset in place, which is the one-click annual refresh the Jobs section describes; the row's status dot then goes green or red — hover it for the time and the row count. Edit opens the job editor, and Delete (the ✕) removes the job while keeping its output dataset (with Undo). A private toggle is here too. Jobs are the one of the three with no pin — a job is something you run, not something you keep at the top of a list — so reach for the section's "Last run" sort instead.
Views — browse your saved charts

The Views section is the browse/manage catalog for every View you've saved — a reusable chart, KPI, pivot, or map. The builders (Quick Views and the View Builder) are where you make and edit one; Views is where you find it again. Search by name, narrow by chart type or Folder, and flip between the compact list and Tile view (remembered per device). Each row's icon previews its actual chart type — the same themed thumbnail art the chart-type picker uses.
Every row offers:
- Open — jumps into the builder that made it with the View loaded (clicking anywhere on the row does the same).
- Open in the other builder — a second button always offers the other editor. Any View opens in either Quick Views or the View Builder: a View Builder–made View shows best-effort in Quick Views with a dismissible notice (its shelves, filters, and calculated columns stay editable only in the View Builder), and a Quick View's settings are mapped onto the View Builder shelves as a starting point. The same pair of targets appears on Home's pinned-View cards and in Repository's Quick edit dialog.
- Chart settings the View Builder can't edit are carried, not dropped. A View may be authored with per-chart options the View Builder has no control for — a a map's colour-class count, a table's page size, a quadrant's thresholds and zone labels (the sample packs use several). They're applied to the preview, so what you see is what the View really is, and they're written back unchanged when you update, so the builder never flattens a chart it's only showing you a simplified edit of. A notice names the ones being carried. Changing the chart type does drop them — a quadrant's thresholds mean nothing on a table — and the notice says so.
- Add to dashboard — the same picker the builders use.
- Duplicate — clones chart, folder, and private setting; starts unpinned with a uniquified "(copy)" name.
- Export — a tiny, self-contained single-View
.htmlfile, no open dashboard needed. - A pin toggle (favorites it to the top and to Home), a private toggle, and delete.
+ New ▾ offers both builders — New View (the View Builder) and New Quick View (the fast one-chart path).
Repository — search the whole workspace

The Repository section is one searchable view of every object in your workspace — dashboards, datasets, connections, Views, and jobs — with a chip strip to narrow to one kind (each with a running count). It's the fastest way to find one specific thing once a workspace has grown past a glance.
Browse — a real folder tree
- A "/" in a folder name (e.g. "Finance/2024") nests a collapsible child folder under its parent; a folder's header counts everything inside it, subfolders included.
- Folders sort A→Z at each level; an "Unfiled" group always comes last. Click a folder header to collapse or expand just that branch. All five object kinds carry a folder.
- Tile view (next to the search box) switches every group from the compact list to a tile grid — remembered per device, same toggle as the other catalog sections.
Act — open, edit, file, create
- Open a row and you land in that object's own editor — a dataset or connection opens its modal, a job its editor, a dashboard or View switches you into the right builder with it loaded.
- Quick edit — hover a row for the ✎ pencil: rename it (dashboards keep their title in the builder, so that one is folder-only) or change its folder in place, with an "Open full editor →" link when you need more.
- File by drag — drag a row onto a folder header to refile it instantly (onto Unfiled to clear it). Typing the folder name remains the primary, mobile-friendly way.
- + New folder — create an empty (even nested) folder ahead of filing anything; an empty placeholder shows a ✕ on hover to remove it again.
- + New — create any kind of object right from here (dashboard, View, dataset, connection, job), each routing into its own builder. Viewer-role accounts don't see "New dashboard" — building dashboards needs builder access.
Saved views
Once a search and/or adapter/connection/type/tag pill selection narrows the Datasets or Connections list to something worth returning to, a "+ Save view" box appears next to the filter chips — name it and it becomes a chip of its own. Click that chip later to instantly restore the exact same search text and filters; hover it for a small trash button to delete it. Saved views live in the same workspace-settings store as Dashboards' workbooks, so they sync across devices along with the rest of your workspace.
Marking a dashboard, connection, dataset, View, or job private
A lock toggle on every Dashboards, Connections, Datasets, and Jobs row, and every saved View in Quick Views' list marks that item private — it stays visible to you and to admin accounts, but disappears from every other signed-in account's catalogs (and, for connections, from the connection picker other people see when authoring a dataset, and for datasets, from the source/join/union dataset pickers when authoring a job). This is UX-level gating, the same honesty as the rest of sign-in — an admin account (or local/no-auth mode with no sign-in configured) always sees everything. All five saved-object types now have the toggle.
Admin & backend setup
These topics are for the person who administers a shared workspace — wiring up a real backend, provisioning teammates, and turning on database-enforced per-user security. Everyday building never needs any of it; a local workspace works with no setup at all.
Choosing a workspace backend
A workspace backend is where your whole workspace — dashboards, datasets, connections, jobs, saved Views and settings — lives, and whether it travels to other devices and teammates. All four options query the same way and carry the same objects; they differ in where the data lives, whether it syncs, and how strongly per-user privacy is enforced. Set or switch it under Settings → Workspace backend.
| Backend | Where data lives | Syncs across devices | Per-user accounts & privacy | Setup |
|---|---|---|---|---|
| Local default | This browser only. | No — stays on this device. | Local sign-in only (usernames on this device); private/public flags are UX-level. | None — works instantly, nothing to configure. |
| Turso | Turso (libSQL / SQLite) in the cloud. | Yes — every device pointed at it shares one workspace. | Shared credential (one URL + token); accounts and private flags are UX-level, not database-enforced. | Paste the database URL + auth token; it creates its own schema from the browser (no SQL to run). |
| Supabase | Postgres (via its REST API). | Yes. | Strongest. Real per-user accounts (Supabase Auth / GoTrue) and optional database-enforced Row-Level Security — the only backend where private truly means private at the database, not just hidden in the UI. Add users from the Admin console. | A one-time SQL script pasted into Supabase; an optional polecat-admin Edge Function unlocks in-app go-live + admin-create (see below). |
| Firebase | Cloud Firestore (document store). | Yes. | Enforced by your own Firestore security rules, configured in the Firebase console (not from inside Studio). | Configure Firestore + security rules; collections appear on first write. |
The picker shows three more cards, greyed out and badged Future: PostgreSQL — your own Postgres, exposed over REST (PostgREST); Cloudflare D1 — serverless SQLite at the edge, via a Worker API; and MongoDB Atlas — hosted MongoDB through the Atlas Data API. Each one could host the whole catalog over a REST surface, none has an adapter yet, and they are listed so you can see where the roster is headed rather than wonder whether you missed a setting. They cannot be selected.
The first of the three is worth a sentence, because the page can otherwise look like it contradicts itself: PostgreSQL is greyed here while PostgreSQL (PostgREST) sits in the connector list above as something you can use today — and both are right. A PostgREST connection answers dataset queries. Hosting the workspace itself — the app's own catalog, its schema migrations, the sync protocol — is a separate capability, and only the backends in the table above have it. The same split explains the workspace-capable badge on three of the thirteen connectors.
Short version: stay on Local for a private, single-device workspace; pick Turso for the simplest shared workspace across your own devices or a trusted team; pick Supabase when you need real accounts and database-enforced per-user privacy; pick Firebase if you already run Firestore and manage access through its security rules. The rest of this section covers the Supabase-specific admin flow in detail.
Standing up a Supabase project (the step before any SQL)
Every other Supabase topic below starts one step too late — at “paste this script”. This is the step before it: creating the project. Supabase's create-project screen asks three Security questions and two permanent ones, and the answers are not obvious — one of them is the opposite of Supabase's own recommendation. Each answer below is derived from the SQL this app actually ships, and is named so you can check it yourself.
The three Security toggles
| Toggle | Answer | Why |
|---|---|---|
| Enable Data API | ON — required. | The Supabase adapter is a PostgREST client — the Data API is the only door it knocks on. With it off, the project has no HTTP surface and the connect wizard cannot reach it at all, whatever else you set. |
| Automatically expose new tables to the Data API | OFF — as Supabase recommends. | This one used to be the counter-intuitive answer on this page, and it no longer is.
tools/supabase-deploy.sql now carries its own privileges (its § 6c),
so the recommended, tighter setting is finally the correct one too. Until 2026-08-08 that
file contained no GRANT statements and leaned entirely on the
project's default privileges — so turning this toggle off left you tables with row-level
security and the right policies but no table-level grant to anon/
authenticated, and PostgREST refused every request with an error that looked
nothing like its cause. If § 6c is ever removed, this answer goes back to being the
looser one, and this page has to change with it. |
| Enable automatic Row Level Security on new tables | ON. | Belt and braces. The deploy script already enables RLS and installs a policy on every workspace table itself, so this changes nothing for the tables it creates — it protects anything created outside it later. Note that with Supabase's newer publishable keys a table with RLS on and no policy returns zero rows over the Data API rather than erroring, so RLS-on is the app's normal working state, not a locked-down mode you switch out of. |
The two answers you can only get right at creation
- Save the database password when it is shown. Supabase shows it once and it
is unrecoverable — resetting it is the only other path. Beyond ordinary admin use, the
security-posture test (
tests/rls.mjs) needs it asSUPABASE_PASSWORD, and without it that test skips silently and exits successfully — a green run that checked nothing. - The region is
ca-central-1— Canada (Central). A standard, not a preference, and Supabase cannot change a project's region after creation (the only path is create-new-and-restore). Ignore the dashboard's “Recommended” badge onus-east-1: that is generic geography advice, not advice about this fleet. The reason is concrete —tests/rls.mjsdefaults to the pooler hostaws-0-ca-central-1.pooler.supabase.com, so a same-region project needs no extra configuration, while any other region needsSUPABASE_DB_HOSTset on every run. Combined with that silent skip above, a region mismatch is exactly the kind of mistake that reads green for weeks.
Then: which SQL file to run
The repository ships three Supabase SQL files and they are not interchangeable. For a brand-new project there is one answer:
| File | When | What it is |
|---|---|---|
tools/supabase-deploy.sql |
A fresh environment — this is the one. | The superset, run top-to-bottom and bare (no begin/rollback
wrapper): workspace tables, the verified authenticated-only RLS posture, the activity and
feedback log tables, the first-admin step and a verify block. Idempotent — safe to re-run. |
tools/supabase-rls-real.sql |
Re-tightening a project whose tables already exist. | The security posture alone — the subset of the file above. Nothing to run on a fresh project that has already had the deploy script. |
tools/supabase-bootstrap.sql |
Historical. | The original allow-all demo posture, kept for reference. Not for new environments — it deliberately installs no policies. |
Dev and stage run the same posture as production — the real one. An environment whose security posture differs from prod cannot test the thing most likely to break, so “it's only dev” is never a reason to reach for the allow-all file.
You can also get that same script without leaving the app. Point Settings → Workspace backend → Connect at a blank Supabase project and the wizard hands you a script to paste into the SQL editor — and since v904 that script is the deploy file above: the same tables, the same authenticated-only RLS posture, the same grants, with § 7 pre-filled for the account you are signed in as whenever the app can resolve its Supabase Auth uid. (Before v904 the wizard generated tables and a comment reading “then enable Row-Level Security policies appropriate to your project”, so the one path the UI actually offered left the database wide open. Both paths now install the same thing, and a dev-gate check compares them on every run.) Because that script ends with the database closed to anonymous callers, the wizard asks for this connection's Supabase Auth email and password before it will connect — the anonymous key alone can no longer read or write the workspace.
The Auth fields are required, not optional
On a workspace set up by that script, Supabase Auth email and
password are what make the connection work at all. Every policy is granted to
authenticated callers, so a connection without them signs in as anon,
auth.uid() is NULL, and each policy declines by returning no rows —
the workspace reads as empty rather than refused, which looks exactly like a
database that was never provisioned. Since v907 the app names that state instead of leaving you
to guess: the connect wizard says the workspace enforces per-user security and offers a step back
to the credentials, and an already-saved connection in that state reports it on the Settings
backend card and keeps working from this browser's local copy — it will never adopt the emptiness
over your own work. Only a legacy allow-all database still works on the anon key alone.
Two things about those fields that surprise people, and neither is a fault:
- The password is never stored. Signing in mints a short-lived refresh token
that lives in
sessionStorage, so you re-enter the password once per browser session by design — nothing failed, and nothing is left behind when the tab closes. - The admin function URL genuinely is optional. Leave it blank and everything works; only Go live and admin user-creation fall back to running SQL in the Supabase dashboard. A URL pointing at a function that was never deployed is worse than a blank one — it fails confusingly at the moment you need it.
The two steps the script cannot do for you
Both are once per environment, and both are spelled out in the script's own numbered sections:
- § 7 — the first admin. Create the account under
Authentication → Add user, then insert its
public.usersrow while connected aspostgres(which bypasses RLS — the policies that will guard that table need an admin to already exist). After this one bootstrap, admins are added from inside the app. - § 8 — verify. Run it separately, as the
anonrole. The expected result is all zeros: an unauthenticated reader sees nothing on any workspace table. Anything else means a legacy open policy survived, and it is worth stopping for — permissive policies OR together, so a single leftover defeats every tighter policy beside it.
Things that only bite later
- Free-tier projects pause after about a week idle — which a dev backend will reach. A paused project is unreachable, not refusing, and the app draws that distinction deliberately: it never signs you out or latches a workspace read-only because it couldn't reach the backend, only because the backend answered “no”. Resume it from the Supabase dashboard.
- One project can host several fleet apps. Each workspace records which app
owns it in its
polecat_metamarker table, so Analytics can tell its own workspace from another Polecat app's in the same database, and from a foreign database entirely. Sharing onepolecat_devproject across the fleet is a sound choice, not a shortcut.
The environment topology
The databases mirror the app's own dev → stage → main delivery pipeline — a
separate project per stage, each with its own project URL and database password, so an experiment
can never be run against the workspace that real people use:
- Production — the live workspace. Its posture is verified read-only before anything ships to it, and it is never the target of a test that writes.
polecat_dev— where the security-posture test actually runs today: nightly, and again as part of every promotion to stage. It was created inca-central-1to match production, for the pooler-host reason above.polecat_stage— the matching slot beside stage. Until it exists, stage promotions exercise the dev database.
The posture test only ever builds throwaway schemas of its own and never touches
public, whichever project it is pointed at.
How syncing works (and what the rail indicator means)
With a backend connected there is nothing to "sync" by hand — every change (saving a View, editing a dashboard, adding a dataset…) mirrors up automatically a moment later. The rail indicator (Manage group) shows the live state:
- Local — no backend; everything stays in this browser.
- Connected (green) — the mirror is working; every change writes through.
- Reconnecting… (red) — the last write failed and the app is retrying with backoff. Your work stays safe in this browser and pushes the moment the backend accepts it.
- Read-only — the workspace was created by a newer version of Analytics than the one this browser is running. See below.
“Read-only — created by a newer version”. Every workspace records the schema version it was built at, and the app checks it whenever it connects, refreshes, or quietly re-reads the backend — and whenever a tab you left open becomes visible again, or a device comes back online. Those last two matter because the quiet re-read deliberately skips itself while you have unsaved changes waiting (adopting the backend copy at that moment would overwrite them), so the tab most likely to be out of date was the one that never looked. The resume check reads only the single line that records the version, downloads nothing else, and the save that waking a tab sets off waits for it rather than racing it. If the workspace is newer than the app, the app stops writing to it and says so with a banner: an older build saves a whole snapshot of the tables it knows about, so one push from a stale tab could quietly drop whatever the newer version added. Reading is unaffected — the workspace opens and works normally, and anything you change stays in this browser. Reload the page to pick up the newer app (and if the banner keeps coming back, use Settings → App → Hard reset to clear a stuck offline copy); once the versions line up, the changes you made in the meantime push up on the next Refresh.
Upgrading an older workspace. The opposite case — a workspace made by an earlier version of Analytics — is not a problem: every change to the workspace shape has only ever added to it, so an older workspace keeps working exactly as it always has. It just has nowhere to put the parts newer versions added, which is what turns up much later as a save failing on a table that isn't there. You can settle it in advance: when the versions differ that way, the Settings → Workspace backend card names both of them and offers an Upgrade workspace button. Two things are worth knowing before you press it. First, a full backup of the workspace downloads first, every time — it is not a checkbox you can turn off, and if the backup can't be written the upgrade doesn't run at all. The file is a plain JSON copy of everything the backend holds, taken immediately before anything is changed. Second, the upgrade only adds — nothing you have saved is read, moved or deleted, and it is safe to run twice.
Turso and Firebase workspaces upgrade in one click, because those backends let the app create what it needs directly. Supabase workspaces set up with the current script upgrade in one click too: that script installs a small, admin-only database function whose job is exactly this, so pressing Upgrade workspace runs the upgrade from inside the app. The card tells you which kind of database you have before you press anything. Only an administrator can run it — that check lives in the database itself, not in the browser.
A Supabase workspace created before that function existed still needs one manual run, and the card says so: your backup downloads, then Copy SQL, run it once in Supabase → SQL editor, and press I've run it — re-check. Either way the upgrade adds the missing tables and records the new version; it does not touch your Row-Level Security policies or your grants, so an upgrade can never loosen who can see the workspace.
A failed write also raises a one-time notification and puts the full error on the Settings → Workspace backend card, with a Retry now button.
One error worth knowing: a Supabase workspace provisioned before newer app
versions can be missing the analyses/jobs/users tables —
saving a View then fails with HTTP 404 writing analyses. The Settings card shows the one-time
upgrade SQL in a copyable block: paste it into Supabase → SQL editor, run it
once, then hit Retry now and the pending changes push up.
Atomic saves (Supabase). A Supabase workspace normally saves one table at a time, so a connection dropped part-way through a push can leave it half-written — the newest dashboards saved, say, but not the datasets they read. Installing one small database function makes every save a single transaction instead: it either fully lands or fully rolls back. The Settings → Workspace backend card says which state your workspace is in, and offers the one-time SQL with a Copy SQL button when it isn't atomic yet — paste it into Supabase → SQL editor and run it once. It changes no data, runs under your own database permissions, and is safe to run twice. Workspaces provisioned from now on get it as part of the bootstrap script; ones that skip it keep saving exactly as before. (Turso and Firebase workspaces were already atomic — nothing to do there.)
Sync is also hardened against a flaky network or backend: a single failed request (a rate-limit 429, a 5xx, a dropped connection) retries once on the spot before anything is reported, automatic pushes keep a minimum spacing so rapid editing can't hammer the backend into rate limits, and the Settings backend card shows a Recent sync activity log — the last several push/pull attempts with the exact error text for any that failed, so "it keeps reconnecting" always has a readable reason attached.
Syncing works in both directions on its own: besides pushing your changes up, the app quietly checks the backend every couple of minutes and whenever you return to the tab, silently refreshing the screen when a teammate or another of your devices saved something. It never overwrites edits this browser hasn't pushed yet — if anything is pending here, the check simply waits — and an empty or failed read is never adopted over real local work.
Where credentials and passwords are kept
Studio runs entirely in your browser, so it is worth being plain about what is stored where — and in what form.
- Connection credentials (tokens, keys, database passwords) live in this browser's local storage as plaintext, on every backend including Local. Anyone with access to this device profile can read them, and clearing site data removes them.
- With a backend connected, those same values mirror to the backend as plaintext too — unless you turn on Encrypt secrets on the Settings → Workspace backend card. Then every password-style field is AES-GCM ciphertext in the backend, and only a browser that knows your passphrase can read it back.
- The encryption passphrase is never written to disk. It is held for the current browser session only, so you enter it once per session per browser and every sync in between is silent; closing the tab forgets it. (Earlier versions cached it in local storage next to the data it protects — that copy is deleted automatically the first time this version loads.)
- Sign-in passwords are never stored, only their digests — salted and iterated (PBKDF2-SHA-256), so two accounts with the same password no longer look identical and a stolen user table can't be reversed with a lookup table. Older single-round digests are upgraded automatically the next time that person signs in; nobody has to re-enter anything. Real per-user enforcement comes from Supabase Auth + Row-Level Security, and on a connected Supabase workspace an email sign-in is now decided by that workspace's own Auth rather than by the digest mirrored here (see below). Local and non-Supabase sign-in remains UX-level gating, as described above.
- Your workspace password is never written to disk either. Signing in to a connected Supabase workspace used to save it in local storage, so the app could renew your sign-in when it expired. It now keeps the workspace's own short-lived renewal token instead, for the current browser session only — the same treatment the encryption passphrase gets. A renewal token is deliberately weaker than a password: it works only with that one workspace, can't change your account, and an admin can cancel it without anyone changing a password. Any password an earlier version left in storage is deleted the first time this version loads, and you are not signed out when that happens.
- Exported dashboards never carry a secret. The export strips it and the finished file prompts once, in memory only, the first time it needs to query.
Signing in with real Supabase Auth (optional)
The Supabase connect wizard has two optional fields below the Project URL and anon key: Supabase Auth email and password.
- Blank — Supabase behaves exactly as before; every request uses the shared anon key.
- Filled in — the app signs in with real Supabase Auth (GoTrue) and sends
that session's JWT on every request, so Postgres'
auth.uid()resolves to your real account instead ofNULL. That's the prerequisite for database-enforced per-user privacy (Row-Level Security) on top of the UX-level private/public flags. - A wrong password fails clearly at the "Check database →" step — the same gate that validates the URL and anon key — so it can't silently connect with the wrong identity.
Signing in on a new device is one step. Once a workspace is connected to a Supabase backend, a teammate can sign in on a brand-new browser by entering their email and password on the sign-in screen — the app verifies those straight against the backend's own Supabase Auth (GoTrue), then adopts their account locally. No admin needs to re-add them here first, this browser doesn't need to have seen the account before, and no password is ever mirrored between devices — the backend itself is the source of truth. (Signing in with a plain username, or on a local/non-Supabase workspace, works exactly as before.)
On a connected Supabase workspace, the workspace decides who gets in. When you sign in with an email and a Supabase workspace is selected, the app now asks that workspace's own Supabase Auth first and takes its answer as final. If it says no, the copy of your password digest that happens to be mirrored on this device gets no second vote — so changing (or revoking) a password in the workspace takes effect everywhere immediately, instead of waiting for each browser's mirror to catch up. Two deliberate exceptions:
- Offline still works. If the workspace can't be reached at all — no network, blocked by CORS — the app falls back to the local sign-in exactly as before. "We couldn't ask" is never treated as "the answer was no", so losing your connection never locks you out of your own device.
- Local accounts are unaffected. Plain usernames (including the
adminanddemodemo accounts) and any local or non-Supabase workspace sign in locally, as they always have. Choosing Local only (this browser) in the Workspace picker puts you back on the local path.
Once per browser session, you'll be asked again. Because the password isn't kept on the device, a workspace session lasts as long as the browser session does. Reloading a tab, or leaving one open, never asks; closing the browser completely and coming back does — with your email already filled in, so it's one field. That ask is also a safeguard: a browser that can no longer prove who you are would otherwise carry on as an anonymous visitor, and on a workspace with per-person privacy switched on that looks exactly like an empty workspace. The app waits for you to sign in rather than showing you nothing and syncing that nothing back. Signing out ends the workspace session too, so the next person at this browser starts from scratch.
Picking a workspace on the sign-in screen
The sign-in screen has a Workspace picker above the username field, so a teammate never has to touch connection setup at all:
- Local only (this browser) — the default: everything stays on this device.
The two local demo accounts —
admin/admin(an admin) anddemo/demo(a viewer) — live here; signing into either one switches the workspace back to Local even if a Supabase workspace was picked, so a demo session can never touch a real backend. (On a custom workspace you provisioned yourself, the seededadminaccount is genuinely that workspace's admin and signs into it as before.) - A packaged workspace — workspaces shipped with the app appear here by name. Picking one points the app at that workspace's database; then just sign in with the email and password of your own account there (the one-step direct sign-in above). Who can sign in is controlled by the workspace's admin, not by anything on this device.
- Import access file… — an admin can hand you a small
…-access.jsonfile instead (see below). Importing it adds that workspace to the picker on this device and selects it; the file carries the connection details, never any account password. An imported entry with the same id as a packaged one overrides it — the escape hatch if a database moves before a re-release. - Custom workspace… — opens the full connection wizard for anything else. When the wizard finishes, the app asks you to name that workspace and adds it to this picker (already selected), so it's there the next time you sign in — and if you gave the wizard a workspace email, that's filled in for you too, leaving just the password. The saved entry keeps the connection details only; the sign-in you typed into the wizard is never stored in it. Connecting to a workspace that's already in the list just re-selects it rather than adding a second copy.
- Manage workspaces… — opens the list itself, right on the sign-in screen (it also opens by itself the moment the wizard finishes, so a workspace you have just defined can be handed on straight away). Each entry can be renamed, set as the default — the workspace this browser opens on, so a device that always works against the same one arrives ready — exported as an access file, or removed from this device. Removing an entry only forgets it here; the workspace itself, and everything in it, is untouched. Workspaces that shipped with the app can be defaulted and exported but not renamed or removed — they come back on the next load. The same list is in Settings → Workspace backend → Saved workspaces once you're signed in, and it is the same list: a rename in one place is a rename in both.
Picking a workspace only points the app at it — nothing is read or written until you actually sign in, so an unauthenticated device can't disturb (or be disturbed by) the workspace's data. Admins create the access file from Settings → Workspace backend → Export access file on any connected device, or from any entry in the saved-workspace list — including on the sign-in screen, so you can define a workspace and hand someone the file without signing into it first. It contains the workspace's connection key (share it only with people who should be able to sign in), and it deliberately strips the connection's own service sign-in — each teammate authenticates as themselves.
What follows you between browsers
On a connected workspace, your content has always roamed — dashboards, Views, datasets, connections, jobs, folders, and pins live in the workspace and arrive on any device you sign into. Two more things now roam too: branding (the app name, link, and icon an admin sets) is workspace-wide — everyone who connects sees it — and your theme (color theme + light/dark) is saved on your own account and applies wherever you sign in. The two local demo accounts stay browser-local, since they have no backend identity. Your wider personal setup roams as well: Simple mode, the restore-unsaved-work opt-in, the builder's side-panes preference, sample visibility, pane sizes (Studio's side panes, the View Builder's datasets pane and chart canvas), and your tile/list layout picks for Dashboards and the Repository — all saved on your own account row and applied when you sign in elsewhere.
Invite hot links (prefilled sign-in)
For a hand-out that's even lighter than an access file, an admin can send a teammate a hot link that pre-selects the workspace and prefills their credentials, so all they do is click Sign in:
https://analytics.polecat.live/app/#ws=WORKSPACE-ID&user=EMAIL&pass=PASSWORD
ws is the workspace's picker id (the packaged Polecat workspace is
polecat; local works too), and user/pass are
optional — include either or both. Values must be URL-encoded if they contain characters like
@ or & (an encoded @ is %40). The
credentials ride the URL fragment (everything after #), which browsers never
send to any server, and the app scrubs it from the address bar the moment the page loads — it
won't sit in the location bar or be bookmarkable after that first load. Treat a hot link exactly
like the password it contains: share it over a channel you'd trust with the password itself, and
don't put one anywhere public. Links that carry a workspace this browser doesn't know prompt the
recipient to pick one or import an access file instead.
Adding users when connected to Supabase (in-app account provisioning)
Once the workspace backend is Supabase, the Admin page's + Add user form gains an Email field and creates a real Supabase Auth (GoTrue) account via the public sign-up endpoint, stamping the account id onto the user's row:
- No Supabase dashboard visit, and no admin/service-key credential ever handled in the browser — every account is managed from the Admin console. Only the very first admin needs a one-time bootstrap in Supabase itself; editing an existing user never repeats the sign-up.
- Two Supabase project settings can leave a new account temporarily unusable, and the form explains each instead of failing silently: sign-ups turned off, or email confirmation still required (both under Authentication → Providers → Email — adjust, then add the user again).
Per-user provisioning defaults (first sign-in)
The Admin page's Add user (and Edit user) form carries two pickers that set an account up before its owner ever signs in: Default theme, and Sample pack — a list of every pack this build ships, so an account can arrive with whichever of the sample packs suits the work it was created for, not just one. Set either and that new account logs in ready: the app color theme switches to the one you picked, and the pack installs itself — no manual setup, no digging through Settings. This only ever runs once, the very first time the account signs in anywhere; after that it's stamped as provisioned and later logins never fight whatever the user has since changed for themselves. Leaving the theme at "Don't set" or the pack at "Don't install one" simply skips that part.
The same form also has a "Copy my current Dashboard defaults" button — click it and Studio snapshots your own Settings → Dashboard defaults (default subtitle, accent color, header logo, header background, title size, subtitle style, dashboard theme and card style), together with your stored Quick-import creativity level, and stashes the lot on the new account. That last one is worth naming because you cannot see it: its row on the Settings card is hidden for now, but the stored value is real and it travels with the snapshot like everything else. At their first sign-in, that whole "house style" applies automatically alongside the theme/pack above, same once-only rule. Clear removes a previously captured snapshot before saving.
Disable a user (without deleting them)
Every row in the Admin users list has a Disable button. Disabling an account blocks sign-in — on every device — with a clear "this account has been disabled" message, and if they're signed in somewhere right now, that session ends the next time the app loads. Nothing is deleted: the account, its role, its provisioning, and everything it owns stay exactly as they were, and Enable restores sign-in instantly. You can't disable your own signed-in account, and the last admin still able to sign in can never be disabled (there'd be nobody left to undo it). Use this instead of Remove when someone might be back — or when you want their work to stay attributed while they're away.
Force the welcome tour at next sign-in
The same Add/Edit user form has a "Show the welcome tour at their next sign-in" checkbox. Normally the welcome tour appears only the very first time a browser visits the app — so if someone's device has already seen it (or you're re-testing an account), a fresh sign-in shows no tour at all. Check this box and the account's very next sign-in opens the welcome tour anyway, on any device. It's one-shot: the flag resets itself the moment the tour is shown, so later sign-ins go straight in as usual. Re-check it any time you want to force it again. (Anyone can also replay the tour themselves whenever they like from the Home page's "Take the tour" card.)
Managing multiple workspaces (admin)
The Admin page's Workspaces card saves the databases this app can work in — Turso, Supabase, or Firebase — under a name, with credentials, ahead of connecting to any of them. Saving one is just remembering it for later: it does not sync, mirror, or connect anything by itself. (The card was called Backends until the two lists became one; "backend" now names only the state — Workspace backend — Local (this browser) — never a row in this list.)
- + Add workspace — pick the kind of database and fill in the same fields the connect wizard uses; Test checks credentials without connecting.
- Each saved workspace has Edit/Test/delete, plus a Connect button running the exact Settings → Workspace backend flow. Only the currently-connected one is active (an active badge shows which); a workspace with users assigned shows a count badge like "2 users".
- Once one is saved, Switch backend in Settings offers this list first — one-click connect, or "Enter connection details manually" for something new.
It is one list. The workspaces saved here, the workspaces you save at the sign-in screen, and the entries Settings → Workspace backend manages are all the same saved list — so a workspace saved in Admin can be signed into from the Workspace picker, and a workspace someone saved (or imported from an access file) at sign-in can be assigned to a user here without re-typing its credentials. Removing an entry in either place removes it in both; the database itself is never touched. A workspace you started but never finished configuring is kept and still editable here — it carries a not configured badge and is simply not offered at sign-in, since it cannot yet say where it points. The workspaces packaged with the app are offered at sign-in as well, but are not managed on this card: they ship with the build.
Once at least one workspace is saved, the Add/Edit user form (same "Per-user provisioning defaults" area above) also gains an Assigned workspace picker naming which saved workspace that account belongs to — shown back on their row in the Users list too (→ Workspace name).
The assignment is real: it connects at sign-in. Assigning a workspace stores its connection details with the account, so they travel wherever the account signs in. On a brand-new device (nothing user-made stored locally) the app connects to the assigned workspace automatically — no setup, nothing to type. On a device that already carries local work, the app asks first, since switching adopts the workspace and replaces what's stored locally; declining is remembered per workspace, so sign-in never nags. And if it reads back empty — the signature of a locked-down (per-user security) database that needs its own sign-in — the app refuses to adopt it over your local data and points you at the sign-in screen's Workspace picker instead, which handles that case properly.
Going live with real per-user security (Supabase)
The private/public flag on every object is UX-level by default — hidden from other accounts in the app, but the Supabase backend still stores everything under one shared key. An admin can flip that to real database-enforced Row-Level Security from the Admin page's "Enable per-user security / Go live" card:
- One-time deploy (repo maintainer, not the app): the
polecat-adminSupabase Edge Function — seetools/M7-RLS-GOLIVE-RUNBOOK.mdfor the exact commands. - Paste its URL into the Supabase connection's Admin function URL field, and sign in as the account that should become the first admin (the Auth email/password fields).
- Click Go live and enter the one-time provision secret set at deploy time — used once, never stored.
Go live wipes the current workspace tables (your browser's own data is untouched), seeds you as the admin, and applies the real per-user policies — from then on, private truly means only you (and admins) can see it, enforced by the database. With the relay deployed, + Add user also switches to the more secure admin-create path automatically (no email-confirmation step); plain self-signup stays the fallback without it.
First-admin bootstrap is automatic. Go live records your own admin account for you (in the database) before it runs, so on a brand-new project it no longer stops with "This account is not an admin of this workspace" — you don't have to hand-run any SQL in the Supabase editor to make yourself the first admin. It also refreshes your Supabase Auth sign-in each time, so a session that quietly expired won't surface as "That sign-in session is no longer valid" — just click Go live (or + Add user) again. The one prerequisite the app still can't do for you is the maintainer's one-time polecat-admin function deploy above.
Exporting dashboards
Click Export ▾ in the topbar to open the export menu (it only shows while Studio is open). Every option opens an export modal with file size, a copy-to-clipboard button, and a download button. On a phone the topbar has no room for that button — take ⋯ More → Export… instead, which opens the same menu (the whole toolbar moves there).
What data travels with an export. A source with no engine behind it — sample-pack content, authored datasets, View Builder results — can't be re-queried from a standalone file, so the export always bakes in a data snapshot taken at export time: the file shows real numbers anywhere you host it, and simply doesn't update until you export again. (View Builder Views snapshot their actual computed rows.)
Live data sources pick a data mode. When the dashboard queries live remote sources (a connected database, a Google Sheet, a warehouse…), the .html export first asks what should travel inside the file:
- Data snapshot (recommended, the default) — the app runs each live source once at export time and bakes the rows in. No credentials are included; the file works anywhere, offline, forever showing that moment's numbers. A source that can't be reached at export time stays live in the file instead of exporting empty.
- Live — ask for credentials on open — the file keeps querying your sources live, and whoever opens it is prompted for the credentials. None are embedded.
- Live — credentials embedded — the file queries live and carries your credentials in plain text so it works standalone. Anyone who gets the file can read them — choose this only where you'd share the credentials themselves. This choice is never remembered as a default; you must pick it deliberately each time.
Dashboards with no live remote source skip the dialog — everything in them is already a snapshot. (A dropped local file's data always travels inside the export; it needs no mode.)
| Format | What you get | Use when |
|---|---|---|
| Dashboard (.html) | A fully self-contained .html file with all toolkit JS/CSS inlined. No server required — open in any browser. (Internally this is the Dashboard Framework export — the name you'll see in a text View's inspector note — but the button says Dashboard (.html).) |
Sharing demos, embedding in portals, or hosting as a standalone dashboard on any static web host. |
| Excel workbook (.xlsx) | A genuine multi-sheet Excel workbook (opens in Excel, Google Sheets, or LibreOffice — no warnings, built entirely in your browser with no upload). Tab 1 is a Dashboard summary: the title, every KPI with its value, the list of Views (title, chart type, data source), and any filters. Each following tab holds the backend data behind one data source the dashboard uses — its columns and rows — with two Views on the same source sharing one tab. Downloads straight to your machine. | Handing the numbers behind a dashboard to someone who wants to pivot, chart, or re-cut them in Excel — the visual on tab 1, the raw data right after. |
| Word document (.docx) | A genuine Word report (opens in Word, Google Docs, or LibreOffice — built in your browser, no upload). The dashboard title and description, a KPIs table (name + value) and a Views table (title, chart type, data source), then a table of the backend data behind each data source the dashboard uses. Downloads straight to your machine. | Dropping the dashboard's numbers into a written report, memo, or brief — headings and tables you can edit, not a picture. |
| PowerPoint (.pptx) | A slide deck (opens in PowerPoint, Google Slides, or LibreOffice Impress — built in your browser, no upload). A title slide, a KPI slide (each KPI with its value), then one slide per View carrying that View's chart as a full image (rasterized from the live preview); a table/text View that has no chart falls back to a text slide naming its data source. | Turning a dashboard into a walkthrough deck for a meeting — one chart per slide, ready to present or annotate. |
| PDF (print) | Asks for page size (Letter/A4/Legal), orientation, and scale, then opens the same self-contained dashboard export in a new tab and starts your browser's print dialog there — choose "Save as PDF" (or a real printer). A print stylesheet keeps Views/KPI rows from splitting across a page break and sets sane page margins; "Fit to page width" (the default) scales a wide dashboard down uniformly so nothing gets cropped at the page edge. | A one-off PDF or hard copy of the current dashboard, sized for the paper you're printing to — no separate export step to manage. |
| Editable spec (.studio.json) | The dashboard's own source of truth — the JSON spec the builder reads and writes. It carries the layout, every View and KPI, the filters and the dataset references, and nothing else: no toolkit, no baked rows, so it stays small. Open it again with Open… → Open file…, or publish it anywhere public and hand out the link (Import from URL…). | Version-controlling a dashboard, handing it to someone who will keep editing it, or moving one between workspaces. |
| All artifacts (bundle) | Both artifacts together — the .html and the editable .studio.json spec. |
Full delivery: the rendered dashboard plus its editable source of truth. |
Saving and loading your work
- Save (Ctrl S / ⌘ S; ⋯ More → Save on a phone) — saves the dashboard to your Dashboards catalog (and cuts a version-history checkpoint). To get a file instead, use Export ▾ → Editable spec (.studio.json) — that file is all a dashboard needs.
- Save as… (⋯ More → Save as… on a phone) — saves the current dashboard as a brand-new one (you name it) instead of overwriting the one you opened. Sample/demo content is always read-only: hitting plain Save on a sample dashboard automatically opens the Save-as prompt instead of silently overwriting it, so removing or reinstalling a sample pack never clobbers your edits — they live on in your own copy.
- Open (⋯ More → Open… on a phone) — picks a dashboard from your catalog; its footer also offers Open file… (accepts
.studio.jsonor a previously exported.html— Studio embeds the spec in exported files) and Import from URL…. - Auto-save — the Studio quietly saves your work to browser localStorage after each edit. Turn on Restore unsaved work in Settings (off by default) and, if you accidentally close the tab, a banner offers to restore that draft on your next visit.
- Workspace backend — your Dashboards catalog lives in the same local-first workspace store as your Connections and Datasets, so if you've connected a workspace backend in Settings (Turso, Supabase, or Firebase), saved dashboards — including their pins and workbook filing — mirror to it automatically and follow you to another browser via Refresh. Nothing changes if you haven't: everything keeps working purely on this device.
Embedding a single View
Select any View and click Export this View… in its inspector to download a tiny, self-contained .html View with just that one chart — the same toolkit, minus the rest of the dashboard's KPIs, filters, and other Views. Drop it anywhere you'd embed a standalone chart.
Saving a chart as an image
Select any View and click Save chart as PNG in its inspector to download the chart itself as a PNG image — handy for dropping into a slide deck or a doc. This covers chart types that render as SVG (the vast majority) plus the Map (US choropleth) chart's Interactive GL renderer (captured straight off its canvas); Table and Richtext Views don't support it yet. The image is the chart only — for donut/gauge-style charts whose legend is drawn inside the chart it's included, but a legend rendered as a separate element next to the chart is not.
Downloading a View's image or data, right on the canvas
Hover any View — in the builder or a published/embedded dashboard — and a small Export button appears in its top-right corner, offering:
- Download PNG image — for SVG-rendered chart types and the GL map renderer (not Table/Richtext).
- Download data (CSV) — the View's own query result, header row included; shows for any View with a bound query.
- Export as standalone HTML (while you're building — never in a published dashboard) — the same self-contained single-View file as the inspector's "Export this View…", one click away. It works from the View Builder's live preview too, where it exports the View you're building, carrying the numbers on screen.
Each is on by default and can be turned off independently per View under the inspector's Downloads section — useful for a View you don't want viewers exporting from a published dashboard.
Saving a View to the View library
Select any View with a bound query and click Save to View library in its inspector to snapshot it as a reusable View — the same View library Quick Views saves into. Give it a name and it shows up in the builder's Data panel under Views (drag it onto the canvas, or use its "+ Add to dashboard" chip) and in Quick Views' own saved list, exactly like a View built there. It's a self-contained copy — editing or deleting the original View's dashboard afterward doesn't affect it. Not available for Richtext/annotation Views, which have no query to snapshot.
Keyboard shortcuts
These listen on the page, not on one pane, so they work anywhere in the app — with one exception that covers all of them: nothing fires while you are typing in a text field, a text area or a dropdown. The rows marked View selected act on whichever View is selected on the builder canvas, so select one first. Open the same reference in the app with ?.
| Keys | Action |
|---|---|
| Ctrl K / ⌘ K | Open the command palette (works from anywhere, any section) |
| Ctrl Z / ⌘ Z | Undo the last structural edit |
| Ctrl Shift Z / ⌘ Shift Z | Redo |
| Ctrl Y / ⌘ Y | Redo — the Windows alias, same action |
| Ctrl S / ⌘ S | Save to your Dashboards catalog |
| Ctrl D / ⌘ D | Duplicate the selected View or KPI |
| Ctrl F / ⌘ F | Focus the builder's Data panel search — on a phone this opens that pane's drawer first |
| / | Focus the chart-type gallery's search box (View selected, gallery on screen) |
| ↑ / ↓ | Reorder the selected View up / down |
| Shift ← / Shift → | Resize selected View span narrower / wider |
| Delete / Backspace | Delete the selected View or KPI |
| Escape | Leave Focus mode; deselect the View; close any open modal or menu |
| Tab | Move through the interactive controls of whatever is on screen |
| ? | Open / close the keyboard shortcuts reference modal |
The in-app shortcuts overlay closes with two things that aren't keys, so they have no row above: double-click a View's title on the canvas to rename it inline, and the ↗ button on a View zooms it to full screen (Escape closes it again).
Reading a dashboard without a mouse
In the Viewer and in any dashboard you've exported as a self-contained file, KPI tiles are buttons: Tab moves through them, a focus ring shows which one you're on, and ↵ or Space opens that tile's detail — the same drawer of underlying records a click gives you. Inside the drawer, Escape closes it.
The same is true of the chart marks that do something when clicked — the bars of a bar chart, the slices of a donut, the tiles of a treemap, wherever that View has a detail drawer or a click-through set up. Tab stops on each one and ↵ or Space activates it. Because a bar or a slice has no text of its own, each one announces itself as its label and value — "Snowflake: $84.2K" — so a screen reader reads the same fact the hover tooltip shows. Marks that aren't clickable stay out of the way entirely. On the Studio canvas, clicking a mark still selects the View for editing, so the builder's keyboard behaviour is unchanged.
The tooltip opens on focus, too. Tabbing onto a mark pops the same tooltip card hovering it with a mouse would — the exact figure, the percentage, the share of the total — anchored to the mark itself rather than to a cursor that isn't there. It disappears when you tab away, and Escape dismisses it on the spot without losing your place, so a card sitting over the next bar is never in your way. Reaching a mark with the mouse is unchanged: the tooltip keeps following the pointer.
Table rows that open a detail drawer work the same way from the keyboard — Tab stops on each clickable row, ↵ or Space opens the drawer — with one deliberate difference: a row is not turned into a button. Doing that would stop it being a row, and a screen reader would lose the table itself (which column you're in, which header a cell belongs to). So the row keeps its table semantics, announces that it opens a dialog, and reads out its own cells rather than a summary. Sorting, filtering and paging redraw the table, and every new page behaves the same; a table with no detail drawer bound keeps its rows out of the tab order.
Command palette
Press Ctrl K / ⌘ K anywhere in the app — or click the Search… pill in the top bar — to open a fuzzy-searchable list of commands, then jump straight to any of them by typing part of its name. Use ↑/↓ to move, ↵ to run, Escape to close.
Every row carries a small family tag on the right, and the palette prints fourteen of them. Section and Reference are the left rail's own places to go — that list is built from the rail itself, so a section your role can't reach (or one an admin has turned off for viewers) is missing from the palette too, rather than being offered and then refused. Create, File and Export are the dashboard lifecycle: a new or a sample dashboard, opening and saving one, and the three export formats. Edit is undo, redo, adding a text View and editing the JSON spec; Data is a new dataset or connection. View and Present are the modes — light/dark, Focus mode, Simple mode, Slideshow, Demo mode. Learn is the welcome tour, the interactive tutorial and this keyboard reference (it is the family every tour means when it says "⌘K → Interactive tutorial"), and Manage is clear local data and sign out. Sample and Recent are every curated example and every dashboard you've opened, and Add View is one command per chart type in the gallery.
Five of those families are labelled from whatever the workspace currently holds, so each one reads as a fixed prefix plus the thing's own name — and the prefix is what you type to reach the family: "Go to <section>" (with "Open Help & docs" as the one section that reads as a reference to open rather than a place to go), "Open sample: <name>", "Open dashboard: <name>", and "Add View: <chart type>", which creates a View bound to a catalog query and switches it straight to that type. These are the words the palette matches on, so they are the ones worth typing: search covers a command's visible label, its family tag and its hidden synonyms, and — like every other search box in the app — every term you type has to match, in any order. Those hidden synonyms are why an older word still works: a View on a dashboard used to be called a panel, and typing that still finds both commands that add one.
Open the palette with an empty box and the commands you have run before come first, most recently run first — that ordering is recency alone, not a popularity chart. How often you have run something counts only once you start typing, and only as a tie-breaker: how well a command matches what you typed decides the order, and usage separates commands that match equally well.
Voice command mode: on a browser with the built-in Speech Recognition API (Chrome, Edge, Safari — no API key, no backend, no sign-up), the palette shows a small mic button beside the search field. Click it, say a command out loud ("add a bar chart", "go to settings", "toggle light dark theme"), and it runs the same way pressing ↵ on that text would — fully hands-free. The mic pulses red while listening and stops on its own once you finish speaking. Not shown at all in browsers without the API (e.g. Firefox) — typing still works everywhere.
Glossary — every term, one line each
These are the app's own words, not ours: every kind of thing you can make (the five the Repository indexes), every place you make it (the three builders on the rail's Build group), and the two ways they are filed. One line each, spelled the way the screen spells it.
- Adapter — the driver that speaks one backend's language (Postgres, Supabase, Snowflake, BigQuery, Google Sheets, CSV/JSON files…). You never pick one directly — choosing what to connect to picks it for you.
- Connection — an adapter plus your credentials, pointing at one place data lives. Credentials stay in your browser by default.
- Dataset — a named, reusable query on a connection; the building block every chart draws from.
- Data access (DA) — the named query a View is actually bound to, listed in the builder's Data panel; a dashboard can own one outright or borrow the workspace's Datasets and saved Views.
- Job — a prep pipeline (rename, filter, aggregate, roll up) whose output lands back in Datasets as a new dataset.
- View — one chart, KPI, map, or text block; the unit dashboards are made of. Built in Quick Views (fast) or the View Builder (shelves, pivots, calculations).
- Ensemble — several sources estimating the same thing shown as one common estimate: the median of the providers left toggled on draws bold, and each provider stays visible as supporting evidence.
- Dashboard — Views arranged in the Dashboard Builder into a page you can feature on Home, share read-only, and export as a self-contained file.
- Workbook — a named group of dashboards, used as a filter chip in the Dashboards section.
- Folder — a filing path any catalog row can be given — datasets, connections, jobs, dashboards, Views, and Workbooks too — so long lists group instead of sprawling.
- Repository — one searchable index over all five catalogs at once (Dashboards, Datasets, Connections, Views, Jobs), with the folder tree beside it and + New folder to seed an empty one.
- Filter — a dashboard-level control that narrows every View wired to it; "Since …" filters keep everything from the picked value onward.
- Sample pack — installable demo content (datasets, Views, dashboards, a job) so you can explore before connecting anything. Install/remove from Settings.
- Workspace backend — where the whole workspace syncs (Supabase, Turso…), so it follows you between devices; Local keeps everything in this browser.
Simple mode vs Advanced mode
There are four ways in, and they are the same switch — flip it anywhere and every other one follows. The ⋯ More menu; the ⌘K command palette (type “simple”); the two quick switches at the foot of the left rail, where Simple mode sits beside Dark mode; and the Settings page, where it is one of the switches in the Mode group. The setting persists across sessions, and while it is on the top bar carries a Simple mode badge, so you always know which mode you are in.
Simple mode
What it takes away:
- 15 chart types shown in the gallery (the most universally understood).
- Every advanced section of the View inspector is hidden — all fifteen advanced inspector sections, in the order the inspector builds them: Drill-through, Detail drawer, Cross-filter, Target line, Reference band, Callout arrow, Period highlight, Event markers, Point annotations, Conditional formatting, Color scale, Compare to, Click-through, Calculated columns and Output options. Nothing is lost — a dashboard that already uses one keeps it, and switching back to Advanced mode shows the section again with its settings intact.
- The Data panel becomes read-only browse + drag, and so
does the one link the View inspector offers out of it. Five authoring controls are hidden,
all of them in the Dashboard Builder:
- the Data panel header's + New ▾ button;
- the Create a new data source button on the This dashboard’s datasets group header;
- that same This dashboard’s datasets group's per-card Duplicate and Delete actions;
- the My queries group's per-card Edit data source and Delete data source actions;
- and the Edit data source jump link in a View's Data section.
Nothing outside the builder changes. The workspace catalog pages keep every authoring control in both modes — the Repository page still offers Quick edit on a row, + New folder, and the Select bulk bar, and Datasets, Connections, Jobs and Views are untouched. Simple mode is a quieter builder, not a read-only app.
And what it adds, all of it in the builder:
- A Simple mode is active note at the top of the dashboard inspector, with a Switch to Advanced mode button — the one-click way back.
- A guided Getting started checklist while the dashboard is still empty, and a What's next? card once the first View or KPI is on the canvas.
- Guided column setup in a View's Data section (every chart type except Rich text): a nudge when no dataset is bound yet, a warning when the query has no columns, and — when the slots are simply empty — an Auto-pick columns button that assigns them by name.
Advanced mode (default)
- All 54 chart types + specialist annotation and interaction tools.
- Full data-source authoring: author SQL (sample engine) and direct-connector data accesses; create compound joins and unions; manage workspace connections and datasets; configure output options.
- All fifteen advanced inspector sections listed above are on the View inspector, none of them hidden.
The Settings page — every card on it
Settings is pinned at the foot of the left rail, and it is one scrolling page of cards. In the order they appear:
- Account — who you are signed in as, with your role beside the name (and a demo account note when it is one), and the Sign out button. This card is why Settings stays in the rail for a viewer even when an admin has switched every other section off.
- Workspace backend — which workspace this browser is working in and whether it is syncing, with Connect / Switch backend, the Recent sync activity log and the access-file export. It has a chapter of its own: Admin & backend setup.
- Appearance — Dark mode, plus the Color theme picker's palette cards.
- Mode — three switches: Simple mode, the subject of this chapter; Restore unsaved work, off by default, which offers on your next visit to bring back a dashboard you were editing but never saved; and Open the builder with side panes, also off by default, which decides whether the Dashboard Builder opens with its Data and Inspector panes already out.
- Presentation — Demo mode, which simulates a live-refreshing feed for a stakeholder demo, and Welcome tour, whose Take the tour button re-runs the guided walkthrough. This card is where the tour lives; it is not on the top bar, and ⌘K → Take the tour reaches the same thing.
- Dashboard defaults — the house style every brand-new blank dashboard starts from, plus Style presets. Detailed immediately below.
- Sample packs — install or remove each sample pack, one pack at a time, with each card naming where its data came from. This is the only sample-content switch there is.
- Data — Export settings / Import settings, described under Backing up your settings below.
- App — Hard reset, which throws away this browser's offline copy of Analytics (its service worker and cached files) and reloads so every file is fetched fresh. It is the remedy for an update banner that keeps coming back however often you reload. It removes nothing you have made — your workspace, dashboards, datasets, connections and settings are all untouched — which is what makes it different from ⋯ More → Clear local data, and the one to try first.
Three more cards look like Settings cards but live on the Admin page instead, because they are app-wide rather than yours: Section access, Workspaces and Branding & app identity.
Dashboard defaults
The Settings page's Dashboard defaults card sets the house style every brand-new Blank dashboard starts from — handy for teams with a house tagline, brand color, logo, or whole-look theme. Seven fields, each matching its per-dashboard counterpart:
- Default subtitle, Default accent color, and a Default header logo upload.
- Default header background color — a flat banner fill; blank keeps the standard gradient.
- Default title size / Default subtitle style.
- Default dashboard theme — the same whole-look choice as the per-dashboard picker. Left unset, it follows whichever app Color theme is active until you pick an explicit default.
Existing dashboards, examples, and imports are never touched. Carried along by Export/Import settings and included in Clear local data.
Below that, Style presets lets you save the current set of defaults above as a named preset — type a name and click "+ Save as preset". Save more than one (e.g. one per client) and click Apply on any saved preset to switch which one is the active default; the trash button removes a preset. The preset whose saved fields match your current defaults is marked Active (a highlighted row + pill), so you can tell at a glance which house style is live — change any default and the marker clears until a preset matches again. A preset with a saved logo shows a small thumbnail instead of a plain color swatch. Presets travel with Export/Import settings and are included in Clear local data too.
Backing up your settings
The Settings page has a Data card with Export settings / Import settings. This saves/restores your theme, mode, workspace connections, and pane-layout preferences as a .json file — handy before clearing local data, or to carry your setup to another browser or device. It only covers app preferences, not dashboard content (use Save / Open for a .studio.json).
Branding & app identity
The Admin section has a Branding & app identity card — it's app-wide (what everyone who opens the workspace sees), so only admins can change it. It controls three things:
- App mark — the icon at the top of the left rail: Default (the Analytics mark), Custom logo, or None. A custom logo should be a square PNG/JPG/SVG (ideally a simple, transparent-background icon around 64–256 px, up to 200 KB) — it renders small in the rail and tiny in the browser tab, so avoid fine detail or lots of text.
- Favicon — when you set a custom logo it also becomes the browser-tab favicon, so a white-labeled app matches in the tab.
- Suite name — the small label under the app name in the rail (polecat.live by default). Set your own short white-label name (up to 24 characters — keep it to a word or two so the rail lockup stays tidy) or hide it entirely. A custom name can also carry its own Custom link — where clicking the name goes (e.g. your organization's site, opened in a new tab); leave the link empty and the name is plain text. The default label keeps pointing at polecat.live.
Branding is saved with the workspace — on a connected workspace it syncs to the backend and applies for everyone who signs in, on every device (a custom logo shows on the sign-in screen too). Export settings also carries a copy along. This only affects the app's own chrome — it's independent of any exported dashboard's branding.
Sending feedback (and what gets recorded)
The speech-bubble button in the top bar (next to What's-next) opens a tiny dialog — on a phone the top bar keeps only its essentials, so you'll find Send feedback in the ⋯ menu instead, right below What's new. Either way: pick a kind — Bug, Feature, Comment or Question — optionally say more, and hit Send.
Alongside what you typed, the report carries a small block of context, captured automatically so that a one-word report still tells the team exactly where you were. This is the whole of it: your account name and your sign-in id (both empty if you aren't signed in), the section you were on, the open dashboard's id plus its title (only when one is open), the route in your address bar, the app version, your window size, and your browser's user-agent string. Nothing else travels with it — not the contents of the page you were on, and none of your workspace data.
Reports go to an admin-only log in the workspace backend; on a local-only workspace they wait on your device and send once a backend is connected. Separately, the backend keeps a concise activity trail — sign-ins/outs, dashboard opens, exports, and one time-on-page event per visit — also admin-only, with nothing visible to anonymous readers.
On the deployed site the trail covers anonymous and local visits too: a visitor who never signs in (or signs in to a local-only account) still records a sign-in-screen view and a time-on-page event, delivered through the packaged Polecat workspace. Those rows carry the route, referrer, and viewport from the browser, and the server stamps the requester's IP address and user agent on arrival (a browser can't see — or spoof — its own public IP). Writing is all anonymous visitors can do: the log stays admin-only to read, and an anonymous row can never claim a signed-in identity. Local development pages never send anything.
Color theme

The Appearance card on the Settings page has a Color theme picker (separate from the Dark mode switch) — clickable palette cards, each previewing its own actual chrome colors so you pick by looking. It offers the same seven looks as the Dashboard theme picker, so app chrome and dashboards read as one system:
- Polecat — warm terracotta/plum/cream house palette (the default)
- Classic Blue — the original chrome
- Fleet Modern — the cooler fleet palette
- High Contrast — stark black/white, solid borders, maximum legibility
- Editorial — paper-and-ink boardroom mood
- Neon — synthwave near-black with electric cyan/magenta accents
- Conservation — the CTIC olive/field-green + deep-pine look
Good to know:
- Every theme has light and dark variants, so it combines freely with Dark mode; the active card shows a checkmark.
- Your app theme also seeds the default Dashboard theme for brand-new dashboards (unless you set an explicit Default dashboard theme) — Conservation app + Conservation dashboards in one click.
- Carried along by Export/Import settings; exported dashboards keep their own theming, independent of the app chrome.