JavaScript SDK
JavaScript SDK
Static sites load the Meridian Ads tag. Browser JavaScript can use the same npm package with a public web token. Node servers keep the HMAC secret in that process.
Static sites
A static page cannot hold an HMAC secret. Load https://meridianads.tech/tag/v1.js and set data-token to a web token from the console (mw_live_…). Mark each slot with data-meridian-placement. The tag does not import @yellesoftware/meridian-ads.
<script
async
src="https://meridianads.tech/tag/v1.js"
data-token="mw_live_0123456789abcdef"
></script>
<aside data-meridian-placement="leaderboard"></aside>
<div data-meridian-placement="infeed"></div>
<div data-meridian-placement="infeed"></div> When the document is ready, the tag requests every marked slot with POST /v1/decisions to https://serve.meridianads.tech. Each request carries at most 20 names. A longer list is several requests, in document order. Each placement name gets one fill, in document order. Repeating a name asks for another slot. Inside that response a campaign appears again only when it is the only campaign that can still fill. A house ad can fill a later slot before a paid campaign repeats. An empty slot is cleared and hidden. An impression is counted when the creative is inserted, including slots below the fold. Slots added after that first run are not requested.
An element can set its own data-token. Otherwise it uses the script token. Names for one token stay together, 20 per request. Optional data-context is JSON. Its keys replace the automatic locale and device values when they conflict. Invalid JSON is ignored, and those two traits are still sent. Put country or plan there when a campaign needs them. A campaign that requires a trait you do not send will not match.
Client JavaScript
A page that already runs in the browser can import @yellesoftware/meridian-ads and pass the public web token as webToken. The client sends X-Web-Key and does not sign. You pass user_key. render mounts the creative and records the impression. Do not also pass appKey or secret. The serve call is allowed only when the page origin matches a stored origin for that token.
import { MeridianAds } from '@yellesoftware/meridian-ads'
const client = new MeridianAds({
webToken: 'mw_live_0123456789abcdef',
})
const slot = document.querySelector('#home-banner')
const decision = await client.serve('home_banner', 'user-42', {
locale: navigator.language,
})
if (decision && slot instanceof HTMLElement) {
await client.render(slot, decision)
}Install
Use Node.js 18 or newer. Install the same @yellesoftware/meridian-ads package from the public npm registry. No token or .npmrc is required.
npm install @yellesoftware/meridian-adsESM
import Save this as server.mjs, or as server.js in a package that sets "type": "module". serve asks for one fill. recordImpression runs before the response, so the page never handles the impression token. HTTP 204 means no paid or house creative was eligible. On this single call, a house ad waits until no paid campaign can fill.
import http from "node:http"
import { MeridianAds } from "@yellesoftware/meridian-ads"
const ads = new MeridianAds({
appKey: process.env.MERIDIAN_ADS_APP_KEY,
secret: process.env.MERIDIAN_ADS_SECRET,
})
const server = http.createServer(async (req, res) => {
if (req.method !== "GET" || req.url !== "/api/ad") {
res.writeHead(404)
res.end()
return
}
const decision = await ads.serve("home", "user-42", {
plan: "pro",
country: "US",
})
if (!decision) {
res.writeHead(204)
res.end()
return
}
await ads.recordImpression(decision)
const creative = decision.creative
res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify({
house: decision.house,
campaign_id: decision.campaign_id,
format: creative.format,
image: creative.image ?? null,
native: creative.native ?? null,
click_url: creative.click_url,
}))
})
server.listen(3000)CommonJS
require Node loads .js files as CommonJS when the package omits "type": "module". require("@yellesoftware/meridian-ads") returns the same client.
const http = require("node:http")
const { MeridianAds } = require("@yellesoftware/meridian-ads")
const ads = new MeridianAds({
appKey: process.env.MERIDIAN_ADS_APP_KEY,
secret: process.env.MERIDIAN_ADS_SECRET,
})
const server = http.createServer(async (req, res) => {
if (req.method !== "GET" || req.url !== "/api/ad") {
res.writeHead(404)
res.end()
return
}
const decision = await ads.serve("home", "user-42", {
plan: "pro",
country: "US",
})
if (!decision) {
res.writeHead(204)
res.end()
return
}
await ads.recordImpression(decision)
const creative = decision.creative
res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify({
house: decision.house,
campaign_id: decision.campaign_id,
format: creative.format,
image: creative.image ?? null,
native: creative.native ?? null,
click_url: creative.click_url,
}))
})
server.listen(3000)Page behind your server
This page calls your server. It paints an image or a native headline and links click_url. It does not import the SDK and it does not hold the HMAC secret. For a site with no server, use the tag above.
<div id="ad-slot"></div>
<script>
async function mountAd() {
const slot = document.getElementById("ad-slot")
const res = await fetch("/api/ad")
if (res.status === 204) {
slot.replaceChildren()
return
}
const ad = await res.json()
const link = document.createElement("a")
link.href = ad.click_url
link.target = "_blank"
link.rel = "noopener noreferrer"
if (ad.format === "image" && ad.image && ad.image.cdn_url) {
const img = document.createElement("img")
img.src = ad.image.cdn_url
img.alt = ad.image.alt
link.append(img)
} else if (ad.native) {
link.textContent = ad.native.headline
}
slot.replaceChildren(link)
}
mountAd()
</script>Next
Typed server projects use the TypeScript SDK . Pass traits the way Users and context describes, and send clicks through Impressions and clicks . Signing for the server path is Authentication .