Adrop Ads Web SDK
Official Web SDK for Adrop advertising platform. Integrate direct deal ads seamlessly into your web applications.
Features
- 🚀 Easy integration with both npm and CDN
- 📱 Banner and Native ad formats
- 🎯 Direct deal advertising
- 🔒 Secure iframe-based ad rendering
- 📊 Built-in event tracking
- 🎨 Customizable native ad rendering
- ⚡ Lightweight and performant
- 🔧 TypeScript support
Installation
NPM
npm install @adrop/ads-web-sdk
CDN
<script src="https://cdn.jsdelivr.net/npm/@adrop/ads-web-sdk@latest/dist/adrop-ads.min.js"></script>
Quick Start
1. Initialize the SDK
import Adrop from '@adrop/ads-web-sdk';
const adrop = Adrop.observe({
appId: 'your-app-id',
uid: 'your-user-id', // Logged-in users only — see "User ID"
debug: true // Set to false in production
});
For CDN usage:
<script>
const adrop = Adrop.observe({
appId: 'your-app-id',
uid: 'your-user-id', // Logged-in users only — see "User ID"
debug: true
});
</script>
2. Load Banner Ads
Automatic Loading (Declarative)
<div data-adrop-unit="YOUR_UNIT_ID"></div>
The SDK automatically detects and loads ads in elements with data-adrop-unit attribute.
Optional data attributes:
data-adrop-context-id: Context ID for contextual targetingdata-adrop-keywords: Keywords for keyword targeting (comma-separated)data-adrop-theme: Theme for light/dark mode (values:light,dark)
<div
data-adrop-unit="YOUR_UNIT_ID"
data-adrop-context-id="CONTEXT_ID"
data-adrop-theme="dark">
</div>
Programmatic Loading
const container = document.getElementById('ad-container');
await adrop.renderAd(container, {
unit: 'YOUR_UNIT_ID'
});
The SDK automatically detects and renders native ads.
Optional field:
contextId: Context ID for contextual targetingkeywords: Keywords for keyword targeting (string[]or comma-separated string)theme: Theme for light/dark mode (values:light,dark)
const container = document.getElementById('ad-container');
await adrop.renderAd(container, {
unit: 'YOUR_UNIT_ID',
contextId: 'CONTEXT_ID',
theme: 'dark'
});
3. Load Native Ads
Native ads provide customizable ad experiences that blend seamlessly with your app's content. They require custom UI implementation using the data-adrop-native attributes.
Automatic Loading with Custom UI (Declarative)
To use custom UI, you must set data-adrop-render="custom":
<div data-adrop-unit="YOUR_NATIVE_UNIT_ID" data-adrop-render="custom">
<!-- Native ad components with data-adrop-native attributes -->
<div class="ad-profile">
<img data-adrop-native="profile.displayLogo" class="ad-profile-logo" alt="Profile">
<span data-adrop-native="profile.displayName" class="ad-profile-name"></span>
</div>
<h3 data-adrop-native="headline" class="ad-headline"></h3>
<img data-adrop-native="asset" class="ad-image" alt="Ad Image">
<p data-adrop-native="body" class="ad-description"></p>
<button data-adrop-native="callToAction" class="ad-cta-button"></button>
</div>
For entire area click functionality:
<div
data-adrop-unit="YOUR_NATIVE_UNIT_ID"
data-adrop-render="custom"
data-adrop-entire-click="true">
<!-- Entire container becomes clickable -->
<div class="ad-profile">
<img data-adrop-native="profile.displayLogo" class="ad-profile-logo" alt="Profile">
<span data-adrop-native="profile.displayName" class="ad-profile-name"></span>
</div>
<h3 data-adrop-native="headline" class="ad-headline"></h3>
<img data-adrop-native="asset" class="ad-image" alt="Ad Image">
<p data-adrop-native="body" class="ad-description"></p>
<span data-adrop-native="callToAction" class="ad-cta-text"></span>
</div>
Programmatic Loading with Custom UI
const container = document.getElementById('native-ad-container');
await adrop.renderAd(container, {
unit: 'YOUR_NATIVE_UNIT_ID',
trackMode: 1 // Required for custom UI
});
For entire area click functionality:
const container = document.getElementById('native-ad-container');
await adrop.renderAd(container, {
unit: 'YOUR_NATIVE_UNIT_ID',
trackMode: 1, // Required for custom UI
isEntireClick: true // Makes entire container clickable
});
Available data-adrop-native Fields
The SDK supports the following data-adrop-native attributes:
Basic Fields:
headline: Ad headline textbody: Ad description textcallToAction: Call-to-action button textasset: Main ad image URL
Profile Fields (for advertiser information):
profile.displayLogo: Advertiser logo image URLprofile.displayName: Advertiser name text
Extra Fields (custom properties):
extra.{key}: Custom field from ad data (e.g.,extra.price,extra.rating)
Media Slot Fields (units with multiple media):
extraAssets.{id}.asset: Media slot URL bound to an<img>(e.g.,extraAssets.m2.asset)extraAssets.{id}.cover: Video slot poster image URL (e.g.,extraAssets.m2.cover)
Custom UI Implementation Example
<div data-adrop-unit="YOUR_NATIVE_UNIT_ID" data-adrop-render="custom" class="native-ad-container">
<!-- Advertiser Profile -->
<div class="advertiser-info">
<img data-adrop-native="profile.displayLogo" class="advertiser-logo" alt="Advertiser">
<span data-adrop-native="profile.displayName" class="advertiser-name"></span>
</div>
<!-- Ad Content -->
<div class="ad-content">
<h2 data-adrop-native="headline" class="ad-title"></h2>
<img data-adrop-native="asset" class="ad-main-image" alt="Ad">
<p data-adrop-native="body" class="ad-text"></p>
<!-- Custom Fields -->
<div class="ad-extras">
<span data-adrop-native="extra.price" class="price-tag"></span>
<span data-adrop-native="extra.rating" class="rating"></span>
</div>
<button data-adrop-native="callToAction" class="cta-button"></button>
</div>
</div>
Media Slots (extraAssets)
When a native unit defines media slots, adData.extraAssets (in the adReceived / adImpression / adClicked callbacks and in requestAd() / requestAds() results) holds every slot keyed by slot ID — { id, asset, ad, cover?, type?, isVideo }. asset is the media URL, ad is the rendered HTML for that slot (same meaning as adData.ad), cover is the poster image of a video slot, type is the creative medium reported by the server ('display' | 'video'; absent on older responses), and isVideo is true when type is 'video' — or, when type is absent, when cover is present or asset ends with .mp4 / .m3u8. The first slot is the same media as adData.asset / adData.ad. The field is absent for single-media creatives (no slots) and ads without media — read the primary media from adData.asset / adData.ad.
adrop.on(Adrop.Events.AD_RECEIVED, (unit, adData) => {
const slot = adData.extraAssets?.m2;
if (slot?.isVideo) {
console.log(slot.asset, slot.cover); // video URL, poster URL
}
});
Click Behavior Control
You can control how clicks are handled in native ads:
Individual Element Clicks (default):
- Only elements with
data-adrop-nativeattributes are clickable - Each text and image element handles its own click
Entire Area Clicks:
- Set
data-adrop-entire-click="true"(declarative) orisEntireClick: true(programmatic) - The entire ad container becomes clickable
- Useful for card-style layouts where any part of the ad should be clickable
Important Notes:
- Custom UI requires
data-adrop-render="custom"ortrackMode: 1 - Click behavior:
isEntireClick: truemakes entire area clickable, otherwise only individual elements - The SDK automatically handles click tracking and impression tracking
- Elements with
data-adrop-nativewill be automatically populated with ad content - Missing fields will be left empty (no error thrown)
- Profile clicks redirect to advertiser pages, other clicks go to ad landing pages
4. Event Handling
Listen to ad lifecycle events:
// Ad received successfully
adrop.on(Adrop.Events.AD_RECEIVED, (unit, adData) => {
console.log('Ad received:', unit, adData);
});
// Ad failed to load
adrop.on(Adrop.Events.AD_FAILED, (unit) => {
console.error('Ad failed:', unit);
});
// No ad available
adrop.on(Adrop.Events.AD_NO_FILL, (unit) => {
console.warn('No ad available:', unit);
});
// No backfill ad available
adrop.on(Adrop.Events.AD_BACKFILL_NO_FILL, (unit) => {
console.warn('No backfill ad available:', unit);
});
// Ad impression
adrop.on(Adrop.Events.AD_IMPRESSION, (unit, adData) => {
console.log('Ad impression:', unit, adData);
});
// Ad clicked
adrop.on(Adrop.Events.AD_CLICKED, (unit, adData) => {
console.log('Ad clicked:', unit, adData);
});
Advanced Usage
Update Properties
Properties can be chained and committed together in a single call:
// Set app key
adrop.appKey = 'YOUR_APP_KEY';
// Chain multiple property updates and commit once
await adrop.metrics
.setUserProperties({
last_login: new Date().toISOString(),
age: '25',
gender: 'M',
// Custom properties can be added directly
theme: 'dark',
language: 'en'
})
.setAppProperties({
version: '1.0.0',
platform: 'web'
})
.commit();
Verifying Live Ads on localhost
Pages served from localhost receive test ads by default. To verify real ad delivery before deploying,
set liveAds: true together with your appKey. The SDK then authenticates with the app key, just like the native SDKs.
const adrop = Adrop.observe({
appId: 'your-app-id',
appKey: 'your-app-key',
liveAds: true
});
- Impressions and clicks are counted exactly as in production and billed to advertisers. Turn it off when you are done, or register your IP as a tester in the console to receive test campaigns instead.
- Only takes effect on pages served over
http(s)://fromlocalhost,*.localhost,127.x.x.xand[::1]. On a deployed domain it does nothing. - Hybrid apps must turn it off in release builds. Capacitor, Ionic, Cordova and Tauri serve even the shipped app from
localhost. The SDK only filters the custom schemes used on iOS, macOS and Linux (capacitor://,ionic://,tauri://); Android and Windows builds (http(s)://localhost,http(s)://tauri.localhost) look exactly like a local dev server. If the setting is left on, production ad requests switch to the app-key path (with a CORS preflight on every request). - Because a hybrid app's origin is
localhost, the server treats it as test mode and serves test ads only, even withliveAdsoff. The Web SDK does not support production ad delivery in hybrid apps. - A missing
appKey, or a key that belongs to another app, logs a warning in the browser console. With another app's key the server answers "no ad". - App- and unit-level request gating (remote-config) is not applied on localhost.
Context Targeting
Use contextId to enable contextual targeting for more relevant ads:
await adrop.renderAd(container, {
unit: 'YOUR_UNIT_ID',
contextId: 'YOUR_CONTEXT_ID' // Context ID from Adrop Console
});
The contextId should match the ID configured in your Adrop Console under Targeting Menu > Context Targeting.
Theme Support
The SDK supports light and dark themes for ads. You can set the theme using the data-adrop-theme attribute or the theme option in renderAd.
Load Backfill Ads
Backfill ads are automatically loaded when direct deal ads are not available.
Note: Backfill configuration must be set up in the Adrop Console for this to work.
When the app has no active campaigns
If the Adrop Console reports no active campaigns for your app (remote-config active: false), the SDK skips the ad request locally until the server-provided recheck time: requestAd / requestAds return code: 4001 without a network call, and load() falls through to backfill and fires adNoFill (the same event as a no-fill from the server, so a page that attaches its own fallback on adNoFill keeps working). The state is kept in localStorage['adrop:{appId}:backoff'] (no identifiers) so it survives page loads. Preview requests (adId) are never skipped.
When a specific ad unit has nothing to fill
When the app is active but that unit has no ads at all, the server attaches a retry time to the no-fill response. The SDK then skips requests for that unit only until that time, returning code: 4002 (no-fill); load() falls through to backfill and fires adNoFill as usual. Other units are unaffected, and the skip is lifted as soon as an ad fills. It lasts at most 24 hours, and is lifted sooner when a campaign is added in the console.
The skip is scoped to the unit and contextId pair. Contextual targeting can change what is available for the same unit, so each contextId is tracked separately and a no-fill on one screen never blocks another.
Backfill rendering mode (1.2.3+)
| Mode | Behavior |
|---|---|
responsive (default) | The backfill provider picks a size based on container width. SDK reserves a min-height automatically when the container has no explicit height. |
fixed | Stretches the ad to the container height (width:100%; height:100%) — 1.2.1 behavior. Use for slots designed around a fixed height. |
// Global default
Adrop.observe({ appId: 'YOUR_APP_ID', backfillMode: 'fixed' });
// Per-slot (HTML)
// <div data-adrop-unit="..." data-adrop-backfill-mode="fixed"></div>
// Per-slot (programmatic)
await adrop.renderAd(container, { unit: 'PUBLIC_xxx', backfillMode: 'fixed' });
See Backfill rendering mode guide for details.
Clear Ads
Remove ads from containers:
const container = document.getElementById('ad-container');
adrop.clear(container);
Reload Ads
In HTML, you can reload ads by calling:
const container = document.getElementById('ad-container');
const paramsToOverride = {
unit: 'NEW_UNIT_ID',
contextId: 'NEW_CONTEXT_ID',
theme: 'light'
}
adrop.renderAd(container, paramsToOverride);
// if you don't want to override previous params, just call without second argument
adrop.renderAd(container);
User ID
If you don't set uid, the SDK creates an anonymous ID (ULID) for the browser and sends it with ad requests, events and property updates — so frequency caps and audience targeting also apply to logged-out visitors. The ID is scoped to your appId and stored in one place:
| Storage | Name | Purpose | Lifetime |
|---|---|---|---|
| First-party cookie on the registrable domain (shared across subdomains) | adrop_uid_{appId} | Anonymous user ID | 400 days, renewed on every page |
localStorage — only when the cookie can't be written | adrop:{appId}:uid | Anonymous user ID | Until cleared |
| Cookie (test) | adrop_probe | Finds the registrable domain for the cookie above — random value, deleted right after writing | Deleted immediately (60 s cap) |
- Set
uidfor logged-in users on every page (Adrop.observe({ uid }),setConfig({ uid })oradrop.uid = ...). Setting it deletes the anonymous ID, and the next page withoutuid(after logout) starts a new one. A logged-in page that omitsuidgets a fresh anonymous ID, which weakens frequency caps on that page. request.uidoverrides the ID for that request only; events and property updates keep using the configured or anonymous ID.adrop.uidreturns only the value you set, never the anonymous ID.- Frequency caps hold while browser storage is kept. If both cookies and
localStorageare blocked, the ID lasts for the current page only — ads still load. No ID is created outside the browser (SSR). appIdis required to keep the anonymous ID across pages; without it the ID is kept in memory only.- For anonymous visitors,
metrics.commit()sends properties only whileuserDataConsentistrue.
Update User ID
adrop.uid = 'new-user-id';
Configuration Options
Initialization Options
{
appId: string; // Application ID (required)
uid?: string; // Logged-in user ID — omit for an anonymous ID (see "User ID")
appKey?: string; // Application key
liveAds?: boolean; // Serve live ads on localhost when appKey is set (default: unset = test ads)
debug?: boolean; // Enable debug logging (default: false)
}
Ad Request Options
{
unit: string; // Ad unit ID (required)
uid?: string; // Override user ID for this request
contextId?: string; // Context identifier
keywords?: string[] | string; // Keyword targeting (array or comma-separated string)
trackMode?: number; // Tracking mode
}
Events
AD_RECEIVED- Ad successfully loadedAD_FAILED- Ad failed to loadAD_NO_FILL- No ad available for this requestAD_BACKFILL_NO_FILL- No backfill ad availableAD_IMPRESSION- Ad impression recordedAD_CLICKED- User clicked on the ad
TypeScript Support
The SDK includes TypeScript definitions out of the box:
import Adrop, { AdropConfig, AdData } from '@adrop/ads-web-sdk';
const config: AdropConfig = {
appId: 'your-app-id',
uid: 'user-123',
debug: true
};
const adrop = Adrop.observe(config);
SPA Support
For Single Page Applications, clear previous ads when navigating:
function navigateToNewPage() {
// Clear existing ads
const adElements = document.querySelectorAll('[data-adrop-unit]');
adElements.forEach(element => adrop.clear(element));
// Load new page content
loadNewPageContent();
}
License
Copyright 2025 Open Rhapsody Co., Ltd.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.