Export Metabase questions to Google Sheets, including the public link that works with IMPORTDATA and the session tokens that quietly expire on you.
Metabase already holds the queries you care about, so the job is getting a saved question's results into a sheet on a schedule. There is a genuinely no-code route through public links and IMPORTDATA, and an API route for private data. The main thing to get right is authentication: session tokens expire on a timer and take your scheduled report down with them.
The no-code option: public link with IMPORTDATA
An admin enables public sharing under Admin settings → Public sharing. Then, on the question, use the sharing menu to create a public link. Appending .csv to that link returns the results as CSV, which Sheets can read directly:
=IMPORTDATA("https://metabase.yourcompany.com/public/question/UUID-HERE.csv")Honest warning: a public link has no authentication at all. Anyone who obtains the URL sees the data, and the URL will end up in browser history, sheet revision history and anyone's clipboard. Use this for aggregate or non-sensitive figures, never for customer records or anything with PII.
Metabase to Google Sheets for free (Apps Script + API)
For private data, create an API key in Admin settings → Authentication → API keys and send it as an x-api-key header. Older Metabase versions without API keys require posting credentials to /api/session to obtain a session token, which expires after about two weeks, so prefer keys where available.
function importMetabaseQuestion() { const METABASE = "https://metabase.yourcompany.com"; const API_KEY = "mb_XXXXXXXXXXXX"; // does not expire on a timer const CARD_ID = 148; // number in the question's URL // Note: POST, not GET. A GET here returns 404 and looks like a bad ID. const response = UrlFetchApp.fetch( METABASE + "/api/card/" + CARD_ID + "/query/csv", { method: "post", headers: { "x-api-key": API_KEY }, muteHttpExceptions: true, } ); if (response.getResponseCode() !== 200) { throw new Error(response.getResponseCode() + ": " + response.getContentText()); } // The response is raw CSV text, not JSON. const data = Utilities.parseCsv(response.getContentText()); const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getSheetByName("Metabase") || ss.insertSheet("Metabase"); sheet.clearContents(); if (data.length) { sheet.getRange(1, 1, data.length, data[0].length).setValues(data); }}For ad-hoc SQL rather than a saved question, post to /api/dataset with a query naming the database ID and your native SQL. In most cases keeping the SQL in a Metabase question is better practice, since it stays reviewable and reusable rather than living inside a spreadsheet.
Limits: Apps Script's six-minute cap applies, and slow questions can exceed it before returning. Metabase also applies a download row ceiling, so aggregate in the question rather than exporting very large raw result sets.
The Brooked method
Brooked authenticates with an API key so scheduled reports do not break when a session expires, lists your saved questions to pick from, and supports questions with parameters so one question can serve several filtered imports. Results land as typed columns rather than raw CSV text.
Step 1: Install Brooked
Install Brooked from the Google Workspace Marketplace. Free tier includes 100 imports per month, no credit card required.
Step 2: Connect Metabase
Choose Metabase in Brooked, enter your instance URL and an API key. Brooked lists the collections and questions that key can reach; pick one and import.
Step 3: Schedule the refresh
Set the import to re-run hourly, daily or weekly so the sheet reflects the question's current results without anyone opening Metabase.
Method comparison
| Method | Cost | Setup | Refresh | Two-way | Best for |
|---|---|---|---|---|---|
| Manual CSV download | Free | 1 min | Manual | No | A one-off result set |
| Public link + IMPORTDATA | Free | 3 min | On sheet recalculation | No | Non-sensitive data, no code at all |
| Apps Script + Metabase API | Free | 20 to 30 min | Time-driven trigger | No | Private data and parameterised questions |
| Brooked | Free tier · Pro $29/user/mo | ~3 min | Scheduled (hourly, daily, weekly) | No | Scheduled reporting from saved questions |
Common use cases
- Board packs: pull the metrics questions leadership already trusts into a formatted sheet
- Blended reporting: join a Metabase result against data that only lives in spreadsheets
- Snapshots: schedule imports to keep history for questions that only show current state
- Distribution: give stakeholders numbers without provisioning Metabase seats
- Alerting: run threshold checks in Sheets against a scheduled Metabase import
Troubleshooting common issues
Related Articles
- Connect Google Search Console to Google Sheets
- How to Connect Looker to Google Sheets
- GA4 Custom Dimensions in Google Sheets: The customEvent API Format Nobody Documents
Frequently asked questions
Can I pull a Metabase question into Sheets without writing code?
Yes, if the data is not sensitive. Enable public sharing in Metabase's admin settings, then share the question as a public link and request its CSV form. That URL can go straight into an IMPORTDATA formula in Sheets and refreshes when the sheet recalculates. The important caveat is that a public link is genuinely public: anyone with the URL sees the results, with no login.
Should I use an API key or a session token?
An API key, on any Metabase version that supports them. Session tokens obtained from /api/session expire, by default after two weeks, so a script built on them works fine and then silently starts failing. API keys are created in admin settings and sent in an x-api-key header, and they do not expire on a timer.
How do I export a saved question's results?
Call /api/card/{id}/query/csv, and note it must be a POST rather than a GET, which is a common source of 404s. The response is raw CSV text rather than JSON, so parse it accordingly. The question ID is the number in the question's URL.
Can I run ad-hoc SQL rather than a saved question?
Yes, by posting to /api/dataset with a query object naming the database ID and either native SQL or an MBQL query. Saved questions are usually the better pattern for reporting, because the SQL lives in Metabase where it can be reviewed and reused rather than buried inside a spreadsheet script.
Why does my export have fewer rows than Metabase shows?
Metabase applies different limits to on-screen display and to downloads, with the display limit far lower. Exporting through the CSV endpoint gets the larger download limit rather than the display cap, so if a browser view looked truncated the export usually is not. Very large result sets do still hit a download ceiling, in which case aggregate in SQL rather than exporting raw rows.
Can I write back to Metabase from Sheets?
No, and that is by design. Metabase is a query layer over your databases, not a system of record, so there is nothing meaningful to write back to. If you need to push edits somewhere, connect to the underlying database directly instead.
Connect Metabase to Google Sheets today.
Free tier: 100 imports per month, no credit card required. Or see the full Metabase integration details.
Install Brooked free →

