> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adrop.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Native Ads

> Learn how to implement native ads in React.

## Implementation Methods

Native ads allow you to display ads with custom UI that matches your app's design.

| Method           | Description               | Recommended                         |
| ---------------- | ------------------------- | ----------------------------------- |
| Default Template | Use SDK-provided template | Quick implementation                |
| Custom UI        | Build your own UI         | When design customization is needed |

<Note>
  Use the test unit ID in development: `PUBLIC_TEST_UNIT_ID_NATIVE`
</Note>

***

## Default Template

You can use the default template in the same way as banner ads. Images and text are automatically rendered in the SDK-provided template.

```tsx theme={null}
function NativeAd() {
  return <div data-adrop-unit="YOUR_NATIVE_UNIT_ID" />;
}
```

<Note>
  The default template does not support advertiser profile sections. Use custom UI to display advertiser profiles.
</Note>

***

## Custom UI

### 1. Data Attributes Method

Add the `data-adrop-render="custom"` attribute and bind fields using `data-adrop-native` attributes on inner elements.

```tsx theme={null}
function CustomNativeAd() {
  return (
    <div
      data-adrop-unit="YOUR_NATIVE_UNIT_ID"
      data-adrop-render="custom"
      data-adrop-entire-click="true"
      data-adrop-custom-click="true"  // Enable custom click handling
      className="native-ad"
    >
      {/* Advertiser Profile */}
      <div className="ad-profile">
        <img data-adrop-native="profile.displayLogo" alt="Advertiser" />
        <span data-adrop-native="profile.displayName"></span>
      </div>

      {/* Ad Content */}
      <h3 data-adrop-native="headline"></h3>
      <img data-adrop-native="asset" alt="Ad" />
      <p data-adrop-native="body"></p>

      {/* Extra text items */}
      <span data-adrop-native="extra.price"></span>

      {/* CTA Button */}
      <button data-adrop-native="callToAction"></button>
    </div>
  );
}
```

#### Data Attributes

<ParamField body="data-adrop-unit" type="string" required>
  Unit ID created in Ad Control Console
</ParamField>

<ParamField body="data-adrop-render" type="string" required>
  Set to `custom` for custom UI
</ParamField>

<ParamField body="data-adrop-context-id" type="string">
  Context ID for contextual targeting
</ParamField>

<ParamField body="data-adrop-theme" type="string" default="light">
  Theme setting (`light` or `dark`)
</ParamField>

<ParamField body="data-adrop-entire-click" type="boolean" default={false}>
  Enable entire area click

  * `false`: Only text/images are clickable, clicking advertiser profile goes to profile link
  * `true`: Entire container is clickable, all clicks go to ad destination
</ParamField>

<ParamField body="data-adrop-custom-click" type="boolean" default={false}>
  Enable custom click handling - When `true`, SDK will not automatically navigate to the destination URL on click, only emit `adClicked` event with destination URL. Allows developers to implement custom click behavior.
</ParamField>

<ParamField body="data-adrop-ad-id" type="string">
  Advertising identifier — required for the **View on Device** feature to preview a draft creative on a registered test device. Do not hard-code in production.
</ParamField>

### 2. renderAd Method

Use this when you want direct control over ad loading timing.

```tsx theme={null}
import { useEffect, useRef } from 'react';
import { Adrop } from '@adrop/ads-web-sdk';

function CustomNativeAd() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const adrop = Adrop.instance();

    if (containerRef.current) {
      adrop.renderAd(containerRef.current, {
        unit: 'YOUR_NATIVE_UNIT_ID',
        contextId: 'YOUR_CONTEXT_ID',
        theme: 'light',
        trackMode: 1,        // Required for custom UI
        isEntireClick: true, // Enable entire area click
        useCustomClick: true // Enable custom click handling
      });
    }

  }, []);

  return (
    <div ref={containerRef} className="native-ad">
      <div className="ad-profile">
        <img data-adrop-native="profile.displayLogo" alt="Advertiser" />
        <span data-adrop-native="profile.displayName"></span>
      </div>
      <h3 data-adrop-native="headline"></h3>
      <img data-adrop-native="asset" alt="Ad" />
      <p data-adrop-native="body"></p>
      <button data-adrop-native="callToAction"></button>
    </div>
  );
}
```

#### renderAd Options

<ParamField body="unit" type="string" required>
  Unit ID created in Ad Control Console
</ParamField>

<ParamField body="uid" type="string">
  User identifier (used to override SDK-level setting for individual placements)
</ParamField>

<ParamField body="contextId" type="string">
  Context ID for contextual targeting
</ParamField>

<ParamField body="trackMode" type="number" required>
  Set to `1` for custom UI
</ParamField>

<ParamField body="isEntireClick" type="boolean" default={false}>
  Enable entire area click
</ParamField>

<ParamField body="theme" type="string" default="light">
  Theme setting (`light` or `dark`)
</ParamField>

<ParamField body="useCustomClick" type="boolean" default={false}>
  Enable custom click handling - When `true`, SDK will not automatically navigate to the destination URL on click, only emit `adClicked` event with destination URL. Allows developers to implement custom click behavior.
</ParamField>

<ParamField body="adId" type="string">
  Advertising identifier — required for the **View on Device** feature to preview a draft creative on a registered test device. Do not hard-code in production.
</ParamField>

***

## Field Binding

Bind ad data to UI elements using the `data-adrop-native` attribute.

| Field                 | Description         | Element Type |
| --------------------- | ------------------- | ------------ |
| `profile.displayLogo` | Advertiser logo URL | `<img>`      |
| `profile.displayName` | Advertiser name     | Text         |
| `headline`            | Ad title            | Text         |
| `body`                | Ad description      | Text         |
| `asset`               | Main image URL      | `<img>`      |
| `callToAction`        | CTA button text     | Text         |
| `extra.{key}`         | Extra text items    | Text         |

<Note>
  `extra.{key}` is the ID of extra text items set in Ad Control Console. It may be empty if not set as required.
</Note>

<Note>
  To display advertiser profile (`profile.displayLogo`, `profile.displayName`), you must enable <strong>Show Advertiser Profile</strong> for that ad unit in Ad Control Console.
</Note>

***

## Ad Size

Custom UI size can be set freely.

| Type         | Size Setting                            | Alignment                  |
| ------------ | --------------------------------------- | -------------------------- |
| Direct Ads   | Responsive support                      | Horizontal/Vertical center |
| Backfill Ads | Initial width matching, height required | Horizontal center          |

<Note>
  Backfill ads request creatives matching the container size. Specify an appropriate size.
</Note>

***

## Fetching Data Only with requestAd

If you want complete control over the UI, you can fetch only the ad data using `requestAd`.

```tsx theme={null}
import { useState, useEffect } from 'react';
import { Adrop } from '@adrop/ads-web-sdk';

function NativeAd() {
  const [adData, setAdData] = useState<AdData | null>(null);

  useEffect(() => {
    async function loadAd() {
      const adrop = Adrop.instance();

      const response = await adrop.requestAd({
        unit: 'YOUR_NATIVE_UNIT_ID',
        trackMode: 1
      });

      if (response.code === 0 && response.result) {
        setAdData(response.result);
      }
    }

    loadAd();
  }, []);

  if (!adData) return null;

  return (
    <div className="native-ad">
      <div className="ad-header">
        {adData.profile?.displayLogo && (
          <img src={adData.profile.displayLogo} alt="Advertiser" />
        )}
        <span>{adData.profile?.displayName}</span>
      </div>
      <h3>{adData.headline}</h3>
      {adData.asset && <img src={adData.asset} alt="Ad" />}
      <p>{adData.body}</p>
      <a href={adData.destinationURL} target="_blank" rel="noopener noreferrer">
        {adData.callToAction}
      </a>
    </div>
  );
}
```

<Warning>
  When using `requestAd`, you must implement impression/click tracking yourself. Also, backfill ads are not supported with this method. We recommend using `renderAd` unless there's a special case.
</Warning>

### AdropAdResponse

Return value of `requestAd()`.

| Field    | Type     | Description                                  |
| -------- | -------- | -------------------------------------------- |
| `code`   | `number` | Response code (0: success, 4000-4003: error) |
| `msg`    | `string` | Response message                             |
| `result` | `AdData` | Ad data (only present on success)            |

### AdData

Ad data object. Event handlers also receive the same structure. See [Reference > AdData](/sdk/web/reference#addata) for details.

| Field                 | Type                     | Description                                  |
| --------------------- | ------------------------ | -------------------------------------------- |
| `format`              | `string`                 | Ad format (`banner`, `nativeAd`, `backfill`) |
| `unit`                | `string`                 | Unit ID                                      |
| `headline`            | `string`                 | Ad title                                     |
| `body`                | `string`                 | Ad description                               |
| `callToAction`        | `string`                 | CTA button text                              |
| `asset`               | `string`                 | Main image URL                               |
| `destinationURL`      | `string`                 | Click destination URL                        |
| `profile.displayLogo` | `string`                 | Advertiser logo URL                          |
| `profile.displayName` | `string`                 | Advertiser name                              |
| `profile.link`        | `string`                 | Advertiser profile link                      |
| `extra`               | `Record<string, string>` | Extra text items                             |

***

## Custom Click Handling

When `useCustomClick` is enabled, the SDK will not automatically navigate to the destination URL. Instead, you can handle the click event and implement custom behavior.

```tsx theme={null}
import { useEffect, useRef } from 'react';
import { Adrop } from '@adrop/ads-web-sdk';

function CustomClickNativeAd({ unitId }: { unitId: string }) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const adrop = Adrop.instance();
    const filter = { unit: unitId };

    // Handle ad click with custom behavior
    const handleClicked = (unit: string, adData: any) => {
      console.log('Ad clicked:', unit);
      console.log('Destination URL:', adData.destinationURL);
      
      // Implement custom click behavior
      // For example: navigate within SPA, show modal, track analytics, etc.
      if (adData.destinationURL) {
        // Custom navigation logic
        window.location.href = adData.destinationURL;
      }
    };

    adrop.on(Adrop.Events.AD_CLICKED, handleClicked, filter);

    if (containerRef.current) {
      adrop.renderAd(containerRef.current, {
        unit: unitId,
        trackMode: 1,
        isEntireClick: true,
        useCustomClick: true
      });
    }

    return () => {
      adrop.off(Adrop.Events.AD_CLICKED, handleClicked);
    };
  }, [unitId]);

  return (
    <div ref={containerRef} className="native-ad">
      <div className="ad-profile">
        <img data-adrop-native="profile.displayLogo" alt="Advertiser" />
        <span data-adrop-native="profile.displayName"></span>
      </div>
      <h3 data-adrop-native="headline"></h3>
      <img data-adrop-native="asset" alt="Ad" />
      <p data-adrop-native="body"></p>
      <button data-adrop-native="callToAction"></button>
    </div>
  );
}
```

<Note>
  With custom click handling enabled, you have full control over what happens when users click the ad. The `destinationURL` is provided in the `adClicked` event data.
</Note>

***

## Event Handling

```tsx theme={null}
import { useEffect, useRef } from 'react';
import { Adrop } from '@adrop/ads-web-sdk';

function NativeAd({ unitId }: { unitId: string }) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const adrop = Adrop.instance();
    const filter = { unit: unitId };

    const handleReceived = (unit: string, adData: any) => {
      console.log('Ad received:', adData);
    };

    const handleNoFill = (unit: string) => {
      console.warn('No ad available:', unit);
    };

    const handleFailed = (unit: string) => {
      console.error('Ad failed:', unit);
    };

    const handleImpression = (unit: string, adData: any) => {
      console.log('Ad impression:', unit);
    };

    const handleClicked = (unit: string, adData: any) => {
      console.log('Ad clicked:', unit);
    };

    // Video playback started
    const handleVideoStart = (unit: string, adData: any) => {
      console.log('Video started:', unit);
    };

    // Video playback ended
    const handleVideoEnd = (unit: string, adData: any) => {
      console.log('Video ended:', unit);
    };

    adrop.on(Adrop.Events.AD_RECEIVED, handleReceived, filter);
    adrop.on(Adrop.Events.AD_NO_FILL, handleNoFill, filter);
    adrop.on(Adrop.Events.AD_FAILED, handleFailed, filter);
    adrop.on(Adrop.Events.AD_IMPRESSION, handleImpression, filter);
    adrop.on(Adrop.Events.AD_CLICKED, handleClicked, filter);
    adrop.on(Adrop.Events.AD_VIDEO_START, handleVideoStart, filter);
    adrop.on(Adrop.Events.AD_VIDEO_END, handleVideoEnd, filter);

    if (containerRef.current) {
      adrop.renderAd(containerRef.current, {
        unit: unitId,
        trackMode: 1,
        isEntireClick: true
      });
    }

    return () => {
      adrop.off(Adrop.Events.AD_RECEIVED, handleReceived);
      adrop.off(Adrop.Events.AD_NO_FILL, handleNoFill);
      adrop.off(Adrop.Events.AD_FAILED, handleFailed);
      adrop.off(Adrop.Events.AD_IMPRESSION, handleImpression);
      adrop.off(Adrop.Events.AD_CLICKED, handleClicked);
      adrop.off(Adrop.Events.AD_VIDEO_START, handleVideoStart);
      adrop.off(Adrop.Events.AD_VIDEO_END, handleVideoEnd);
    };
  }, [unitId]);

  return (
    <div ref={containerRef} className="native-ad">
      <div className="ad-profile">
        <img data-adrop-native="profile.displayLogo" alt="Advertiser" />
        <span data-adrop-native="profile.displayName"></span>
      </div>
      <h3 data-adrop-native="headline"></h3>
      <img data-adrop-native="asset" alt="Ad" />
      <p data-adrop-native="body"></p>
      <button data-adrop-native="callToAction"></button>
    </div>
  );
}
```

### Supported Events

| Event          | Constant              | Description               |
| -------------- | --------------------- | ------------------------- |
| Ad Received    | `AD_RECEIVED`         | Ad request successful     |
| No Ad          | `AD_NO_FILL`          | No direct ads available   |
| Request Failed | `AD_FAILED`           | Ad request failed         |
| Impression     | `AD_IMPRESSION`       | Ad impression recorded    |
| Click          | `AD_CLICKED`          | User clicked the ad       |
| No Backfill    | `AD_BACKFILL_NO_FILL` | No backfill ad available  |
| Video Start    | `AD_VIDEO_START`      | Video ad playback started |
| Video End      | `AD_VIDEO_END`        | Video ad playback ended   |

***

## Backfill Ads

When backfill ads are enabled, they are automatically requested when no direct ads are available. You can distinguish ad types using `adData.format`.

```tsx theme={null}
import { useEffect, useRef } from 'react';
import { Adrop } from '@adrop/ads-web-sdk';

function NativeAd({ unitId }: { unitId: string }) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const adrop = Adrop.instance();
    const filter = { unit: unitId };

    // Ad received (direct or backfill)
    const handleReceived = (unit: string, adData: any) => {
      if (adData.format === 'backfill') {
        console.log('Backfill ad loaded');
      } else {
        console.log('Direct ad loaded'); // 'nativeAd'
      }
    };

    // No direct ad (backfill request starts)
    const handleNoFill = (unit: string) => {
      console.log('No direct ad available, requesting backfill...');
    };

    // No backfill ad either
    const handleBackfillNoFill = (unit: string) => {
      console.warn('No backfill ad available either');
      // Hide ad area, etc.
    };

    adrop.on(Adrop.Events.AD_RECEIVED, handleReceived, filter);
    adrop.on(Adrop.Events.AD_NO_FILL, handleNoFill, filter);
    adrop.on(Adrop.Events.AD_BACKFILL_NO_FILL, handleBackfillNoFill, filter);

    if (containerRef.current) {
      adrop.renderAd(containerRef.current, { unit: unitId });
    }

    return () => {
      adrop.off(Adrop.Events.AD_RECEIVED, handleReceived);
      adrop.off(Adrop.Events.AD_NO_FILL, handleNoFill);
      adrop.off(Adrop.Events.AD_BACKFILL_NO_FILL, handleBackfillNoFill);
    };
  }, [unitId]);

  return (
    <div ref={containerRef}>
      <h3 data-adrop-native="headline"></h3>
      <img data-adrop-native="asset" alt="Ad" />
      <p data-adrop-native="body"></p>
      <button data-adrop-native="callToAction"></button>
    </div>
  );
}
```

<Note>
  **Development Environment Testing**: If at least one ad unit connected to the app is **Monetizing**, backfill ad requests can be made from localhost. On unapproved domains, an `AD_BACKFILL_NO_FILL` event will be triggered.
</Note>
