Opens in a new tab

Posted

on
September 19, 2026

Bricks Builder API Query Loop: Display Live API Data Without a Plugin

Table of Contents

What you will build

The finished page shows a 7-day forecast and recent weather alerts for one of six cities, and switches city when a visitor clicks a link. A working copy is at /weather-api/ on this site.

The page uses two query loops:

  • Forecast loop: 14 cards (day and night periods) from /gridpoints/{office}/{x},{y}/forecast.
  • Alerts loop: up to 6 rows from /alerts?area={state}&limit=6.

You need:

  • Bricks 2.1 or later. The API query type and the tag arrived in 2.1.
  • An API that answers a GET request with JSON containing a list. api.weather.gov needs no key or signup.
  • A way to look at raw JSON: a browser tab, or curl in a terminal.

How the pieces fit

Every city switch is a full page load. Bricks builds the API request on the server while it renders the page, so the visitor’s choice has to arrive in the URL.

  1. The visitor clicks a city link.
  2. The page reloads with ?office=LOT&x=76&y=73.
  3. Bricks resolves the dynamic tags inside the API URL.
  4. If a response is cached for that exact URL, Bricks uses it. Otherwise it sends a GET request to api.weather.gov.
  5. Bricks reads the response path to find the list.
  6. The loop element renders once per item.
  7. tags print each item’s fields.

The only moving part the visitor controls is the query string in step two. Everything after it is ordinary server-side rendering.

Step 1: Inspect the API before you open Bricks

Most failed API loops come from guessing the response shape, so look at the raw JSON first. Open the endpoint in a browser tab or run curl -s "https://api.weather.gov/gridpoints/SGX/57,14/forecast".

Answer four questions before you build:

  1. Where is the list? Find the array you want to repeat and write down the path to it. In the forecast response it is properties.periods. In the alerts response it is features.
  2. What does one item look like? Note the field names you will print, including nested ones. A forecast period has name, temperature, shortForecast, icon, and a nested probabilityOfPrecipitation.value.
  3. Can the endpoint limit its own results? Bricks has no “max items” setting for API loops. /alerts/active ignored limit and returned 424 alerts. /alerts?area=CA&limit=6 returned 6. Pick the endpoint that bounds itself.
  4. Which parts of the URL should change on a click? Those become URL parameters in Step 4.

api.weather.gov has one extra step. Forecasts are addressed by forecast office and grid cell, not by city name. Look each city up once at https://api.weather.gov/points/{lat},{lon} and copy gridId, gridX and gridY.

CityStateOfficeGrid xGrid y
San DiegoCASGX5714
New YorkNYOKX3342
ChicagoILLOT7673
MiamiFLMFL11050
SeattleWASEW12568
DenverCOBOU6362

Step 2: Build the loop

The element that carries the query loop is the repeating item, so the grid has to be its parent. Build two elements before touching any API setting.

Div  .wx-grid    display: grid, 4 columns     <- no loop here
  Div  .wx-card  Use Query Loop: on           <- repeats once per forecast period
    Text, Image, Text ...                     <- the card's content

If you put the grid CSS on the loop element, you get 14 separate one-card grids stacked in a column.

Then configure the loop element:

  1. Select the card Div and turn on Use Query Loop.
  2. Set Type to API and click API Settings. A popup opens with request settings on the left and a response preview on the right.
  3. Fill in the request settings from the table below. Start with a hard-coded URL. Add the dynamic parts in Step 4, once the basic loop works.
  4. Click Fetch in the preview panel. You should see the raw JSON.
  5. Enter the Response path and fetch again. The preview should now show a plain list of items. If it still shows the whole response, the path is wrong (see Pitfalls).
  6. Save the page and view it on the front end. You should see one empty card per item.
SettingForecast loopAlerts loop
URLhttps://api.weather.gov/gridpoints/SGX/57,14/forecasthttps://api.weather.gov/alerts
HTTP MethodGETGET
URL Parametersnonearea = CA, limit = 6
HeadersAccept = application/geo+jsonAccept = application/geo+json
Response pathproperties.periodsfeatures
Cache Duration600 seconds300 seconds

Put query-string values in URL Parameters and not in the URL itself. Bricks drops a parameter whose value is empty, which Step 4 relies on.

The Accept header is optional for this API. Bricks already sends a User-Agent of BricksBuilder/<version>, which api.weather.gov requires.

Step 3: Print fields with the query_api tag

Inside the loop, one tag prints any field of the current item: . It works anywhere Bricks renders dynamic data, which includes text, links, image URLs and custom attributes.

You wantTagNotes
A top-level fieldPrints “Tonight”, “Sunday” and so on.
A nested fieldA pipe walks into nested objects. Dots do not work here.
An item in a nested arrayUse the numeric index as a key.
A default when the field is emptyWithout it, a null prints nothing and you get a bare “%”.
Text around a value°Mix tags and plain text freely in one field.

The picker (the lightning-bolt icon) inserts but does not list @key or @fallback. You type those yourself.

Three bindings beyond plain text are worth knowing:

  • Remote images. The API returns an icon URL per period. In the demo the Image element’s external URL is set to and its alt text to . Bricks does not import these files into the media library, so they load from api.weather.gov on every view.
  • Attributes that drive styling. Add a custom attribute to the loop element, such as data-severity = . Then style it in CSS with .wx-alert[data-severity="Extreme"] .wx-alert__severity { ... }. This gives you per-item styling without element conditions. The demo’s active city pill uses the same trick with .
  • Booleans. A true prints as 1 and a false prints as an empty string. The demo tints night cards with .wx-card[data-daytime=""].

To check a tag without saving the page, the Bricks MCP server has a bricks/preview-dynamic-tag ability. It renders an expression against a real post and reports whether the result is empty.

Step 4: Make the loop respond to a click

Replace the hard-coded parts of the request with default, then link to the same page with those parameters set. Bricks resolves dynamic data in the URL, URL parameters, headers, body and auth values before it sends the request.

  1. Swap the fixed values for tags. The forecast URL becomes:https://api.weather.gov/gridpoints/SGX/57,14/forecastIn the alerts loop, set the area URL parameter to CA.
  2. Always give a fallback when the value sits in the URL path. On the first visit there is no query string. Without fallbacks the request goes to /gridpoints//,/forecast and fails. With them, the page opens on San Diego.
  3. Add the links. Each city is a plain link back to the same page:/weather-api/?office=LOT&x=76&y=73&city=Chicago&state=ILEncode spaces as %20, for example city=San%20Diego.
  4. Reuse the parameters in the page copy. The demo heading is Weather in San Diego. Printing the resolved endpoint under each section heading is a cheap debugging aid.
  5. Test three URLs: no parameters, a full valid set, and a deliberately wrong set such as ?office=ZZZ. The last one tells you what visitors see when the API refuses (Step 5).

A parameter in URL Parameters with an empty value is dropped from the request. That makes optional filters easy: area = with no fallback means “filter by state when one is chosen, otherwise send no filter”. Check what the unfiltered request returns before relying on this. For alerts it is more than 400 rows.

A link is not the only trigger. Any form with method="get" that submits to the same page sets URL parameters too, which gives you search boxes and dropdowns. Do not name a field s, because WordPress treats ?s= as a site search and the page returns a 404.

URL parameters are also not the only source. Any dynamic tag works in the request:

Source of the valueTagGood for
The visitor’s click or formSwitchers, search, filters. Needs a page load.
A custom field on the current post, or an ACF or Meta Box tagA “City” post type where one template serves /city/miami/ with clean URLs and no user input.
The logged-in userPer-user dashboards.
Your own PHP functionCookies, geolocation, computed dates, whitelisting. The function name must be allowed through the bricks/code/echo_function_names filter.
The parent loop’s current item inside a nested API loopDetail per item. Not tested in the demo. It costs one request per parent item.

If your cities are a fixed list, the custom-field route is usually the better design. The values come from your database, so visitors cannot alter the request.

Step 5: Handle empty and failed responses

Set the loop’s no-results text, because it is the only thing visitors see when the API returns nothing or refuses the request. Bricks treats an HTTP error, a timeout and an empty list the same way: the loop has zero items.

  • No-results text. In the loop’s query settings, fill in the no-results text (stored as no_results_text; a no-results template is also supported). Tested on the demo with ?office=ZZZ&state=ZZ: both loops printed their message and the page still returned 200.
  • The message takes the loop’s place. Bricks renders it as one element inside the grid, so the layout holds.
  • Word it for both cases. You cannot tell “no alerts today” from “the API is down” in the markup. “No recent alerts for this state.” reads correctly either way.
  • Hide a whole section when its loop is empty. Add an element condition to the section wrapper using 0 greater than 0, where the ID is the loop element’s 6-character Bricks ID.
  • Errors are visible in the builder. The API Settings preview shows the status and error message after you click Fetch. On the front end nothing is logged by default.

Styling it with ACSS

The demo on this site is styled with Automatic.css variables inside BEM classes (.wx-card, .wx-alert__severity and so on), with no hex values anywhere. Three things were worth writing down:

  • Variables only means the color scheme toggle is free. This site is dark by default and its alternate scheme flips every shade. A card with background: var(--neutral-dark) and color: var(--neutral-light) inverts correctly with no extra CSS. A single hard-coded #ffffff would have broken it. The featured “now” card simply swaps the two ends of the scale: var(--neutral-ultra-light) background with var(--neutral-ultra-dark) text.
  • Use the framework’s layout variables too. The forecast grid is grid-template-columns: var(--grid-4), stepping down to var(--grid-3), var(--grid-2) and var(--grid-1) at the Bricks breakpoints. Gaps and padding come from --space-*, borders from --border-size and --border-color-light.
  • Give optional variables a fallback. Status colors are off by default in ACSS. The severity chips are written as var(--danger, var(--neutral-ultra-light)) and var(--warning-dark, var(--action-ultra-dark)), so they render with the brand palette when status colors are disabled and pick up the real ones the moment they are enabled.

Pitfalls

Most of these fail silently, so check the symptom column first. Each cause was confirmed in the Bricks 2.4 source (includes/integrations/query/query-api.php) or on the demo page.

SymptomCauseFix
The loop renders a few empty items instead of your dataThe response path is wrong. When a path is not found, Bricks falls back to the whole response and loops over its top-level keys.Fetch in the API Settings preview and confirm it shows a plain list. Paths use dots (properties.periods).
Same, but the path is correctThe path lands on a single object, not a list. The response must resolve to an array.Choose an endpoint that returns a list. /stations/KSAN/observations?limit=1 returns a one-item list; /observations/latest returns an object.
Cards stack in one columnThe grid CSS is on the loop element.Move the grid to a non-looping parent (Step 2).
Hundreds of items renderAPI loops have no item limit.Use an endpoint or parameter that limits results. Hiding extras with CSS still renders them.
A nested value prints nothingThe key uses a dot.Use a pipe in tags: @key:'properties|event'. Dots are only for the response path.
The first visit is empty, clicking a city worksA URL-path tag has no fallback, so the default request is malformed.Add @fallback to every tag in the URL path.
The API is back up but the page still shows no resultsError responses are cached too, for the full Cache Duration.Keep the cache short for flaky APIs. Clicking Fetch in the builder clears that element’s cache.
A parameter with the value 0 never reaches the APIBricks drops parameters whose value is empty, and 0 counts as empty.Put that parameter in the URL itself.
Pagination shows one pageThe total-pages path lacks its source prefix.Write body.pagination.pages or header.x-total-pages.
Times show the wrong hour outputs in the site’s timezone, not the forecast location’s.Print API text fields that are already local, such as name, or skip clock times.
Code-style text renders in a serif fontBricks wraps the font-family value in quotes, so a comma-separated stack becomes one invalid name.Put the first family in font-family and the rest in the fallback field.
A featured first card breaks the grid on mobileA :first-child rule with grid-column: span 3 still applies when the grid drops to one column, which creates phantom columns.Reset the span at each breakpoint, and give the responsive rules higher specificity than the base rule so their order in the stylesheet does not matter.
The builder shows fresh data but the front end is staleThe front end serves the cached response. The builder’s Fetch always bypasses it.Expected. Lower the Cache Duration while building.
The page hangs for many secondsThe request runs during page render with a 30-second timeout and no retry.Cache generously, and put a page cache in front for high-traffic pages.

Hard limits

API loops cannot update in place, so every visitor choice costs a page load. The Bricks documentation lists the limits below, and none of them has a setting that turns it off.

LimitWhat it means in practiceWhat to do instead
No Query FiltersFilter elements cannot target an API loop.Links or a GET form that set URL parameters (Step 4).
No live searchNo results-as-you-type.A search form that reloads the page with ?q=term.
No Bricks componentsA component placed inside an API loop does not render.Build the card from plain elements and share styling through global classes.
JSON onlyXML, CSV and HTML endpoints do not work.Put a small proxy in front that converts to JSON.
The response must be a listA single object cannot be looped.Find a list endpoint, even one that returns a single item.
No media importRemote images stay remote.Accept hot-linking, or copy the images yourself.
No retry or backoffA rate-limited or failing request is not retried.Cache longer and keep the number of distinct requests small.
GET and POST onlyNo PUT, PATCH or DELETE.This is a read tool. Use a form action or custom code for writes.
No item limit or offset in the loopYou cannot show “the first 5” of a longer response.Limit at the API with a parameter such as limit or per_page.

One more limit is structural and not in the docs. The request runs on your server, not in the visitor’s browser. The API sees your server’s IP address and the BricksBuilder user agent. Location-aware APIs will locate your server, not your visitor.

Before you ship

The main production risk is that visitors control part of an outbound request from your server. The demo accepts that for a fixed, harmless host. A public site should tighten it.

  • Restrict what a parameter can be. ?office= is inserted into the request path as typed. The host stays fixed, so a visitor cannot redirect the request elsewhere, but they can reach any path on that API. For a fixed list, use custom fields on posts, or an function that maps a short key to allowed values.
  • Expect cache flooding. Every distinct resolved URL gets its own cache entry, stored as a transient in wp_options. A script that requests ?x=1, ?x=2, ?x=3 forces one outbound API call and one database row each time. Whitelisting fixes this too. A page cache alone does not, because the query string varies.
  • Do not trust parameters in copy. Bricks escapes output. A <script> in ?city= rendered as harmless text on the demo. Anyone can still craft a link that makes your heading say “Weather in Anything”, so keep user-supplied text out of titles and meta tags.
  • Set the cache to match the data. The default is 300 seconds. The demo uses 600 for forecasts, which change hourly at most. Remember that errors are cached for the same duration.
  • Know the API’s rules. api.weather.gov asks for an identifying User-Agent and rate-limits abusive clients. Most APIs publish a requests-per-minute figure. Divide it by your number of distinct URLs to choose a cache duration.
  • Keep secrets out of the database. For keyed APIs, turn on “Use PHP Constant” and define BRX_QUERY_API_KEY_<ELEMENT_ID> in wp-config.php (also BRX_QUERY_BEARER_TOKEN_, BRX_QUERY_BASIC_AUTH_USERNAME_ and BRX_QUERY_BASIC_AUTH_PASSWORD_). The ID is the loop element’s ID in uppercase, so duplicating or rebuilding the element breaks auth until you add a constant for the new ID.
  • Note that TLS certificates are not verified. The request is sent with sslverify off by default. The bricks/query_api/request_args filter lets you turn it on, and also change the 30-second timeout.
  • Plan for the API being down. The page still loads, the loop shows its no-results text, and that result is cached. Decide whether the section should hide itself instead (Step 5).
  • Check the terms of use. Hot-linked images and redistributed data are governed by the API’s terms. National Weather Service data is public domain, which is why it suits a demo.

Appendix: the demo loops as element JSON

These are the settings saved on the two loop elements, for anyone building through the Bricks MCP server or reading element data directly. In the builder they correspond to the fields in Step 2.

Forecast loop (the card Div):

{
  "hasLoop": true,
  "query": {
    "objectType": "api",
    "api_url": "https://api.weather.gov/gridpoints/SGX/57,14/forecast",
    "api_method": "GET",
    "api_headers": [{ "id": "wxhda1", "key": "Accept", "value": "application/geo+json" }],
    "response_path": "properties.periods",
    "cache_time": 600,
    "no_results_text": "The National Weather Service did not return a forecast for this location. Try another city."
  }
}

Alerts loop (the row Div):

{
  "hasLoop": true,
  "query": {
    "objectType": "api",
    "api_url": "https://api.weather.gov/alerts",
    "api_method": "GET",
    "api_params": [
      { "id": "wxpar1", "key": "area", "value": "CA" },
      { "id": "wxpar2", "key": "limit", "value": "6" }
    ],
    "api_headers": [{ "id": "wxhdb1", "key": "Accept", "value": "application/geo+json" }],
    "response_path": "features",
    "cache_time": 300,
    "no_results_text": "No recent alerts for this state."
  }
}

The icon image element:

{
  "image": { "url": "", "external": "" },
  "altText": ""
}

Through MCP, the whole page went in as two calls: one atomic batch that created the 35 global classes, and one that wrote the element tree with hasLoop and query already on the two loop elements. The HTML importer cannot express a loop on its own, so if you start from HTML and CSS, add the loop settings afterwards with bricks/batch-update-elements.

Sources

  • Query Data from APIs, Bricks Academy: builder labels, dynamic-data support, authentication constants and the stated limits. Opened 19 September 2026.
  • Dynamic Data, Bricks Academy: tag reference.
  • api.weather.gov: the points, gridpoints forecast, alerts and observations endpoints were called directly for every response shape and grid value quoted here.
  • Bricks 2.4 theme source: includes/integrations/query/query-api.php (request, caching, parameters, response path), includes/query.php (loop handling, no-results) and includes/integrations/dynamic-data/ (tag arguments). Behaviour described as confirmed comes from these files or from the live demo page.

Brendan O'Connell

Brendan is a longtime WordPress user and has built and managed hundreds of websites over the last decade.

Recent Posts

You're still here? Wow, dedication. Here are some of my latest ramblings.