Connect Power BI
What you need before you start
- Power BI Desktop, free from Microsoft, installed on a Windows computer. (Power BI Desktop does not run on Mac or Chromebook — if that's what you have, export to Excel from Responses instead, and open that.)
- Editor or Admin access to the form whose responses you want.
- About five minutes, and this page open on a second screen or tab while you work.
The quicker way: connect via OData (no script to paste)
Every form now also exposes an OData feed— the same standard Power BI's own Get Datamenu already knows how to read, with no script involved at all. Try this first; keep the Power Query script further down as a fallback if your version of Power BI Desktop doesn't offer the credential type it needs.
- 1Get an API key exactly as in Step 1 below (skip ahead, then come back here).
- 2In Power BI Desktop: Get Data → search for OData Feed.
- 3Paste this as the URL, with your deployment address, organization ID and form ID filled in:OData feed URL
https://YOUR-DEPLOYMENT-HOST/v1/organizations/YOUR-ORGANIZATION-ID/forms/YOUR-FORM-ID/odata - 4When asked how to authenticate, choose the credential type for a Web API key / custom header (worded slightly differently across Power BI versions — look for anything that lets you name a header). Set the header name to X-API-Key and the value to your API key.
- 5The Navigator shows one table, Submissions, already laid out with one column per question, named from its label — load it.
Step 1 — Get an API key
An API keyis what proves to the system that Power BI is allowed to read your organization's data. Create one scoped to exactly this — nothing more:
- 1Go to Integrations and click Create API key.
- 2Name it something you'll recognise later — “Power BI” is enough.
- 3Tick exactly two scopes: forms:read and submissions:read. Nothing else — this key should not be able to change anything.
- 4Copy the key the moment it's shown. It starts with fbk_and is shown exactly once — if you lose it before pasting it into the script below, you'll need to create a new one.
Step 2 — Find three IDs
The script below needs three plain pieces of text, each easy to find:
| What | Where to find it |
|---|---|
| Your deployment address | The web address you normally use to open this app, e.g. https://forms.yourorg.org — the script needs /v1 added to the end of it. |
| Your organization ID | Settings → Organization. A new field there, Organization ID, has a copy button next to it. |
| This form’s ID | Open the form and look at your browser’s address bar — it’s the long id that appears right after "/forms/". |
Step 3 — Paste the script into Power BI
- 1Open Power BI Desktop. On the Home tab, click Get Data → Blank Query.
- 2On the Home tab of the query editor that opens, click Advanced Editor.
- 3Select everything already there and delete it.
- 4Copy the whole script below and paste it in.
- 5Fill in the four values near the top — your deployment address, organization ID, form ID, and the API key from Step 1 — in place of the placeholder text between the quote marks.
- 6Click Done. Power BI loads every response to the form as one table, with real question wording as the column names.
let
// ═══ FILL THESE IN ═══════════════════════════════════════════════════
BaseUrl = "https://YOUR-DEPLOYMENT-HOST/v1", // no trailing slash
OrgId = "YOUR-ORGANIZATION-ID",
FormId = "YOUR-FORM-ID",
ApiKey = "fbk_YOUR-API-KEY",
// ═════════════════════════════════════════════════════════════════════
// The API limits a page to 100 rows regardless of what's asked for.
PageSize = 100,
RequestHeaders = [#"X-API-Key" = ApiKey],
// Every response here is wrapped as { data: ..., meta: ... } — [data]
// unwraps that once so every call below reads the real payload directly.
GetJson = (relativePath as text, optional query as nullable record) as record =>
Json.Document(
Web.Contents(
BaseUrl,
[
RelativePath = relativePath,
Query = if query = null then [] else query,
Headers = RequestHeaders
]
)
)[data],
FormPath = "organizations/" & OrgId & "/forms/" & FormId,
// 1) The question dictionary: id → the label you gave the question.
FormResponse = GetJson(FormPath, null),
Questions = List.Select(FormResponse[questionsJson], each _[type] <> "SECTION_HEADER"),
QuestionIds = List.Transform(Questions, each _[id]),
QuestionLabels = List.Transform(Questions, each _[label]),
// 2) Every page of submissions, followed until there's nothing left.
FirstPage = GetJson(FormPath & "/submissions", [page = "1", limit = Text.From(PageSize)]),
TotalPages = FirstPage[pagination][totalPages],
PageNumbers = if TotalPages < 1 then {} else {1 .. TotalPages},
Pages = List.Transform(
PageNumbers,
each GetJson(FormPath & "/submissions", [page = Text.From(_), limit = Text.From(PageSize)])[submissions]
),
AllSubmissions = List.Combine(Pages),
// 3) One row per submission, one column per question — named from the
// dictionary above, not the internal id "answers" is keyed by.
Rows = List.Transform(
AllSubmissions,
(s) =>
let
Meta = [
SubmittedAt = s[submittedAt],
Status = s[status],
RespondentEmail = try s[respondent][email] otherwise null,
CompletionTimeMs = s[completionTimeMs]
],
AnswerValues = List.Transform(QuestionIds, (qid) => try Record.Field(s[answers], qid) otherwise null),
Answers = Record.FromList(AnswerValues, QuestionLabels)
in
Meta & Answers
)
in
Table.FromRecords(Rows)Getting the latest responses later
Click Refresh on the Home tab any time you want the table brought up to date — this always works in the free Desktop app, with no limit. A scheduled refresh that happens automatically overnight, without you opening Desktop yourself, is a feature of the paid Power BI service (Power BI Pro) rather than the free app — not required to use this, just not included in it.
Treat the file like it holds a password — because it does
If something doesn't look right
| What you see | What it means |
|---|---|
| Power BI can’t reach the address at all | Double-check the deployment address — it must end in "/v1" and have no trailing slash after that. |
| An error mentioning 403 or "not valid for this organization" | The organization ID doesn’t match the API key. Re-copy both from Settings and the key you created, rather than typing either by hand. |
| An error about a duplicate column name | Two questions on this form share the exact same wording. Rename one of them slightly in the builder, then refresh. |
| The table loads but a question is missing | Section headers are dropped on purpose — they’re layout only and never hold an answer. Any other missing question is worth reporting. |
What this doesn't do yet
- One form at a time — there isn't yet a single connector spanning every form in the organization at once.
- Every refresh re-reads the form's entire response history from the start, rather than only what changed since last time. Not a problem at the sizes this app expects today.
- No ready-made charts or dashboard — this gets you a clean table; building the report itself is still up to you in Power BI.
Frequently asked questions
- Do I need a paid Power BI licence to use form responses?
- No. The guide uses the free Power BI Desktop app only, with no Azure sign-in and no paid licence.
- How do I get responses into Power BI?
- Two ways: a built-in OData feed, or a Power Query script.