Web.Contents
Accessing DataDownloads content from a URL as binary.
Syntax
Web.Contents(url as text, optional options as nullable record) as binaryParameters
| Name | Type | Required | Description |
|---|---|---|---|
url | text | Yes | The URL to download content from. |
options | record | No | An options record controlling headers, query parameters, request method, and more. |
Return Value
binary — The binary content downloaded from the URL.
Remarks
Web.Contents fetches content from a URL and returns it as a binary value. You then pipe the result to a parser such as Json.Document, Csv.Document, or Xml.Document to convert it into a usable M value.
Options record fields
The optional options record supports the following fields:
| Field | Type | Description |
|---|---|---|
Query | record | Append query-string parameters to the URL. e.g., [Query = [page = "1", size = "50"]] |
Headers | record | HTTP headers to include. e.g., [Headers = [Authorization = "Bearer token123"]] |
Content | binary | Supplying this field changes the request from GET to POST. The value becomes the POST body. |
RelativePath | text | A path segment appended to the base URL. Useful for dynamic endpoint construction. |
Timeout | duration | Override the default request timeout. e.g., #duration(0, 0, 30, 0) for 30 seconds. |
ManualStatusHandling | list | A list of HTTP status codes (e.g., {404, 500}) that should not raise an error, allowing you to handle them in M code. |
IsRetry | logical | Set to true to ignore any cached response and re-fetch from the server. |
ExcludedFromCacheKey | list | Header names that should not be included in the cache key (useful for volatile auth headers). |
ManualCredentials | logical | Set to true to bypass Power Query's credential prompt when you handle authentication yourself via Headers. |
ApiKeyName | text | Name of the API key parameter when using the Web API credential type. |
Important notes
Web.Contentsbreaks query folding — any transformations applied after it run entirely in the M engine.- In the Power BI Service, the base URL must be a static string literal for data source credentials to bind correctly. Use
RelativePathandQueryfor dynamic parts. Power Query automatically inserts a/between the base URL andRelativePath— don't include a leading slash yourself. - Adding any
Contentfield — even an empty binary — converts the request from GET to POST. UseUri.BuildQueryStringto build form-encoded POST bodies from a record instead of manually concatenating strings. - Queries that combine a POST request (e.g. fetching an OAuth token) and a GET request in the same query may fail to refresh in the Power BI Service due to dynamic data source detection. Consider using Dataflows, a Data Gateway with a custom connector, or splitting the token fetch into a separate query.
- For paginated APIs, combine
Web.ContentswithList.Generateor a recursive function to fetch multiple pages.
Typical usage patterns
GET request with query parameters:
let
Source = Web.Contents("https://api.example.com/data", [
Query = [category = "electronics", page = "1"],
Headers = [Accept = "application/json"]
]),
Parsed = Json.Document(Source)
in
ParsedPOST request with a JSON body:
let
Body = Json.FromValue([name = "Test", value = 42]),
Source = Web.Contents("https://api.example.com/items", [
Content = Body,
Headers = [#"Content-Type" = "application/json"]
]),
Parsed = Json.Document(Source)
in
ParsedHandling specific HTTP status codes manually:
let
Response = Web.Contents("https://api.example.com/item/999", [
ManualStatusHandling = {404}
]),
Status = Value.Metadata(Response)[Response.Status],
Result = if Status = 404 then "Not Found" else Json.Document(Response)
in
ResultExamples
Example 1: Fetch JSON from a REST API
let
Response = Web.Contents("https://pqm.guide/api/tables/Products"),
Parsed = Json.Document(Response),
AsTable = Table.FromRecords(Parsed[rows])
in
AsTableApplied Steps
The final output — a five-row table built from the rows field of the parsed JSON using Table.FromRecords.
ProductID | ProductName | Category | Price | InStock | |
|---|---|---|---|---|---|
| 1 | 1 | Widget A | Widgets | 25 | TRUE |
| 2 | 2 | Gadget B | Gadgets | 50 | TRUE |
| 3 | 3 | Widget C | Widgets | 15 | FALSE |
| 4 | 4 | Gadget D | Gadgets | 75 | TRUE |
| 5 | 5 | Thingamajig E | Misc | 120 | FALSE |
Example 2: Use RelativePath for Power BI Service compatibility
let
TableName = "Sales",
Response = Web.Contents("https://pqm.guide", [
RelativePath = "api/tables/" & TableName
]),
Parsed = Json.Document(Response),
AsTable = Table.FromRecords(Parsed[rows])
in
AsTableOutput
OrderID | CustomerName | Product | Category | UnitPrice | Quantity | OrderDate | Region | |
|---|---|---|---|---|---|---|---|---|
| 1 | 1 | Alice | Widget A | Widgets | 25 | 4 | 1/15/2024 | East |
| 2 | 2 | Bob | Gadget B | Gadgets | 50 | 2 | 1/18/2024 | West |
| 3 | 3 | Charlie | Widget C | Widgets | 15 | 10 | 2/1/2024 | East |
| 4 | 4 | Alice | Gadget D | Gadgets | 75 | 1 | 2/10/2024 | North |
| 5 | 5 | Diana | Widget A | Widgets | 25 | 6 | 3/5/2024 | West |
| 6 | 6 | Bob | Thingamajig E | Misc | 120 | 1 | 3/12/2024 | East |
| 7 | 7 | Charlie | Gadget B | Gadgets | 50 | 3 | 4/1/2024 | West |
| 8 | 8 | Diana | Widget C | Widgets | 15 | 8 | 4/15/2024 | North |
Example 3: Handle a 404 with ManualStatusHandling
let
Response = Web.Contents("https://pqm.guide/api/tables/NonExistent", [
ManualStatusHandling = {404}
]),
Status = Value.Metadata(Response)[Response.Status],
Result =
if Status = 404 then #table({"Status", "Message"}, {{404, "Table not found"}})
else Table.FromRecords(Json.Document(Response)[rows])
in
ResultApplied Steps
The final output — since the status is 404, returns a one-row table with the status code and a user-friendly message instead of failing.
Status | Message | |
|---|---|---|
| 1 | 404 | Table not found |
Compatibility
✓ Power BI Desktop✓ Power BI Service✓ Excel Desktop✓ Excel Online✓ Dataflows✓ Fabric Notebooks