Custom Connector Reference

Last updated: August 2026

A Custom connector lets you write your own PHP logic to feed a Swiffle Grid block — a live API call, a database query, or a fully embedded dataset. It’s the same mechanism used internally by the built-in WordPress, WooCommerce, Google Sheets, CSV, and RSS sources, just exposed for your own code.

📦 Starting point. Don’t want to write the plugin header and registration block from scratch? Download the connector boilerplate — a ready-to-copy plugin skeleton with the header, the registration filter, and a stub get_data() function already wired up. Rename the folder and function names, fill in your own logic, and you’re done.

1. Package structure and registration

A Custom connector is a standalone WordPress plugin, installed the same way as any other plugin — via Plugins → Add New → Upload Plugin. It never runs inside Swiffle Grid’s own settings screen; it simply announces itself to Swiffle Grid through a filter hook, so Swiffle Grid knows it exists and how to call it.

Why a separate plugin? A plugin you install yourself, through WordPress’s own «Upload Plugin» screen, is something you chose to add to your site — exactly like installing any other extension. It shows up in your Plugins list, can be deactivated independently, and nothing runs that WordPress itself didn’t already ask you to confirm.

Your plugin’s main file needs three things: a standard plugin header, an ABSPATH guard, and a registration block:

<?php
/*
Plugin Name: My Connector
Requires Plugins: swiffle-grid-embed-data-anywhere
*/

if ( ! defined( 'ABSPATH' ) ) exit;

add_action( 'plugins_loaded', function () {

    add_filter( 'swiffle_grid_register_connector', function ( $connectors ) {
        $connectors['swg_connector_myslug'] = [
            'label'           => __( 'My Connector', 'my-connector' ),
            'get_data'        => 'swiffle_custom_myslug_get_data',
            'get_filter_form' => 'swiffle_custom_myslug_get_filter_form', // optional
        ];
        return $connectors;
    } );

} );
⚠️ Mandatory slug prefix. The array key you register (swg_connector_myslug above) must start with swg_connector_. Swiffle Grid silently ignores any registered slug that doesn’t — this guarantees a connector can never collide with (or accidentally shadow) a built-in source type such as wp, woo, csv, or rss.
Registration keyRequired?Purpose
labelYesName shown in the block’s «Source» dropdown and in Swiffle Grid’s admin screen
get_dataYesName of the function that returns the data for the grid
get_filter_formNoName of the function that returns the filter form. Omit this key entirely if your connector has no filter.

Once your plugin is active, its slug (myslug in the example above — without the swg_connector_ prefix) appears automatically in the «Source» dropdown of every Swiffle Grid block set to Custom, and the connector is listed under Swiffle Grid in the WordPress admin sidebar for reference. There is nothing to import, edit, or delete from within Swiffle Grid itself — manage the connector the same way you’d manage any other plugin, under Plugins → Installed Plugins.

Storing API credentials. Never hardcode an API key or secret directly in your plugin file. Add your own settings page (register_setting() + a form under options.php, storing the value via wp_options) and read it back with get_option() inside get_data(). This keeps secrets out of any file that might get copied, backed up, or shared.

2. The data function

function swiffle_custom_{slug}_get_data($pagesize, $strl, $parameters) {
    // ... your logic here ...
    return [$posts, $have_more, $css, $script];
}

Parameters

ParameterTypeMeaning
$pagesizeintMaximum number of cards to return for this page load. Set by the «Cards per page» block setting.
$strlint (passed as string)Zero-based offset for pagination. 0 on the first page, $pagesize on the second, and so on.
$parametersarrayAll contextual data available to the connector: filter values, block settings, and more. See below.

Filter values

Any field in your filter form with name="FILTR_xxx" arrives as $parameters['xxx'] — the FILTR_ prefix is stripped automatically. For example, name="FILTR_year" maps to $parameters['year'].

Block parameters — $parameters['swiffle_grid']

All block settings configured in Gutenberg or Elementor are available as a nested array under $parameters['swiffle_grid']. Most parameters exist in a desktop variant and a phone variant (key ending in p); your connector can use whichever is relevant to its logic.

Device variants Many parameters come in pairs: a laptop/desktop value and a phone value (key name ending in p). The badge Laptop and Phone below indicate which variant a key represents. When a parameter exists on both devices, only one row is shown with Both.
Show all block parameters (70+ entries)
Key in $parameters['swiffle_grid']DeviceDescription
proxyBothSite home URL — the WordPress installation that serves the connector
postssourceBothActive data source: category, rss, xlsx, csv, csvs, woocategory, custom
postsurlBothURL or slug of the source (RSS feed URL, CSV URL, custom connector slug, etc.)
postscategoryBothComma-separated list of category slugs (WordPress or WooCommerce)
orderbyBothWooCommerce sort order: date,DESC / popularity,DESC / rating,DESC / price,DESC / price,ASC / menu_order,DESC
filtertitleBothCustom title shown in the filter panel (empty = auto)
followpagefilterBothtrue / false — whether the block follows a page-level filter
followurlfilterBothtrue / false — whether the block follows URL-based filters (WooCommerce only)
shownodataerrBothtrue / false — whether to display an error message when no data is returned
includeheadersBothtrue / false — include WordPress post headers (WordPress source only)
blocklangBothLanguage preference: wp (follow WordPress locale via get_locale()), or an explicit locale code such as fr_FR, en_US, de_DE, es_ES, it_IT, pt_PT, pt_BR, nl_NL, pl_PL, ru_RU, ja_JP, ko_KR, zh_CN, hi_IN, ar_SA
blockcardsperpgLaptopCards per page (desktop). Same value as $pagesize on desktop.
blockcardsperpgpPhoneCards per page (phone)
blockdeflayoutLaptopDefault layout: wrap / swipe / swipefull / row
blockdeflayoutpPhoneDefault layout (phone)
blockshowlayoutLaptopLayout toggle button shown to the visitor: no / all / wrap / swipe / swipefull / row
blockshowlayoutpPhoneLayout toggle button (phone)
filterbtnshowBothtrue / false — show the filter button
minicartbtnshowLaptoptrue / false — show the mini cart (WooCommerce only)
minicartbtnshowpPhoneShow mini cart (phone, WooCommerce only)
minicartbtnshapeLaptopMini cart shape: SF (square with all) / S (square, quantity only) / RF (round with all) / R (round, quantity only)
minicartbtnshapepPhoneMini cart shape (phone)
minicarticoBothMini cart icon: bag / cart / basket
minicartloginBothtrue / false — show login button inside mini cart (WooCommerce only)
minicartbtnlocationLaptopMini cart position: L / T / R / B / H (HTML element, uses CSS selector below)
minicartbtnlocationpPhoneMini cart position (phone)
minicartbtnselectorLaptopCSS selector for the HTML element that hosts the mini cart (when location is H)
minicartbtnselectorpPhoneCSS selector (phone)
minicartbtnmarginLaptopMini cart margin from the edge (e.g. 20vh)
minicartbtnmarginpPhoneMini cart margin (phone)
carddirectionLaptopCard front direction: row / column
carddirectionpPhoneCard front direction (phone)
cardimgadjustinLaptopImage adjustment: cover / height100 / width100
cardimgadjustinpPhoneImage adjustment (phone)
cardimgsizeLaptopImage quality (WP/WooCommerce): large / medium / thumbnail
cardimgsizepPhoneImage quality (phone)
cardflowtitleshowBothtrue / false — show the card flow title
cardflowtitleBothCard flow title (empty = auto-generated)
cardtitleoverpictureLaptoptrue / false — render title overlaid on the image
cardtitleoverpicturepPhoneTitle over picture (phone)
cardtitlefontsizeLaptopTitle font size (CSS value, e.g. 16px, 2vw)
cardtitlefontsizepPhoneTitle font size (phone)
cardpriceshowLaptoptrue / false — show price (WooCommerce only)
cardpriceshowpPhoneShow price (phone)
cardpriceoverpictureLaptoptrue / false — price overlaid on the image (WooCommerce only)
cardpriceoverpicturepPhonePrice over picture (phone)
cardalroverpictureLaptoptrue / false — alert label overlaid on image (WooCommerce only)
cardalroverpicturepPhoneAlert over picture (phone)
cardabstractshowLaptoptrue / false — show the card abstract/excerpt
cardabstractshowpPhoneShow abstract (phone)
cardtocartshowLaptoptrue / false — show Add to Cart button (WooCommerce only)
cardtocartshowpPhoneShow Add to Cart (phone)
cardrateshowLaptoptrue / false — show star rating (WooCommerce only)
cardrateshowpPhoneShow rating (phone)
cardoptionsshowLaptoptrue / false — show product variations (WooCommerce only)
cardoptionsshowpPhoneShow variations (phone)
cardiconshowLaptoptrue / false — show swipe icon on the card
cardiconshowpPhoneShow swipe icon (phone)
cardpromoshowBothSALE badge animation (WooCommerce only): no / spin / jumper / marquee / woo / woojumper
cardcontentonlyBothtrue / false — strip the card chrome and render content only
cardCartBtnRndBothtrue / false — round the Add to Cart / quantity buttons (WooCommerce only)
cardwidthLaptopCard width (CSS value, e.g. 300px, 30vw)
cardwidthpPhoneCard width (phone)
cardwidthmaxLaptopMaximum card width (CSS value)
cardwidthmaxpPhoneMaximum card width (phone)
cardheightLaptopCard height (CSS value)
cardheightpPhoneCard height (phone)
cardgapLaptopGap between cards (CSS value)
cardgappPhoneCard gap (phone)
cardradiuspxlLaptopCard corner radius in px (integer)
cardradiuspxlpPhoneCard radius (phone)
cardimgpctLaptopImage area as a percentage of the card height (0–100)
cardimgpctpPhoneImage percentage (phone)
cardimgradiuspxlLaptopCard image corner radius in px (integer)
cardimgradiuspxlpPhoneImage radius (phone)
cardbtcartsizeLaptopAdd to Cart button size 0–10 (WooCommerce only)
cardbtcartsizepPhoneCart button size (phone)
cardclickLaptopCard front click action: flip / modal/80 / modal/50 / modal/100 / navigate / navigatenew / navigateurl / navigateurlnew / none
cardclickpPhoneCard front click action (phone)
cardnavurlBothTarget URL (used when action is navigateurl or navigateurlnew for RSS, CSV, XLSX sources)
cardnavshowBothtrue / false — show the navigate button (RSS, CSV, XLSX sources)
blockshowcommentsBothtrue / false — show comments (WordPress/WooCommerce sources)

3. Return value

The function must return a 4-element indexed array:

return [$posts, $have_more, $css, $script];

$posts — two supported formats

A. Tabular / auto-rendered mode

The first element of $posts is a header row containing column names; every subsequent element is a data row with values in the same order. Swiffle Grid reads the column names and auto-generates the matching card element based on recognised prefixes:

PrefixGeneratesExample column name
TIT_Card titleTIT_CityName
IMG_Card image — the first IMG_ column becomes the main front-card imageIMG_Photo1
IMGZ_Zoomable imageIMGZ_Detail1
IMGZFS_Full-screen zoomable image in modalIMGZFS_Detail1
MAP_OpenStreetMap card generated from coordinates or an addressMAP_Headquarters
NAV_URL opened when the card-click action is set to Navigate URLNAV_OfficialWebsite
SLIDE_Image or video embed added to the slide gallerySLIDE_Photo2
SLIDEZOOM_Zoomable image or video embed in the zoom gallerySLIDEZOOM_Photo2
SLIDEFULL_Zoomable + full-screen image (no video) in the gallerySLIDEFULL_Photo2
OTHERAdditional HTML or plain-text contentOTHER_Description
⚠️ Column names must be unique. Duplicate column names may cause unexpected behaviour when Swiffle Grid processes your data.
ℹ️ NAV_ columns and the card back. If a NAV_ column is defined, the card behaves as a direct link — visitors will not see the card’s back-side content (slide galleries, media, etc.). Use either a NAV_ column or back-side content, not both.

Example — tabular mode:

$posts = [
    ['TIT_Title', 'IMG_Photo', 'OTHER_Description'],  // header row
    ['Grand Canyon',  'https://example.com/grand_canyon.jpg',  'A vast canyon in Arizona.'],
    ['Mount Fuji',    'https://example.com/mount_fuji.jpg',    'Japan's iconic volcano.'],
];

B. Full custom HTML mode

When you need full control over the card layout, each element of $posts is an associative array with three keys:

$posts[] = [
    'front'  => '<div class="my-card">...</div>',  // HTML for the card's front face
    'back'   => '<div class="my-back">...</div>',   // HTML for the card's back face (shown on flip)
    'navurl' => 'https://example.com/page',           // destination URL; empty string if unused
];
The CSS you return in $css (see below) must cover all classes used in your front and back HTML, since page-level theme CSS cannot reach inside the block’s Shadow DOM.

$have_more

String 'y' or 'n'. Tells the block whether a next page of results exists. Set it to 'y' when more records are available beyond the current page, 'n' when the current page is the last one.

$css

A string of CSS, injected directly into the block’s own rendering scope. Because Swiffle Grid renders as a <swiffle-grid> custom element using a Shadow DOM, page-level theme CSS does not reach inside it. Every class and style your connector’s HTML relies on must be returned here.

$css = <<<CSS
    .my-card { background:#0f172a; color:#e5e7eb; border-radius:16px; }
    .my-card h3 { margin:0; font-size:18px; }
CSS;

$script

A JavaScript string executed after the block has finished rendering its HTML for the current page. Use it to wire up client-side behaviour that depends on the rendered card DOM (event listeners, third-party widget initialisation, etc.). It runs once per page load (i.e. once per pagination step).

$script = <<<SCRIPT
    document.querySelectorAll('.my-card').forEach(card => {
        card.addEventListener('click', () => console.log('Card clicked'));
    });
SCRIPT;

4. The filter form function

function swiffle_custom_{slug}_get_filter_form() {
    // ...
    return $html . "n<!--SWIFFLE_SCRIPT-->n" . $js;
}

Returns a single string: the filter form’s HTML, followed by the literal separator <!--SWIFFLE_SCRIPT-->, followed by JavaScript that runs once the form is in the page. The function does not return a CSS value — all CSS for the filter form must be included inline in the HTML string itself (via a <style> tag or inline style attributes).

  • Every input that should act as a filter must use name="FILTR_xxx". The FILTR_ prefix is stripped and the remainder becomes the key in $parameters.
  • The JavaScript portion runs in the page context, not inside the Shadow DOM. To reach inside the block, use the standard pattern:
const getSwiffleBlock = () => {
    for (const sg of document.querySelectorAll('swiffle-grid')) {
        if (sg.shadowRoot?.querySelector('.fltfrm')) return sg;
    }
    return null;
};
const block = getSwiffleBlock();

Minimal example:

function swiffle_custom_myplugin_get_filter_form() {
    $html = <<<HTML
        <div class="form_control">
            <div class="form_control_container">
                <label for="my_year">Year</label>
                <input type="number" id="my_year" name="FILTR_year" min="2000" max="2030" placeholder="E.g.: 2024">
            </div>
        </div>
HTML;

    $js = <<<JS
        (() => {
            const block = (() => {
                for (const sg of document.querySelectorAll('swiffle-grid')) {
                    if (sg.shadowRoot?.querySelector('.fltfrm')) return sg;
                }
            })();
            if (!block) return;
            // Add behaviour here if needed.
        })();
JS;

    return $html . "n<!--SWIFFLE_SCRIPT-->n" . $js;
}

5. Pagination patterns

The recommended approach is to request $pagesize + 1 records from your data source, then check whether that extra record exists to set $have_more, and remove it before returning the slice. This avoids a separate count query and works with any data source.

$limit  = intval($pagesize) + 1;           // fetch one extra
$offset = max(0, intval($strl));

// ... fetch $limit records from offset $offset ...

if (count($data) === $limit) {
    $have_more = 'y';
    array_pop($data);                      // remove the extra record
} else {
    $have_more = 'n';
}

$posts = [['TIT_Title', 'IMG_Photo']];    // header row
foreach ($data as $row) {
    $posts[] = [$row['title'], $row['image']];
}

You are free to use a different method — the only constraint is that $have_more must accurately reflect whether more pages exist. A separate count query, a date-window calculation, or any other logic that correctly sets this value is equally valid.

6. Practical recommendations

  • Use wp_remote_get(), not file_get_contents() — some hosts disable the allow_url_fopen PHP setting. wp_remote_get() uses cURL when available, returns a real HTTP status code, and gives you a WP_Error object to check on failure instead of a silent false.
  • Set an explicit user-agent header — some external APIs return 403 Forbidden to requests with an empty or generic user agent. A descriptive user agent also helps API owners identify legitimate traffic.
  • Cache responses for unreliable APIs — use set_transient($key, $data, HOUR_IN_SECONDS) for a short-lived cache, and update_option($key, $data, false) for a durable fallback. On a transient miss, try the API; if the API fails, fall back to the last stored option. This ensures your grid stays visible even during brief outages.
  • Never hardcode credentials — store API keys and secrets via register_setting() and get_option() on your own settings page (see section 1), never as a literal string or a define() in your plugin file.
  • Escape all output — use htmlspecialchars() for plain text, esc_attr() for HTML attribute values, esc_html() for inline text content, and esc_url() for URLs.
  • Handle empty results gracefully — always return a valid 4-element array, even when there is no data: return [[], 'n', '', ''];
Planning to submit your connector to WordPress.org? The public plugin directory does not accept heredoc/nowdoc syntax (<<<CSS ... CSS;) in submitted code — replace it with plain string concatenation before submitting. This only matters if you intend to publish your connector publicly; for a private, personally-installed plugin, heredocs are perfectly fine and the examples above can be used as-is.