Developer guide
Fik Satellites puts an online store's products on any website: product grids, carousels, single products and buy buttons, with a cart and the store's own checkout. This page is for whoever builds or maintains the website. The store owner creates the widgets and sends you the code to paste: you don't need an account anywhere.
Your snippet
The store owner sends you a snippet like this one, ready to paste. Its comments number the parts:
<!-- Product widgets. Developer guide: https://app.fiksatellites.com/docs -->
<!-- 1. Once per page, in the site's header or footer (skip if it's already there) -->
<script src="https://cdn.fiksatellites.com/v1/sfw.js" defer data-shop="example-store" data-storefront="example-website"></script>
<!-- 2. Optional: the cart icon, e.g. in the header menu. Without it, a floating cart
button appears once something is in the cart. To use it, move this line out of the comment:
<div data-sfw-widget="cart"></div>
-->
<!-- 3. Where these products should appear -->
<div data-sfw-widget="scroller" data-collection="512295272597" data-id="home-new-releases"></div>- The script goes once on every page that shows widgets, ideally in the site-wide header or footer. It's the same line in every snippet for this website, so if it's already on the page, skip it.
- The cart icon is optional. Put the
<div data-sfw-widget="cart"></div>line where the icon should sit, usually the site-wide header, outside the comment. Without it, a floating cart button appears in the bottom-right corner once something is in the cart. - The widget goes exactly where the products should appear. Paste it as it is: it already says which products to show, in which layout, and names this placement for the store's sales reports.
Everything else (colors, corners, font, language, market) comes from the store owner's widget settings, so they can change it later without touching the website.
Widgets never break the page. If something is misconfigured or a service is down, they stay invisible and log a [sfw] warning to the browser console. See Troubleshooting.
Where to paste it
The cart and widget lines are plain divs, which most editors keep intact. The script needs a place that allows scripts: usually a site-wide header or footer setting, otherwise an HTML block on the same page.
| Platform | 1. Script (once per site) | 2–3. Cart icon and widgets |
|---|---|---|
| WordPress | In the theme's header or footer, or with a plugin such as WPCode (“Header & Footer”). Block themes: Site Editor → a Custom HTML block in the header template part. | Block editor: a “Custom HTML” block. Classic editor: the “Text” tab. Elementor: the “HTML” widget. Divi and WPBakery: their “Code” module. |
| Joomla | A “Custom” module in a header or footer position, with the editor set to “None”. If the script gets stripped, allow it in Global Configuration → Text Filters for that user group. | A “Custom” module, or an article with the editor set to “None”. |
| Drupal | The theme's html.html.twig or a library, or a module such as Asset Injector. | The body field with the “Full HTML” text format (or any format that allows div with data- attributes). |
| Webflow | Site settings → Custom code → Footer code (needs a paid site plan). | A “Code Embed” element. |
| Squarespace | Code Injection → Footer (Core plan or higher). Newer accounts: Website → Website Tools → Code Injection; older ones: Settings → Advanced. | A “Code” block, set to HTML. |
| Wix | Settings → Custom code → add to all pages, at Body end (needs a premium plan with a connected domain). | Limited: Wix puts “Embed HTML” elements in an isolated frame, so a widget there can't use the page's cart. Put the script and the widget in the same embed; that widget then has its own cart and checkout button inside the frame. |
| Ghost | Settings → Code injection → Site footer. | An “HTML” card. |
| HubSpot CMS | Settings → Content → Pages → Templates → Site footer HTML. | A “Custom HTML” module, or the rich text editor's source code view. |
| Custom site (React, Vue, Next.js, static HTML…) | Load the script once, e.g. in the root layout or page template, with defer (Next.js: strategy “afterInteractive”). | Render the widget div as it is (in JSX, without the HTML comments). Widgets added later, by client-side routing or AJAX, are picked up automatically. See “React and other frameworks” below. |
Caching and optimisation plugins (WP Rocket, Autoptimize, LiteSpeed, Cloudflare Rocket Loader…) sometimes combine or delay scripts. If widgets don't appear, exclude /v1/sfw.js from combining and delaying, or add data-cfasync="false" to the script tag for Rocket Loader.
React and other frameworks
- Load the script from the HTML document or the root layout, not from a component: script tags rendered by React don't run. With server rendering, load it after hydration (Next.js:
<Script strategy="afterInteractive">), otherwise the widgets change their placeholder before React hydrates and React reports a mismatch. - In JSX, drop the HTML comments and render the widget
divas it is. Placeholders added later (client-side routing, AJAX) are picked up automatically. - A placeholder mounts once. If a route reuses the same
divfor a different snippet, give it a Reactkeyso a new one is created. - Placeholders inside your own Shadow DOM aren't seen automatically: call
SFW.mount(shadowRoot).
More widgets
Each widget is created by the store owner, with a live preview: a product grid, a carousel, a swipeable row, a single product or just a buy button. Need a different layout, other products, or the same widget in another spot? Ask whoever gave you the code for a new snippet: each placement gets its own, so the store can see which ones sell. Several widgets can share a page; the script still goes on once.
Products with options (size, color…) open a popup with a gallery, a variant picker and a quantity field. Only products the store has made available to this website are shown.
Cart
- The cart line from part 2 shows the cart icon with an item count. Put it in the site-wide header and load the script on every page, so the icon works everywhere. Clicking it opens the cart as a panel over the page.
- Adding a product opens the cart. Without a cart line, a floating cart button appears once something is in the cart.
- The cart is stored in the visitor's browser per domain (localStorage, no cookies), survives reloads and syncs across tabs.
www.and the bare domain keep separate carts. - Checkout opens in the same tab, at the top level even inside an iframe, on the store's own checkout.
- Your own button can open it:
window.SFW?.openCart()(SFWexists once the deferred script has run).
Language and market
- Language follows the page's
<html lang="…">when the widgets support it (Spanish and English today): button and cart text, price format, product translations from the store, and the checkout language. Bilingual sites usually set this per page already.lang="en"without a region uses the store's country for the price format. Other languages fall back to the store owner's default language. - To force a language on a page, add
data-locale="en-GB"(or anotheres-…/en-…locale) to the script tag. - Market (prices, currency, what's available) is set by the store owner. A cart has a single currency, so it's the same on every page of the website.
Styling
Widgets render inside a Shadow DOM, so the site's CSS can't accidentally break them, and their CSS can't leak into the site. They inherit the site's font unless the store owner chose one. The store owner sets accent color, corners and font; you can override them with CSS custom properties on the widget's placeholder or any ancestor. The product popup and the floating cart sit at the end of <body>, so set their colors and font on :root or body.
/* Whole site, including the product popup and floating cart */
:root {
--sfw-accent: #ec98ca;
--sfw-radius: 8px;
}
/* One placement */
.home [data-sfw-widget] {
--sfw-image-ratio: 4 / 5;
}| Property | Default | Controls |
|---|---|---|
--sfw-accent | #0b0b0b * | Buttons, cart badge, highlights |
--sfw-accent-hover | #333333 | Button hover |
--sfw-button-text | #ffffff | Text on accent buttons |
--sfw-text | #111111 | Text |
--sfw-muted | #6b6b6b | Secondary text, compare-at prices |
--sfw-bg | #ffffff | Popup and cart background |
--sfw-font | inherit * | Font family |
--sfw-radius | 0px * | Corners of images, buttons, cart |
--sfw-gap | 20px | Space between cards |
--sfw-columns | 4 | Columns when the widget is wider than 900px |
--sfw-columns-tablet | 3 | Columns between 600 and 900px (tablets, sidebars); 2 below 600px |
--sfw-card-width-mobile | 70% | Card width in mobile swipe rows |
--sfw-card-max-width | 320px | Width of the single product card |
--sfw-image-ratio | 1 / 1 | Product image aspect ratio |
* Or the store owner's setting. If a snippet already sets a column count, it wins over --sfw-columns: ask for a new snippet to change it.
For anything else, style the exposed parts with ::part():
sfw-grid::part(card-title),
sfw-scroller::part(card-title) {
text-transform: uppercase;
letter-spacing: 0.04em;
}
sfw-product-modal::part(modal) {
border-radius: 12px;
}
/* Move the floating cart button above a chat or cookie button */
sfw-cart[floating] {
bottom: 96px;
}| Element | Parts |
|---|---|
sfw-grid, sfw-carousel, sfw-scroller, sfw-collection | container, heading, track, nav, card, card-media, badge, card-title, price, button, load-more |
sfw-product | card, card-media, badge, card-title, price, button |
sfw-buy-button | button |
sfw-cart | cart-toggle, cart-count, cart, cart-lines, cart-footer, checkout, button, toast |
sfw-product-modal (the product popup) | modal, modal-title, price, quantity, button, description |
| — (any widget, in debug mode) | error |
JavaScript and events
| Call | What it does |
|---|---|
SFW.openCart() | Opens the cart, e.g. from the site's own cart link. |
SFW.consent(true | false) | Tells the widgets about analytics consent. See below. |
SFW.mount(root) | Mounts placeholders inside root, e.g. your own component's shadow root. Rarely needed. |
The widgets dispatch events on window, so the site can feed its own tracking. Each event.detail has storefront (the site ID), widget_id and items (GA4 format). view_item, add_to_cart, view_cart and begin_checkout also have value and currency; view_item_list and select_item have item_list_name. Events fire whatever the visitor's consent, so check consent before sending them to your own tools.
window.addEventListener("sfw:add_to_cart", (event) => {
console.log(event.detail.widget_id, event.detail.items);
});Events: sfw:view_item_list, sfw:select_item, sfw:view_item, sfw:add_to_cart, sfw:view_cart, sfw:begin_checkout.
Analytics and consent
- Every order records which website, widget and page it came from, and shows up in the store's orders labelled with the website. The checkout link carries
utm_source(the website),utm_medium=storefront_widgetandutm_campaign(the last widget used). - If the store owner set up Google Analytics 4, the widgets send GA4 ecommerce events, and the store's property also receives a
page_viewfrom your pages. If the page already has gtag, they use it; otherwise they load it with consent denied by default (Consent Mode v2). Before consent, Google receives cookieless pings; if your policy forbids any Google request before consent, tell the store owner. - Consent banners that support Google Consent Mode need nothing more. Otherwise call
SFW.consent(true)when the visitor accepts analytics, andSFW.consent(false)if they withdraw it. It also updatesanalytics_storageon the page's own gtag, so call it only from your consent banner. - A Google font chosen by the store owner loads from fonts.googleapis.com.
Performance and compatibility
- The script is about 17 KB gzipped, loads with
deferand is cached by a CDN. Widgets render after the page loads and fetch products live; images load lazily, so don't use a widget as your hero image (LCP). - A placeholder is empty until its widget renders. To avoid layout shift, give its container a
min-height. If the widget fails, that space stays empty. - Current Chrome, Edge, Firefox and Safari (iOS and macOS 16+). On touch screens, buttons are at least 44px. Buttons and dialogs are native and labelled; dialogs trap focus and close with Esc. Motion respects reduced-motion, and autoplay pauses on hover, focus and touch.
Troubleshooting
- Nothing shows. Add
?sfw-debug=1to the page URL: each widget shows why it's hidden, and the console logs[sfw]messages. Common causes: a typo indata-shopordata-storefront(edited by hand), the widgets were switched off for this store, or the store hasn't made those products available to this website. Send the store owner the console message. - Changes made by the store owner don't show. Settings take a few minutes (up to about 10) to reach websites. Products and prices are always live.
- Content Security Policy. If the site sends a CSP header, allow the following. The Google entries are only needed with GA4; the fonts entries only if the store owner chose a Google font; frame-src only for videos in product descriptions.
script-src https://cdn.fiksatellites.com https://www.googletagmanager.com
connect-src https://cdn.fiksatellites.com https://*.myshopify.com https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com
img-src https://cdn.shopify.com https://*.google-analytics.com https://*.googletagmanager.com
style-src 'unsafe-inline' https://fonts.googleapis.com
font-src https://fonts.gstatic.com
frame-src https://www.youtube.com https://www.youtube-nocookie.com- Two copies of the script are harmless: the second one does nothing. If they differ, the first script tag on the page is used.