What It Does (And Doesn’t Do)
The Beaver Builder API Loop plugin extends the native Loop module to work seamlessly with APIs to display content. Instead of being limited to WordPress data in the Loop module sources, you can now pull content from any REST API, display it in beautiful layouts, and even add pagination, all without writing a single line of code.
This plugin is designed for displaying data in grid and list layouts using Beaver Builder’s Loop module. If you need individual pages for each item (like single post pages), you’ll need a separate integration that imports the API data as custom post types. This plugin focuses specifically on outputting data in grids where you use Beaver Builder to design how each item appears.
If you need to display more detailed information than fits in a grid but don’t have the ability to ingest the API content into a custom post type to create single pages, consider using modals or popups that can show additional item details when users click on grid items.
Real-World Use Cases
- Podcast Episodes: Show recent episodes from RSS/podcast APIs with title, duration, and description that links to the episode.
- News and Blog Aggregation: Pull content from a headless CMS or news API.
- Event Listings: Show events from sites like Eventbrite or a client’s own custom event API.
- Team Member Directories: Pull staff data from HR systems or external databases.
- Portfolio Showcases: Display work from Behance, Dribbble, or custom portfolio APIs.
Key Features
Multiple Authentication Methods
Connect to any REST API endpoint that returns JSON data with support for multiple authentication methods:
- Bearer tokens
- API key headers
- Basic authentication
- Custom headers
Flexible Data Handling
Navigate complex JSON structures with ease using dot notation to access nested data like data.items or response.results. The built-in preview lets you see your API response and automatically detects available fields. Sort your content by any field in ascending or descending order.
Ajax Pagination
Choose from three ajax-powered pagination styles to match your design:
| Style | Description |
|---|---|
| Numbers | Traditional page numbers (1, 2, 3…) with Prev/Next Buttons |
| Previous / Next | Previous and Next buttons |
| Load More | Ajax button that appends new content |
The plugin adapts to different API pagination methods including page-based (?page=2&per_page=10), offset-based (?offset=20&limit=10), and custom parameter names.
Performance & Caching
Built-in WordPress transient caching is crucial for performance and reliability. Caching prevents your site from making repeated API calls on every page load, reduces bandwidth costs, and ensures your content loads quickly even if the external API experiences slowdowns. The cached data also protects against API rate limits and temporary outages.
- Configurable cache duration
- Per-request caching with unique keys
- Smart pre-fetching for pagination
- Builder-only script loading (no admin overhead)
Beaver Themer Integration
Access any API field through the familiar Themer connections interface you already know. Find the plugin’s connections under the “API Query – Loop Module” group, then select the “API Field (key)” connection. A built-in key picker automatically populates with fields from your API preview, making it easy to select the exact data you need. The integration works seamlessly with Themer’s entire layout system, so you can use API data anywhere you’d normally use WordPress content.
Dynamic Formatting
External APIs return data in their own formats, which may not match how you want to display it on your site. A date might come as an ISO timestamp when you need “September 7, 2025”, or text might be lowercase when you need it capitalized. The plugin includes built-in formatters to transform data without custom code.
Formatting
The format attribute lets you transform text and dates using built-in formatters:
uppercase– Convert text to uppercaselowercase– Convert text to lowercasetitlecase– Uppercase the first letter of each word and preserve the remaining characters’ casing (which keeps acronym capitalization likeAPIintact)slugify– Create URL-friendly slugs (non-alphanumerics become hyphens)date:{pattern}– Format dates using WordPress/PHP date patterns (e.g.,date:M j, Y,date:Y-m-d)
Date handling accepts Unix timestamps (seconds or milliseconds) and any string parsable by strtotime().
Truncation
The length attribute lets you limit text to a maximum number of characters. When text is truncated, the more attribute controls what’s appended (defaults to ...).
length– Maximum characters (0 = no limit)more– Text appended when truncated (default:...)
Array Handling
When working with array fields from your API, you have several options for displaying the data. You can access individual items by index using field{0} or field.0. Using a bare array key (like images) outputs the array as JSON text by default.
If you choose to output the array itself, the Themer connection provides multiple Array Options for formatting it:
- Default: Raw array/JSON output
- Use Separator: Joins array items with your specified separator (e.g., “, ” or ” | “)
- Ordered List: Wraps items in an
<ol> - Unordered List: Wraps items in a
<ul> - Wrap in span: Each item wrapped in a
<span>, all items wrapped in a<div>
Additional controls let you set an Array Separator (when using the separator option) and an Array Limit to cap the number of items displayed.
Examples
[wpbb post:api key='title' format='uppercase']
→ 'BLOOD MOON': TOTAL LUNAR ECLIPSE WAS VISIBLE ACROSS ASIA
[wpbb post:api key='publishedAt' format='date:M j, Y']
→ Sep 7, 2025
[wpbb post:api key='category' format='slugify']
→ Space & Astronomy becomes space-astronomy
[wpbb post:api key='tags' array_output='separator' array_separator=', ' array_limit='3']
→ Astronomy, Lunar Eclipse, Blood Moon
[wpbb post:api key='description' length='150']
→ The 'blood moon,' which reached its peak at 9:12 P.M., was visible across the Middle East, Asia and beyond; the next total lunar eclipse won't...
Need custom formatting beyond the built-in options? The plugin provides the bb_api_loop_value filter that lets you add your own formatting logic. This means you can create formatters for currency, phone numbers, custom date formats, or any other data transformation you need. The filter receives the output value, raw value, and settings, giving you complete control over how data appears in your layouts.
Handling Missing Data with Fallbacks
APIs don’t always return consistent data. An article might be missing an image, an author field could be null, or a description might not exist. The plugin includes a fallback attribute that lets you specify what to display when a field is empty or missing.
For example, when connecting an Image module to urlToImage, you can set a fallback to a default placeholder image URL. If the API doesn’t return an image for that article, your layout won’t break – it’ll show your placeholder instead. This works for any field type: display “Author Unknown” when there’s no author, show “No description available” for missing descriptions, or use any default value that makes sense for your content.
[wpbb post:api key='urlToImage' fallback='https://yoursite.com/default-image.jpg']
This is crucial for maintaining professional, polished layouts even when working with unpredictable external data sources.
Sorting & Filtering
Control how your API data is ordered and filtered. Server-side sorting sends parameters to your API for globally correct ordering across all pages. Client-side sorting handles APIs that don’t support sorting parameters. Flexible filtering hooks let you add search boxes, category filters, date ranges, or any custom parameters your API supports, with automatic cache segmentation to ensure users always see the correct filtered results.
Filtering works through WordPress filter hooks that intercept your API requests before they’re sent. This lets you build search forms, faceted filters, or any custom filtering UI you need. Your filter code simply modifies the outbound request URL or headers, and the plugin handles the rest, including cache management and coordination with pagination and sorting. You can scope filters to specific Loop modules or API endpoints, giving you precise control over where each filter applies.
Getting Started
Setting up the plugin is straightforward and requires no coding knowledge. Start by adding a Loop module to your page as you normally would, then select “API” as your data source in the Loop settings. Configure your API by entering the URL and any required authentication details. Use the built-in preview to see your API response and verify the data structure. Finally, design your layout using Themer connections to display the data fields you need.
Example: News from NewsAPI.org
API Endpoint: https://newsapi.org/v2/top-headlines
Sample Response:
{
"status": "ok",
"totalResults": 247,
"articles": [
{
"title": "'Blood Moon': Total Lunar Eclipse Was Visible Across Asia",
"author": "Science Desk",
"publishedAt": "2025-09-07T14:22:00Z",
"description": "The 'blood moon,' which reached its peak at 9:12 P.M., was visible across the Middle East, Asia and beyond; the next total lunar eclipse won't occur until 2028. Astronomers reported clear viewing conditions across most of the region, with thousands gathering in public spaces to witness the celestial event.",
"urlToImage": "https://example.com/images/blood-moon.jpg",
"url": "https://example.com/articles/blood-moon-eclipse"
},
...
]
}Configuration
- Data Source: API
- API URL:
https://newsapi.com/v2/top-headlines - Data Path:
articles(to target the articles array) - Items Per Page:
6 - Total Count Source:
totalResults
Layout with Themer Connections
Add a Box module to your Loop as a wrapper for better layout control. This gives you flexbox/grid capabilities and allows you to add custom classes to style your cards. Inside the Box, add child modules and connect them to API fields:
- Photo Module → “API Field (key)” →
urlToImage - Heading Module → “API Field (key)” →
title(Link tourl) - Text Editor → “API Field (key)” →
publishedAt(date format:M j, Y) - Text Editor → “API Field (key)” →
description(max length: 150)
Understanding Data Paths
Many APIs don’t return data as a simple array. Instead, they wrap the actual content in nested objects. The Data Path setting tells the plugin exactly where to find your array of items within the JSON response.
Important: The Data Path you select becomes the “top level” for your loop. The plugin will loop through each direct child of your Data Path, and all the fields you access with Themer connections will be descendants of that Data Path location.
How to Use Data Paths
Consider this API response structure:
{
"status": "success",
"meta": {
"total": 150,
"page": 1
},
"data": {
"articles": [
{
"title": "First Article",
"content": "..."
},
{
"title": "Second Article",
"content": "..."
}
]
}
}To access the articles array, you’d set the Data Path to data.articles. The plugin uses dot notation to navigate through the nested structure:
data– If you just did data, you wouldn’t be able to access the “Articles”data.articles– Go one level deeper to access the articles array and loop through all “Articles”
For even deeper nesting:
{
"response": {
"results": {
"posts": [
{
"title": "Blog Post",
"author": {
"name": "John Smith",
"email": "[email protected]"
}
}
]
}
}
}Your Data Path would be response.results.posts to reach the posts array. Then you can loop through all “Posts.”
If your API returns a simple array at the root level, leave the Data Path field empty:
[
{"title": "Item 1"},
{"title": "Item 2"}
]Accessing Nested Fields
The same dot notation principle applies to Themer connection keys. Once you’ve configured the Data Path to locate your array of items, you use dot notation in your connection keys to access nested fields within each item:
title– Gets the title field from each itemauthor.name– Gets the name field from the author objectmeta.publishedDate– Gets the publishedDate from the meta object
Array Indexing
Access specific array elements within your items using curly braces:
images{0}– first imagetags{2}– third tag
Parameter Location
Sorting and pagination parameters share a common “Parameter Location” setting that determines where parameters are sent:
- Query (default): Parameters are appended to the URL
- Example:
https://api.example.com/data?orderby=date&order=DESC&page=1&per_page=10
- Example:
- Header: Parameters are sent as HTTP headers
- Useful for APIs that require parameters in headers instead of the URL
This setting only appears when either server-side sorting or pagination (not None) is enabled.
Pagination
Numbers Pagination
Perfect for traditional blog-style navigation. Automatically calculates total pages when total count is provided and supports custom page numbering (starting at 0 or 1).
Alternative Pagination Options
- Previous/Next: Simple navigation arrows for sequential browsing. Works the same as Numbers pagination but without the page numbers in between.
- Load More: Ideal for content feeds and catalogs. Appends new content without page reload, provides smooth UX with loading states, and automatically hides when no more content is available.
Total Count Configuration
Numbers pagination requires a “Total Count Source” (header name or JSON path) to calculate total pages and display page numbers accurately. For Previous/Next and Load More styles, providing a total count source is recommended but optional – these can work using smart detection where fewer items than the per-page size indicates the last page, while equal items triggers background prefetching of the next page to check for more content. However, specifying a Total Count Source provides the most reliable behavior for all pagination styles.
Sorting
The plugin provides flexible sorting options to control how your API data is ordered, with three distinct modes to match different API capabilities and use cases.
Sorting Modes
Default Order
Uses the order returned by your API without any modifications. This is ideal when your API already returns data in the desired sequence, or when you want to preserve the API’s native ordering logic.
Server-side Sorting (Recommended)
Sends sorting parameters directly to your API, allowing it to return pre-sorted data. This is the most powerful option because:
- Sorting happens across your entire dataset, not just the current page
- Works seamlessly with pagination to maintain correct order across all pages
- Reduces processing load on your WordPress site
- Provides the most accurate results for large datasets
When you enable server-side sorting, you configure:
- Order By: The field name your API expects (e.g.,
date,title,price) - Order Direction: Ascending (ASC) or Descending (DESC)
- API Order By Param: The parameter name your API uses (default:
orderby, common alternatives:sort,sort_by) - API Order Direction Param: The parameter name for direction (default:
order, common alternatives:direction,dir)
Client-side Sorting
Sorts data locally after it’s been fetched from the API. This is a fallback option for APIs that don’t support sorting parameters.
Important limitations:
- Only sorts items within the current page, not the entire dataset
- Pagination is automatically disabled when this mode is selected
- Use dot notation to access nested fields (e.g.,
meta.date,author.name)
Sorting with Pagination
- Server-side + Pagination: Full compatibility. Items are sorted globally across all pages.
- Client-side + Pagination: Not compatible. Pagination is automatically disabled because client-side sorting can only sort the current page, which would create inconsistent ordering across pages.
- Default Order + Pagination: Full compatibility. Items appear in whatever order the API returns them.
Configuration Example
For a news API where you want newest articles first:
- Set Sorting Mode to Server-side
- Set Order By to
publishedAt(or whatever field name your API uses) - Set Order Direction to DESC
- Set API Order By Param to match your API’s parameter (e.g.,
sort) - Set API Order Direction Param to match your API (e.g.,
order) - Set Parameter Location to Query (or Header if your API requires it)
The plugin will then request: https://api.example.com/articles?sort=publishedAt&order=DESC
Filtering / Facets
The Loop supports flexible filtering through custom parameters. You can add search, key/value filters, or any parameters your API supports without modifying the plugin core.
Filtering is implemented through WordPress filter hooks that intercept API requests before they’re sent. When a Loop module needs data, the plugin builds a request with your configured URL, authentication, pagination settings, etc. Just before sending that request, it runs through the filters where your custom code can add, modify, or remove parameters based on user input or page context.
This approach gives you complete control. Want to add a search box? Hook into the filter and append a ?q= parameter. Need category dropdowns? Add ?category= parameters. Building a complex faceted search? Add as many parameters as your API supports. Because filtering happens at the request level, it works seamlessly with pagination, caching, and sorting.
Looking for a turnkey faceting solution? Check out the BB API Loop Facets snippet for ready-to-use search boxes, dropdown filters, and reset buttons with built-in AJAX support.
Available Filter Hooks
Main request filters:
bb_api_loop_prepare_request– Modify the complete request payload before it’s sentbb_api_loop_request_url– Final URL filter before the requestbb_api_loop_request_args– Final request arguments filterbb_api_loop_cache_key_suffix– Append cache key suffixes to segment caches by dynamic params
Preview-specific filters:
bb_api_loop_preview_prepare_request– Same as above, but for Settings UI previewbb_api_loop_preview_request_url– Preview URL filterbb_api_loop_preview_request_args– Preview request args filter
Data and display filters:
bb_api_loop_value– Custom formatting for field values (receives$output,$raw_value,$settings)bb_api_loop_pagination_defaults– Customize pagination button text and settings
Request Payload Structure
The prepare_request filters receive a payload array containing:
[
'url' => string, // The API endpoint URL
'args' => array, // Request arguments (headers, method, etc.)
'settings' => array, // Loop module settings including module_id
'items_per_page' => int, // Items per page setting
'current_page' => int, // Current page number
'context' => string // 'preview' or 'query'
]
Basic Search Example
Here’s a complete example showing how to add a search parameter that filters API results:
// Add ?q=... to API requests when present in URL
add_filter('bb_api_loop_prepare_request', function($payload) {
$url = (string) ($payload['url'] ?? '');
$q = isset($_GET['q']) ? trim((string) wp_unslash($_GET['q'])) : '';
if ($q !== '') {
$url = add_query_arg('q', sanitize_text_field($q), $url);
}
$payload['url'] = $url;
return $payload;
}, 10);
// Segment cache by search query
add_filter('bb_api_loop_cache_key_suffix', function($suffix) {
if (!empty($_GET['q'])) {
$suffix .= '|q:' . md5((string) wp_unslash($_GET['q']));
}
return $suffix;
}, 10);Creating a Search Interface
Pair the filtering code above with a simple search form. Here’s a shortcode that creates a search box. Place [bb_api_loop_search] above your Loop module to add search functionality.
// [bb_api_loop_search]
add_shortcode('bb_api_loop_search', function() {
$q = isset($_GET['q']) ? (string) wp_unslash($_GET['q']) : '';
ob_start(); ?>
<form method="get" class="bb-api-loop-filters" id="bb-api-loop-search" style="margin-bottom:16px;">
<label style="margin-right:12px;">Search
<input type="text" name="q" value="<?php echo esc_attr($q); ?>"
placeholder="Search" data-strip-empty="1">
</label>
<?php
// Preserve other GET params
$preserve = $_GET;
unset($preserve['q']);
foreach ($preserve as $k => $v) {
if (is_array($v)) continue;
echo '<input type="hidden" name="'.esc_attr($k).'"
value="'.esc_attr(wp_unslash($v)).'" data-strip-empty="1">';
}
?>
<button type="submit">Apply</button>
</form>
<script>
(function(){
var f = document.getElementById('bb-api-loop-search');
if (!f) return;
f.addEventListener('submit', function(){
f.querySelectorAll('[data-strip-empty="1"]').forEach(function(el){
var val = (el.value||'').trim();
if (val === '') el.disabled = true;
});
});
})();
</script>
<?php return ob_get_clean();
});Scoping Filters
Scoping lets you apply filters selectively instead of globally. This is useful when you have multiple Loop modules on the same site pulling from different APIs, or when you want different filtering behavior for different modules using the same API. Without scoping, every filter would affect every Loop module, which can cause unexpected behavior.
By Module ID
Limit filters to specific Loop instances using the Loop module’s ID:
add_filter('bb_api_loop_prepare_request', function($payload) {
$settings = (array) ($payload['settings'] ?? []);
// Only apply to this specific module
if (($settings['module_id'] ?? '') !== 'your-module-id') {
return $payload;
}
$url = (string) ($payload['url'] ?? '');
$q = isset($_GET['q']) ? trim((string) wp_unslash($_GET['q'])) : '';
if ($q !== '') {
$payload['url'] = add_query_arg('q', sanitize_text_field($q), $url);
}
return $payload;
}, 10);By API Endpoint
Apply filters only when calling specific APIs:
add_filter('bb_api_loop_prepare_request', function($payload) {
$url = (string) ($payload['url'] ?? '');
// Only apply when calling the users endpoint
if (strpos($url, 'https://api.example.com/users') === false) {
return $payload;
}
$q = isset($_GET['q']) ? trim((string) wp_unslash($_GET['q'])) : '';
if ($q !== '') {
$payload['url'] = add_query_arg('q', sanitize_text_field($q), $url);
}
return $payload;
}, 10);Facet Loading Animation
When implementing your own facets (search, filters, reset, etc.), you can show a consistent loading overlay/spinner by toggling the bb-api-loading class on the Loop wrapper element .fl-module-loop. The CSS for this state is provided in css/bb-api-loop.css and renders a dim overlay and spinner while loading.
Notes:
- Add the class before your request and remove it after you swap in the new grid/pagination markup.
- If your control is tied to a specific Loop instance, you can target just that module’s wrapper instead of all
Minimal example:
(function(){
var facetSubmit = document.getElementById('facet-submit');
if (!form) return;
facetSubmit.addEventListener('submit', function(){
document.querySelectorAll('.fl-module-loop').forEach(function(loop){
loop.classList.add('bb-api-loading');
var grid = loop.querySelector('.fl-loop-grid');
if (grid) {
if (!grid.getAttribute('aria-live')) grid.setAttribute('aria-live', 'polite');
if (!grid.getAttribute('role')) grid.setAttribute('role', 'region');
if (!grid.getAttribute('aria-label')) grid.setAttribute('aria-label', 'Loop results');
if (!grid.getAttribute('aria-atomic')) grid.setAttribute('aria-atomic', 'false');
grid.setAttribute('aria-busy', 'true');
}
});
// Loop will refresh; no need to remove the class
});
})();AJAX Filtering
For a smoother user experience, you can implement AJAX-based filtering that updates the loop content without a full page reload. The recommended approach is a client-side GET request that fetches the page with filter parameters, then extracts and swaps the module content.
How it works:
- Build a URL with your filter parameters as query strings
- Fetch the full page HTML via GET request
- Parse the response and extract the target module by its
data-nodeattribute - Swap the grid content and pagination in the current page
- Update the browser URL with
history.pushState()for bookmarkability
JavaScript Example:
function filterLoop(loopElement, facetParams) {
var moduleId = loopElement.getAttribute('data-node');
// Build URL with facet params
var url = new URL(window.location.href);
for (var key in facetParams) {
if (facetParams[key] !== '') {
url.searchParams.set(key, facetParams[key]);
} else {
url.searchParams.delete(key);
}
}
// Show loading state
loopElement.classList.add('bb-api-loading');
fetch(url.toString(), {
method: 'GET',
credentials: 'same-origin'
})
.then(response => response.text())
.then(html => {
// Parse and extract module content
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
var newModule = doc.querySelector('.fl-module[data-node="' + moduleId + '"]');
if (newModule) {
// Update grid
var newGrid = newModule.querySelector('.fl-loop-grid');
var currGrid = loopElement.querySelector('.fl-loop-grid');
if (currGrid && newGrid) {
currGrid.innerHTML = newGrid.innerHTML;
}
// Update pagination
var newPag = newModule.querySelector('.bb-api-pagination-wrapper');
var currPag = loopElement.querySelector('.bb-api-pagination-wrapper');
if (newPag) {
if (currPag) {
currPag.outerHTML = newPag.outerHTML;
} else {
currGrid.insertAdjacentHTML('afterend', newPag.outerHTML);
}
} else if (currPag) {
currPag.remove();
}
// Update URL for bookmarkability
window.history.pushState({}, '', url.toString());
}
loopElement.classList.remove('bb-api-loading');
});
}Notes:
- This approach benefits from page caching – filtered pages can be served from cache
- The rendered HTML is identical to a normal page load, ensuring consistency
- Filter parameters are processed by the
bb_api_loop_prepare_requestfilter hooks - Use
history.pushState()so users can bookmark or share filtered results
Cache Considerations
Always use bb_api_loop_cache_key_suffix when adding dynamic parameters. This ensures each unique set of filter values gets its own cache entry, preventing users from seeing stale filtered results.
Without proper cache segmentation, a user filtering for “Category A” might see cached results from another user who filtered for “Category B”.
Security Notes
- Always sanitize user input using
sanitize_text_field()or appropriate WordPress sanitization functions - Use
wp_unslash()when reading from$_GETor$_POST - Validate data types and formats before adding to requests
- The plugin re-validates URLs after filters to prevent SSRF attacks
SSRF Protection
The plugin validates URLs after your filters run. Only public http and https hosts are allowed by default. Localhost, private IPs, and other protocols are blocked to prevent Server-Side Request Forgery attacks.
Developer Filters
Custom Value Formatting
The bb_api_loop_value filter lets you create custom formatters beyond the built-in options. This is perfect for currency, phone numbers, or any data transformation you need.
// Example of custom formatting for currency
add_filter('bb_api_loop_value', function($output, $raw_value, $settings) {
if ($settings['format'] === 'currency') {
return '$' . number_format((float)$output, 2);
}
return $output;
}, 10, 3);The filter receives three parameters:
$output– The formatted value (after built-in formatters)$raw_value– The original value from the API$settings– Array of connection settings includingformat,length,more, etc.
Once you’ve added your custom formatter, use it in Themer connections by setting the format attribute to your custom value (e.g., format='currency').
Pagination Text Customization
The bb_api_loop_pagination_defaults filter lets you customize pagination button text and other default settings.
// Pagination customization
add_filter('bb_api_loop_pagination_defaults', function($defaults) {
$defaults['prev_text'] = '← Previous';
$defaults['next_text'] = 'Next →';
return $defaults;
});Available defaults you can modify:
prev_text– Previous button text (default: “Previous”)next_text– Next button text (default: “Next”)load_more_text– Load More button text (default: “Load More”)
Styling
The plugin uses CSS variables with built-in fallback values using var(--variable-name, fallback-value) syntax. This means you can customize pagination styling without worrying about missing defaults, and you only need to override the variables you want to change. Add any of these to your theme’s CSS to match your design:
:root {
--bb-api-pagination-gap: 6px;
--bb-api-pagination-padding-y: 5px;
--bb-api-pagination-padding-x: 10px;
--bb-api-pagination-border-color: #ddd;
--bb-api-pagination-border-width: 1px;
--bb-api-pagination-radius: 3px;
--bb-api-pagination-bg: #fff;
--bb-api-pagination-color: #333;
--bb-api-pagination-hover-bg: #f5f5f5;
--bb-api-pagination-hover-color: #0073aa;
--bb-api-pagination-current-bg: #0073aa;
--bb-api-pagination-current-color: #fff;
--bb-api-pagination-disabled-bg: #f5f5f5;
--bb-api-pagination-disabled-color: #999;
}Advanced Auth Options
OAuth Authentication
While the plugin supports Bearer tokens, API keys, and basic authentication, some APIs require OAuth 2.0 authentication flows. OAuth adds complexity because it involves multiple steps: redirecting users to authorize your app, handling callbacks, and exchanging authorization codes for access tokens.
For many OAuth APIs, you can use the plugin’s request filters to dynamically inject access tokens without creating a full proxy endpoint. This works great for server-to-server OAuth flows (like Spotify’s client_credentials grant type) where you don’t need user-specific authorization.
How It Works
Instead of manually entering a Bearer token in the Loop settings (which would expire), you use WordPress filters to:
- Fetch an access token from the OAuth API using your client credentials
- Cache the token (since tokens typically last 1+ hours)
- Inject that token into the Loop module’s API requests automatically
- Handle token refresh when it expires
The Loop module makes requests directly to the API, but your filter code handles the authentication behind the scenes.
Example: Spotify Podcast Episodes
Spotify’s API uses OAuth 2.0 for authentication. Here’s how to set up automatic token injection for fetching podcast episodes:
Step 1: Get Spotify API Credentials
Register your app at Spotify for Developers to get your Client ID and Client Secret.
Step 2: Add Token Management Code
Add this to your theme’s functions.php or a custom plugin:
// Helper function to get and cache Spotify access token
function get_spotify_access_token() {
$client_id = 'YOUR_CLIENT_ID_HERE';
$client_secret = 'YOUR_CLIENT_SECRET_HERE';
// Check for cached token
$cached_token = get_transient('spotify_access_token');
if ($cached_token) {
return $cached_token;
}
// Request new token
$token_response = wp_remote_post('https://accounts.spotify.com/api/token', array(
'timeout' => 10,
'headers' => array(
'Content-Type' => 'application/x-www-form-urlencoded',
'Authorization' => 'Basic ' . base64_encode($client_id . ':' . $client_secret)
),
'body' => array(
'grant_type' => 'client_credentials'
)
));
if (is_wp_error($token_response)) {
error_log('Spotify token error: ' . $token_response->get_error_message());
return false;
}
$token_data = json_decode(wp_remote_retrieve_body($token_response), true);
if (!isset($token_data['access_token'])) {
error_log('Spotify: No access token in response');
return false;
}
$access_token = $token_data['access_token'];
// Cache token for 50 minutes (Spotify tokens last 1 hour)
set_transient('spotify_access_token', $access_token, 3000);
return $access_token;
}
// Inject token into Loop module requests
add_filter('bb_api_loop_prepare_request', function($payload) {
$url = (string) ($payload['url'] ?? '');
// Only apply to Spotify API requests
if (strpos($url, 'api.spotify.com') === false) {
return $payload;
}
$token = get_spotify_access_token();
if ($token) {
$args = (array) ($payload['args'] ?? []);
$args['headers'] = isset($args['headers']) ? (array) $args['headers'] : [];
$args['headers']['Authorization'] = 'Bearer ' . $token;
$payload['args'] = $args;
}
return $payload;
}, 10);
// Also inject token into preview requests in Loop settings
add_filter('bb_api_loop_preview_prepare_request', function($payload) {
$url = (string) ($payload['url'] ?? '');
if (strpos($url, 'api.spotify.com') === false) {
return $payload;
}
$token = get_spotify_access_token();
if ($token) {
$args = (array) ($payload['args'] ?? []);
$args['headers'] = isset($args['headers']) ? (array) $args['headers'] : [];
$args['headers']['Authorization'] = 'Bearer ' . $token;
$payload['args'] = $args;
}
return $payload;
}, 10);Step 3: Configure the Loop Module
In your Loop module settings:
- Data Source: API
- API URL:
https://api.spotify.com/v1/shows/SHOW_ID_HERE/episodes - Authentication: None (the filters handle it)
- Data Path:
items - Total Count Source:
total - Items Per Page: 20
- Pagination Style: Numbers
- API Page Param:
offset - API Per Page Param:
limit
Replace SHOW_ID_HERE with the actual Spotify show ID from the show’s URL.
The filter code automatically injects the Bearer token into every request the Loop module makes to Spotify, including pagination requests and the preview in Loop settings.
Scoping by Module ID
If you have multiple Loop modules on your site and only want OAuth to apply to specific ones, you can scope by module ID:
add_filter('bb_api_loop_prepare_request', function($payload) {
$settings = (array) ($payload['settings'] ?? []);
// Only apply to this specific module
if (($settings['module_id'] ?? '') !== 'your-module-id-here') {
return $payload;
}
$token = get_spotify_access_token();
if ($token) {
$args = (array) ($payload['args'] ?? []);
$args['headers'] = isset($args['headers']) ? (array) $args['headers'] : [];
$args['headers']['Authorization'] = 'Bearer ' . $token;
$payload['args'] = $args;
}
return $payload;
}, 10);When to Use a Proxy Endpoint Instead
The filter approach works great for straightforward OAuth flows, but you might need a custom proxy endpoint when:
Data Transformation: You need to combine data from multiple API calls, transform response structures, or filter data before it reaches the Loop module.
Complex Flows: The API requires user-specific authorization (authorization_code grant type) rather than server-to-server auth.
Rate Limiting: You want to add additional caching layers or implement custom rate limiting beyond what the plugin provides.
Multiple Endpoints: You’re combining data from several different API endpoints into a single unified response.
For these scenarios, create a custom REST endpoint on your site that handles the OAuth flow and data manipulation, then point your Loop module at that endpoint. The endpoint acts as a proxy between the Loop and the external API, giving you full control over authentication and data formatting.
Adapting for Other APIs
This filter-based pattern works for any OAuth API that uses client_credentials or similar server-to-server flows. The key steps remain the same:
- Create a helper function that fetches and caches the access token
- Use
bb_api_loop_prepare_requestandbb_api_loop_preview_prepare_requestfilters to inject the token - Scope the filters to specific URLs or module IDs as needed
- Leave authentication fields empty in the Loop module settings
Check your API’s OAuth documentation for the specific token endpoint URL, required parameters, and grant type. Most APIs that don’t require user-specific authorization can use this approach.
Accessibility
The plugin’s pagination controls include full ARIA support for screen readers, semantic HTML structure, responsive design, and complete keyboard navigation. Grid and layout accessibility depends on how you structure your Loop template.
Troubleshooting
Keys dropdown is empty: Open Loop settings and run Preview to refresh available keys.
API returns empty results: Check your Data Path setting and verify JSON structure in preview.
Pagination not working: Ensure you’ve set the Total Count Source for Numbers pagination.
Authentication failing: Verify API credentials and check custom headers format.
Note: These API feed fields are not compatible with BB 2.10’s “Component” feature
Wrapping It All Up
The Beaver Builder API Loop plugin fundamentally changes how you approach external data in WordPress. No longer are you confined to displaying only WordPress posts and pages in your Loop modules. With this plugin, any REST API becomes a potential content source for your Beaver Builder layouts.
What makes this plugin particularly powerful is its seamless integration with existing Beaver Themer workflows. You don’t need to learn new systems or abandon familiar tools. The same connection interface you use for WordPress data now works with external APIs. The same pagination controls you’re accustomed to now handle remote data sources with intelligent caching and performance optimizations.
The plugin’s flexibility means it grows with your needs. Start simple with a basic API feed, then add authentication as your requirements evolve. Implement straightforward pagination, then customize it with your own styling variables and filters. The robust architecture supports everything from simple product displays to complex, multi-source content aggregation systems.
Whether you’re building a client site that needs to display inventory from their existing system, creating a news aggregator that pulls in the top headlines, or developing a portfolio site that showcases work from a design platforms, this plugin provides the foundation for reliable, performant API integration without the complexity of custom development.
Most importantly, the plugin handles the technical challenges of remote data display – caching, pagination, error handling, and security – so you can focus on creating compelling user experiences. Your API-powered content loads fast, navigates smoothly, and provides the same polished experience users expect.
Purchase this Plugin
Sign up for SnippetNest
Get access to everything for $99/year