npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ceedhq/ads-web-sdk

v1.3.1

Published

Ceed Ads TypeScript SDK for integrating contextual ads into chat applications.

Readme

English | 日本語

Ceed Ads Web SDK

A TypeScript SDK for integrating contextual, in-chat ads into web applications.

⚠️ Language Support: Currently supports English and Japanese for ad decisioning and creatives. Additional languages coming soon.

Table of Contents


Installation

npm install @ceedhq/ads-web-sdk

Quick Start

import { initialize, showAd } from "@ceedhq/ads-web-sdk";

// 1. Initialize once on app load
initialize("your-app-id");

// 2. Show an ad after user message
await showAd({
  conversationId: "chat-123",
  messageId: crypto.randomUUID(),
  contextText: "I want to learn programming",
  targetElement: document.getElementById("ad-slot"),
});

API Reference

1. initialize(appId, apiBaseUrl?)

Required before any other SDK calls.

Sets up global configuration for all subsequent API requests.

initialize(appId: string, apiBaseUrl?: string): void

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | appId | string | Yes | Your application identifier | | apiBaseUrl | string | No | Override API URL (for development) |

Example:

// Production (uses default API)
initialize("my-app");

// Development (local API)
initialize("my-app", "/api");

2. requestAd(options)

Fetches an ad based on conversation context. Does NOT render anything.

Use this when you need full control over rendering.

async requestAd(options: {
  conversationId: string;
  messageId: string;
  contextText: string;
  userId?: string;
  formats?: AdFormat[];
}): Promise<{ ad: ResolvedAd | null; requestId: string | null }>

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | conversationId | string | Yes | Unique ID for the chat session | | messageId | string | Yes | Unique ID for the message | | contextText | string | Yes | User message text for keyword matching | | userId | string | No | Optional user identifier | | formats | AdFormat[] | No | Array of ad formats to request (defaults to all) |

Example:

const { ad, requestId } = await requestAd({
  conversationId: "chat-123",
  messageId: crypto.randomUUID(),
  contextText: "How do I book a flight?",
});

if (ad) {
  console.log(`Ad format: ${ad.format}`);
  console.log(`Advertiser: ${ad.advertiserName}`);
}

3. renderAd(ad, targetElement, requestId?)

Renders an ad into the DOM and automatically tracks impressions and clicks.

renderAd(
  ad: ResolvedAd,
  targetElement: HTMLElement,
  requestId?: string | null
): RenderedAd

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | ad | ResolvedAd | Yes | The ad object from requestAd() | | targetElement | HTMLElement | Yes | DOM element to render into | | requestId | string \| null | No | Request ID for event tracking |

Example:

const container = document.getElementById("ad-slot");
container.innerHTML = ""; // Clear previous ad

renderAd(ad, container, requestId);

4. showAd(options)

Convenience method that combines fetch + render + tracking in one call.

This is the simplest way to integrate ads.

async showAd(options: {
  conversationId: string;
  messageId: string;
  contextText: string;
  targetElement: HTMLElement;
  userId?: string;
  formats?: AdFormat[];
}): Promise<void>

Example:

await showAd({
  conversationId: "chat-123",
  messageId: crypto.randomUUID(),
  contextText: userMessage,
  targetElement: document.getElementById("ad-slot"),
});

Ad Formats

The SDK supports four ad formats, each designed for different use cases.

action_card

Default format — A card with title, description, and CTA button.

┌─────────────────────────────────┐
│ ● Advertiser Name          Ad   │
├─────────────────────────────────┤
│ Ad Title                        │
│ Ad description text goes        │
│ here with supporting details.   │
│                                 │
│ ┌─────────────────────────────┐ │
│ │       Call to Action        │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘

Use case: Standard promotional ads with a clear call-to-action.

Behavior:

  • Impression tracked on render
  • Click tracked when CTA button is clicked
  • Opens ctaUrl in a new tab

lead_gen

Lead generation format — Email capture form with success message.

┌─────────────────────────────────┐
│ ● Advertiser Name          Ad   │
├─────────────────────────────────┤
│ Get Our Free Guide              │
│ Enter your email to download    │
│ the complete tutorial.          │
│                                 │
│ ┌─────────────────────────────┐ │
│ │ Enter your email...         │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │        Subscribe            │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘

After submit:
┌─────────────────────────────────┐
│ ✓ Thanks! Check your inbox.     │
└─────────────────────────────────┘

Use case: Newsletter signups, lead capture, content downloads.

Behavior:

  • Impression tracked on render
  • Submit event tracked with email when form is submitted
  • Success message displayed after submission
  • Form input supports autocomplete attribute

Required config:

leadGenConfig: {
  placeholder: string;       // Input placeholder text
  submitButtonText: string;  // Button label
  autocompleteType: "email" | "name" | "tel" | "off";
  successMessage: string;    // Shown after submit
}

static

Display format — Similar to action_card, for page load targeting.

┌─────────────────────────────────┐
│ ● Advertiser Name          Ad   │
├─────────────────────────────────┤
│ Special Offer                   │
│ Limited time discount on        │
│ selected products.              │
│                                 │
│ ┌─────────────────────────────┐ │
│ │        Shop Now             │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘

Use case: Banner-style ads for sidebars, page headers, or footers.

Behavior:

  • Identical rendering to action_card
  • Different targeting logic on backend (keywords, geo, device type)
  • Impression tracked on render
  • Click tracked on CTA click

Optional config:

staticConfig: {
  displayPosition: "top" | "bottom" | "inline" | "sidebar";
  targetingParams?: {
    keywords?: string[];
    geo?: string[];
    deviceTypes?: ("desktop" | "mobile" | "tablet")[];
  }
}

followup

Sponsored question format — Tappable question card for conversation flow.

┌─────────────────────────────────┐
│ ● Advertiser Name          Ad   │
├─────────────────────────────────┤
│ Want to learn more about        │
│ our language courses?           │
│                                 │
│ → Tap to learn more             │
└─────────────────────────────────┘

Use case: Suggested follow-up questions sponsored by advertisers.

Behavior:

  • Entire card is tappable (not just a button)
  • Hover effect on card border
  • Click event tracked on tap
  • Tap action configurable:
    • redirect: Opens URL in new tab
    • expand: Host app handles expansion
    • submit: Host app handles submission

Required config:

followupConfig: {
  questionText: string;      // The sponsored question
  tapAction: "expand" | "redirect" | "submit";
  tapActionUrl?: string;     // Required if tapAction is "redirect"
}

Specifying Formats

Developers can use the formats parameter to specify which ad formats they want to receive.

Request Specific Formats Only

// Request only action_card and lead_gen formats
const { ad, requestId } = await requestAd({
  conversationId: "chat-123",
  messageId: crypto.randomUUID(),
  contextText: "I want to learn English",
  formats: ["action_card", "lead_gen"],
});

// Also works with showAd
await showAd({
  conversationId: "chat-123",
  messageId: crypto.randomUUID(),
  contextText: "I want to learn English",
  targetElement: document.getElementById("ad-slot"),
  formats: ["action_card", "lead_gen"],
});

Request All Formats (Default)

// If formats is not specified, all formats are eligible
const { ad, requestId } = await requestAd({
  conversationId: "chat-123",
  messageId: crypto.randomUUID(),
  contextText: "I want to learn English",
});

Available Format Values

| Value | Description | |-------|-------------| | "action_card" | Standard CTA card | | "lead_gen" | Email capture form | | "static" | Banner-style ad | | "followup" | Sponsored question |


Event Tracking

The SDK automatically tracks the following events:

| Event | Trigger | Description | |-------|---------|-------------| | impression | On render | Ad was displayed to user | | click | On CTA click | User clicked the CTA button | | submit | On form submit | User submitted lead_gen form |

Automatic deduplication: Impressions are deduplicated per ad+requestId pair to prevent duplicate tracking (e.g., React StrictMode double renders).

Manual tracking (advanced)

If you need manual control, import individual renderers:

import {
  renderActionCard,
  renderLeadGenCard,
  renderStaticCard,
  renderFollowupCard,
} from "@ceedhq/ads-web-sdk";

TypeScript Types

Core Types

import type {
  ResolvedAd,
  AdFormat,
  ResolvedLeadGenConfig,
  ResolvedFollowupConfig,
  StaticConfig,
} from "@ceedhq/ads-web-sdk";

ResolvedAd

The main ad payload returned from requestAd():

interface ResolvedAd {
  id: string;
  advertiserId: string;
  advertiserName: string;
  format: AdFormat;  // "action_card" | "lead_gen" | "static" | "followup"
  title: string;
  description: string;
  ctaText: string;
  ctaUrl: string;
  leadGenConfig?: ResolvedLeadGenConfig;
  staticConfig?: StaticConfig;
  followupConfig?: ResolvedFollowupConfig;
}

Format-specific configs

// Lead Gen
interface ResolvedLeadGenConfig {
  placeholder: string;
  submitButtonText: string;
  autocompleteType: "email" | "name" | "tel" | "off";
  successMessage: string;
}

// Static
interface StaticConfig {
  displayPosition: "top" | "bottom" | "inline" | "sidebar";
  targetingParams?: {
    keywords?: string[];
    geo?: string[];
    deviceTypes?: ("desktop" | "mobile" | "tablet")[];
  };
}

// Followup
interface ResolvedFollowupConfig {
  questionText: string;
  tapAction: "expand" | "redirect" | "submit";
  tapActionUrl?: string;
}

Error Handling

Initialization errors

try {
  initialize(""); // Empty appId
} catch (error) {
  // "CeedAds.initialize: appId is required"
}

No ad available

const { ad, requestId } = await requestAd({...});

if (!ad) {
  // No matching ad for this context
  // This is normal — ads are not always available
  return;
}

Format config missing

// If ad.format is "lead_gen" but leadGenConfig is missing:
renderAd(ad, container, requestId);
// Throws: "leadGenConfig is required for lead_gen format"

Examples

React Integration

import { useRef, useEffect } from "react";
import { initialize, requestAd, renderAd } from "@ceedhq/ads-web-sdk";
import type { ResolvedAd } from "@ceedhq/ads-web-sdk";

// Initialize once
initialize("your-app-id");

function ChatMessage({ message }: { message: string }) {
  const adRef = useRef<HTMLDivElement>(null);
  const [ad, setAd] = useState<ResolvedAd | null>(null);
  const [requestId, setRequestId] = useState<string | null>(null);

  useEffect(() => {
    async function fetchAd() {
      const result = await requestAd({
        conversationId: "chat-123",
        messageId: crypto.randomUUID(),
        contextText: message,
      });
      setAd(result.ad);
      setRequestId(result.requestId);
    }
    fetchAd();
  }, [message]);

  useEffect(() => {
    if (ad && adRef.current) {
      adRef.current.innerHTML = "";
      renderAd(ad, adRef.current, requestId);
    }
  }, [ad, requestId]);

  return (
    <div>
      <p>{message}</p>
      <div ref={adRef} />
    </div>
  );
}

Vanilla JavaScript

<div id="ad-container"></div>

<script type="module">
  import { initialize, showAd } from "@ceedhq/ads-web-sdk";

  initialize("demo-app");

  document.getElementById("send-btn").addEventListener("click", async () => {
    const message = document.getElementById("input").value;

    await showAd({
      conversationId: "demo-session",
      messageId: Date.now().toString(),
      contextText: message,
      targetElement: document.getElementById("ad-container"),
    });
  });
</script>

Local Development

Point the SDK to a local API server:

// Development mode
initialize("test-app", "http://localhost:3000/api");

// Or relative path (same origin)
initialize("test-app", "/api");

Demo Page

See the SDK in action:


Styling

All ad cards use a dark theme with these CSS classes:

| Class | Description | |-------|-------------| | .ceed-ads-card | Base card container | | .ceed-ads-action-card | Action Card format | | .ceed-ads-lead-gen | Lead Gen format | | .ceed-ads-static | Static format | | .ceed-ads-followup | Followup format |

Cards have a max-width of 460px and use inline styles for consistency.


License

MIT © Ceed