Documentation
Embedding the widget
Publishers earn per click on listings rendered by the widget. Two integration paths — both need only your placement ID (issued when your placement is created).
Option A: direct snippet
Paste where the ads should appear:
<div data-yieldfeeds="YOUR_PLACEMENT_ID"></div>
<script src="https://yieldfeeds.com/embed.js" async></script>
Multiple placements per page are fine — one div each; the script tag is needed once.
Option B: Google Tag Manager
- In GTM: Templates → Tag Templates → New, open the top-right menu → Import, choose
yieldfeeds.tpl(we provide it), Save. - Tags → New, pick the YieldFeeds Listings Widget template.
- Enter your Placement ID — that's the tag's only field.
- Trigger: Initialization - All Pages (or an Initialization trigger limited to the pages you want ads on). Publish.
Use Initialization - All Pages, not the plain "All Pages" trigger. "All Pages" fires on the
gtm.jsevent, which your page's GTM snippet pushes intodataLayerbefore the container script loads. If your site hand-rolls that snippet — particularly if it also loads GA4'sgtag/jsonto the samedataLayer— that queued event can be lost, and a tag triggered on it then never fires: noembed.js, no widget, and no request ever reaches YieldFeeds, so nothing shows up in your reporting either.Initialization - All Pagesfires ongtm.init, which GTM synthesizes itself at container initialization, so it does not depend on the page pushing anything. Symptom to watch for: the widget renders in one browser but not another on the same page, while your other tags (AdSense, analytics) are unaffected.
The tag is safe to fire more than once, so you can add extra triggers without risk: injectScript
uses a cache key, so embed.js is fetched and executed only once per page, and load() is a no-op
against a container that is already mounted.
Where the widget renders and whether it stores a visitor id are no longer set on the tag — they're placement settings in the publisher dashboard (/app). Set a Container CSS selector on the placement (e.g. #sidebar) and the tag mounts there automatically; leave it blank and add <div data-yieldfeeds="YOUR_PLACEMENT_ID"></div> to the page yourself instead. Turn on Do not store a visitor id on the placement for consent-gated setups — see Storage and consent below.
The template is published at https://github.com/PavleDz/yieldfeeds-gtm-template (that repository is the copy submitted to the GTM Community Template Gallery). Once Google approves it, publishers can add it by searching the gallery inside GTM instead of importing the file. gtm/yieldfeeds.tpl in this repo is kept byte-identical to that repository's template.tpl — change both together, or the gallery copy drifts from what we ship.
Behavior
- The widget renders an image-card carousel sized to its container: cards ≥ your placement's minimum card width (default 240px), up to the per-page cap (default 6), swipeable through a pool of up to 24 listings.
- No ads available (or any error) → the widget collapses to nothing; your layout is unaffected.
- Impressions are counted only when a card is actually visible in the viewport.
- Styling is isolated (Shadow DOM).
Storage and consent
The widget stores one random id in localStorage under the key yf_id. This is first-party storage (your own origin, via the widget script running on your page) used only for click de-duplication — it lets us avoid double-counting a click from the same visitor. There are no cookies and no personal data in it; the server only ever sees a hash of it, never the raw id.
If your setup is consent-gated and storage hasn't been granted yet, opt a container out:
<div data-yieldfeeds="YOUR_PLACEMENT_ID" data-yf-noid></div>
With data-yf-noid set, the widget skips localStorage entirely (no read, no write) and omits the click de-duplication parameter from listing links. In GTM, use the Do not store a visitor id setting on the placement instead (see above) — it's checked server-side, so it also covers any snippet installs of that placement. Either source disabling storage wins.
Layout stability
Reserve space for the widget so it doesn't cause layout shift while it loads:
<div data-yieldfeeds="YOUR_PLACEMENT_ID" style="min-height:280px"></div>
Size min-height to your expected card height for the common case — the widget renders as soon as it has a pool, but it collapses to height: 0 when no ads serve (see Behavior above), so pick a reservation that suits the common ads-served case rather than the empty case.
window.yieldfeeds.load()
This is what the GTM tag calls. window.yieldfeeds.load(placementId, opts?):
placementId— your placement ID; that's the only thing the GTM tag needs to send.opts.noId— optional;truebehaves likedata-yf-noid. The placement's own Do not store a visitor id setting is honored too — either source disabling storage wins.
load() fetches the placement's config first, then resolves where to render: the placement's Container CSS selector if it's set and matches an element on the page, otherwise an existing <div data-yieldfeeds="YOUR_PLACEMENT_ID"> on the page, otherwise it does nothing (never invents a location in your layout). It shares the same rendering, refresh, and teardown lifecycle as mount() and auto-mounted [data-yieldfeeds] containers below.
window.yieldfeeds.mount()
For explicit control over when/where a placement mounts, use window.yieldfeeds.mount(targetOrSelector, placementId, opts?):
targetOrSelector— a CSS selector string or anElement.placementId— your placement ID.opts.noId— optional;truebehaves likedata-yf-noid(see Storage and consent above).
Containers inserted into the page after initial load are picked up automatically (see below), so you normally don't need to call mount() yourself.
Single-page apps and soft navigation
If a site swaps content client-side (a router that uses the History API instead of full page loads), the widget detects the route change automatically and refreshes itself — no extra work needed. It watches for URL changes without patching history.pushState/replaceState (the widget never mutates host-page globals), and new [data-yieldfeeds] containers inserted into the page after load are picked up automatically too.
If your router isn't detected for some reason, call the escape hatch yourself:
window.yieldfeeds.refresh(); // refresh every mounted placement
window.yieldfeeds.refresh("#my-ad"); // refresh just one, by selector or Element
This is also the call to wire up in a GTM History Change trigger if you're managing the widget through GTM. Refreshes of the same placement are throttled to once per 5 seconds, so a noisy router firing rapid history events can't spam the pool endpoint.
Testing your placement
Open https://yieldfeeds.com/demo?p=YOUR_PLACEMENT_ID — a bare page with only your placement. If your placement is approved and ads are eligible, cards render there.