> For the complete documentation index, see [llms.txt](https://docs.birdie.so/birdie-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.birdie.so/birdie-docs/request-screen-recordings/installation/snippet.md).

# Snippet

Install the Birdie snippet to attach console logs and network requests to every recording, and to mask sensitive data.

The Birdie snippet enables log and network request capture during recordings. It also powers sensitive data masking. Install it on your product pages: it sleeps until a recording starts and won't affect your app's performance at rest.

{% hint style="info" %}
Birdie Snippet **is required** for [capturing logs](/birdie-docs/request-screen-recordings/devtools-console-logs-and-network-requests.md).\
Birdie Snippet **is required** if you want to [hide sensitive data](/birdie-docs/security/hide-sensitive-data.md).
{% endhint %}

{% hint style="warning" %}
**Your product runs on several separate bare domains**, like `acme.com` and `acme.io`? This requires a different setup, enabled by Birdie first. See [Multiple Domains Setup](/birdie-docs/request-screen-recordings/installation/snippet/multiple-domains-setup.md).
{% endhint %}

Your product and your recorder page must share the same **bare domain**: your domain without any subdomain, like `acme.com` for `app.acme.com`. Any subdomain works, on either side.

You enter it in [Settings → Snippet & logs](https://app.birdie.so/settings/logs), under **What domain does your product run on?**

<figure><img src="https://3843507234-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FECPgvsGwy5oMz4WIZw3a%2Fuploads%2FxjvbGiDAjQf3pJ70ePWQ%2FSlice%20406.png?alt=media&amp;token=28b46475-33d5-48c2-9370-49975a16b33f" alt=""><figcaption></figcaption></figure>

<details>

<summary>Examples</summary>

**Works**, same bare domain:

* Product on `app.acme.com`, recorder page on `capture.acme.com`
* Product on `acme.com`, recorder page on `help.acme.com`
* Product on `shop.eu.acme.com`, recorder page on `record.acme.com`
* Product on `app.acme.co.uk`, recorder page on `record.acme.co.uk` (the bare domain is `acme.co.uk`)

**Doesn't work**, different bare domains:

* Product on `app.acme.com`, recorder page on `record.acme.io`
* Product on `app.acme.com`, recorder page on `acme-support.com`

</details>

<details>

<summary>How it works</summary>

The snippet is dormant until a recording starts. When triggered, the recorder communicates with the snippets via a browser-native local messaging API. Because this local messaging channel is scoped to the same site, the recorder page must share the same bare domain as your app pages (subdomains are fine). That's why the recorder needs a page on your domain.

</details>

## Install the snippet

Add the snippet to every page of your product, with npm or yarn, or by pasting it in the `<head>`.

{% tabs %}
{% tab title="npm or yarn" %}
{% code title="Install" %}

```bash
npm install @birdie-so/snippet
# or
yarn add @birdie-so/snippet
```

{% endcode %}

Then initialize it with your client ID, from [Settings → Snippet & logs](https://app.birdie.so/settings/logs):

{% code title="Initialize" %}

```javascript
import { initBirdie } from "@birdie-so/snippet";

initBirdie({ clientId: "YOUR_CLIENT_ID" });
```

{% endcode %}

More on the package: [@birdie-so/snippet on npm](https://www.npmjs.com/package/@birdie-so/snippet?activeTab=readme).
{% endtab %}

{% tab title="Paste in the <head>" %}
In [Settings → Snippet & logs](https://app.birdie.so/settings/logs), open **Install options** and paste the snippet in the `<head>` of every page of your product. It already contains your client ID and is unique to your organization. You can also click **Send to developer** to email it.

{% hint style="info" %}
We do not recommend installing Birdie through Google Tag Manager or Segment. Pasting the code directly in your web app loads faster.
{% endhint %}
{% endtab %}
{% endtabs %}

### Identify your users (optional)

Pass the email of the logged-in user, so recordings show who recorded. Your client ID is in [Settings → Snippet & logs](https://app.birdie.so/settings/logs).

{% tabs %}
{% tab title="npm or yarn" %}
{% code title="Initialize with the user's email" %}

```javascript
initBirdie({
  clientId: "YOUR_CLIENT_ID",
  contact: {
    email: "alex@empire.com",
    name: "Alexander", // optional
    id: 65523          // optional
  },
});
```

{% endcode %}

If you only know the user later, set the email once Birdie is ready:

{% code title="Update the email later" %}

```javascript
birdie.update({ contact: { email: "alex@empire.com" } })
```

{% endcode %}
{% endtab %}

{% tab title="Paste in the <head>" %}
Set `window.birdieSettings` before the snippet loads:

{% code title="Before the snippet" %}

```javascript
window.birdieSettings = {
  contact_email: "alex@empire.com",
};
// then add the snippet…
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Allow Birdie in your Content Security Policy

The snippet loads from two hosts and talks to three. Allow exactly these:

```
script-src   https://cf.birdie.so/widget/embed/ https://cdn.birdie.so/widget/
connect-src  https://app.birdie.so https://cf.birdie.so https://cdn.birdie.so/widget/ wss://sock.birdie.so https://sock.birdie.so
frame-src    https://app.birdie.so
img-src      data:
```

If you use **sensitive-data masking** (elements marked as hidden are masked with a stylesheet and a font), also allow:

```
style-src    https://cdn.birdie.so/widget/
font-src     https://cdn.birdie.so/widget/
```

The snippet needs no inline scripts, inline styles, or workers. The two `script-src` entries are deliberately path-scoped: `cdn.birdie.so` serves only Birdie's static build output, and `cf.birdie.so` serves only the widget under `/widget/`.

{% hint style="info" %}
If you prefer a single entry per directive, `https://*.birdie.so` and `wss://*.birdie.so` also work. The list above is the minimum.
{% endhint %}

If your pages send `Cross-Origin-Opener-Policy: noopener-allow-popups`, the recorder opened in a new window can't reach `window.opener`, and popup-to-opener communication fails. Don't isolate the opened page from its opener.

***

## Set up the recorder page

Birdie displays the recorder on a page on your product's bare domain, so it can reach the snippet running there. In [Settings → Snippet & logs](https://app.birdie.so/settings/logs), under **Set up the recorder page**, pick one way:

{% tabs %}
{% tab title="Connect custom domain (recommended)" %}
Birdie hosts the recorder page for you on a subdomain you choose, for example `https://capture.acme.com/record`, and points your request links at it. It's blank, unauthenticated, runs your snippet and is hidden from search engines. No code, nothing to keep in sync.

1. Click **Connect custom domain** and choose a subdomain of your bare domain, for example `capture.acme.com`.
2. Complete the custom domain setup. If you need help, follow [Custom Domain Setup](/birdie-docs/request-screen-recordings/installation/custom-domain-setup.md).

{% hint style="info" %}
Using [custom storage](https://docs.birdie.so/birdie-docs/security/storage-location#bring-your-own-storage-byos)? You must also add your custom domain, for example `https://capture.acme.com`, to your bucket's CORS configuration. [See more](https://docs.birdie.so/birdie-docs/request-screen-recordings/installation/custom-domain-setup#custom-storage-buckets-and-cors-important).
{% endhint %}
{% endtab %}

{% tab title="Use your own page" %}
Create a page on your bare domain, for example `https://app.acme.com/recorder`, that is:

* **blank**: no layout, menu or content. The recorder appears on top of it.
* **unauthenticated**: your customers open it from a request link, often while not signed in.
* **running the Birdie snippet**: the same one as in your product. Without it, the page can't become the recorder.
* **free of redirects**: no auth redirect, no locale redirect, no trailing-slash rewrite.

Birdie turns the page into the recorder by adding `?birdie-recording=…` to its URL. A redirect that drops the query string takes that parameter with it: the recorder never appears and no logs are captured.

{% hint style="danger" %}
If your stack must redirect, forward the **full query string** to the destination.
{% endhint %}

Then click **Use your own page instead**, enter the page URL and click **Verify**.
{% endtab %}
{% endtabs %}

## Optional: metadata and recorder events

<details>

<summary>How to add custom metadata</summary>

Store additional data along the recordings.

{% tabs %}
{% tab title="npm or yarn" %}
{% code title="Add metadata" %}

```javascript
initBirdie({
  clientId: "YOUR_CLIENT_ID",
  metadata: {
    any: {
      key: "123",
      product_id: "EBF-233"
    },
  },
});
```

{% endcode %}

To update it later, once Birdie is ready:

{% code title="Update metadata later" %}

```javascript
birdie.update({ metadata: { status: { id: "123", label: "active", color: "#000000" } } })
```

{% endcode %}
{% endtab %}

{% tab title="Paste in the <head>" %}
Set it in `window.birdieSettings` before the snippet loads:

{% code title="Before the snippet" %}

```javascript
window.birdieSettings = {
    contact_email: "john.doe@acme.com",
    metadata: {
        mykey: "value",
        otherkey: [
            { x: 12, y: 13 },
            { opacity: 35, width: 440 }
        ]
    }
}
```

{% endcode %}

Or set it after the snippet loads:

{% code title="After the snippet" %}

```javascript
if (window.birdie) {
    window.birdie.metadata = {
        mykey: "value",
        otherkey: [
            { x: 12, y: 13 },
            { opacity: 35, width: 440 }
        ]
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

If several tabs have the snippet loaded, only the latest metadata set is attached to a recording.

</details>

<details>

<summary>How to hook into recorder events</summary>

Know when a recording starts and stops, for example to add metadata at that moment.

{% tabs %}
{% tab title="npm or yarn" %}
{% code title="Listen to recording events" %}

```javascript
initBirdie({
  clientId: "YOUR_CLIENT_ID",
  onReady(birdie) {
    birdie.on("start", (data) => {
      console.log("Recording started", data);
      birdie.metadata = { dynamicKey: "value" };
    });

    birdie.on("stop", (data) => {
      console.log("Recording stopped", data);
    });
  },
});
```

{% endcode %}
{% endtab %}

{% tab title="Paste in the <head>" %}
Register `onBirdieReady` before loading the snippet, so `window.birdie` is available, then listen for `start` and `stop`:

{% code title="Before the snippet" %}

```javascript
window.birdieReadyCallback = function() {
  console.log("Birdie Ready, window.birdie object is now available")
  window.birdie.on('start', function(data) {
    console.log("A recording was started", data);
    // save custom data, or add metadata
    window.birdie.metadata = { key: "value" }
  });
  window.birdie.on('stop', function(data) {
    console.log("A recording was stopped", data);
  });
}

window.birdieSettings = {
  onBirdieReady: birdieReadyCallback
}

// then add the snippet…
```

{% endcode %}
{% endtab %}
{% endtabs %}

</details>

{% hint style="success" %}
Need help? Contact us at <support@birdie.so>
{% endhint %}
