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

# Track forms and booking tools

> Pass contentgrove attribution into your forms, Calendly, Typeform, and other booking tools so bookings and CRM deals trace back to the video that drove them.

A conversion on your thank-you page is recorded automatically. A booking or form submission is different: the visitor leaves your page and finishes somewhere else, often days later in your calendar or CRM.

To keep that connection, the tracking script adds two values to forms and booking links on your page:

| Value        | What it is                                       |
| ------------ | ------------------------------------------------ |
| `cg_click`   | The click that brought this visitor to your site |
| `cg_visitor` | A durable, anonymous ID for the visitor          |

When those values reach your booking tool or CRM, a call booked today can still be traced back to the video that started it.

## What happens automatically

Once the tracking script is installed, it handles these without any extra setup:

| On your page              | What the script does                              |
| ------------------------- | ------------------------------------------------- |
| Any `<form>`              | Adds hidden `cg_click` and `cg_visitor` fields    |
| Calendly links and embeds | Adds `utm_content=cg_<click ID>`                  |
| Typeform links and embeds | Adds `cg_click` and `cg_visitor` as hidden fields |
| Youform links and embeds  | Adds `cg_click` and `cg_visitor` to the link      |

Elements added to the page later — by a popup, an embed script, or your own JavaScript — are handled as they appear.

<Note>
  The script never overwrites a value you set yourself. If you already put a `cg_click` or `utm_content` on a link, yours is kept.
</Note>

## Create the fields in your tool

Most form tools discard fields they don't recognize. Before the values will show up in your submissions, create two fields in the tool itself, named exactly:

* `cg_click`
* `cg_visitor`

In Typeform these are **hidden fields**. In most other builders they're custom or hidden fields. Calendly is the exception — it passes UTM parameters through on its own, so there's nothing to create.

<Tip>
  Send yourself a test submission after setting this up, and check that both values arrive. An empty value usually means the field name doesn't match exactly.
</Tip>

## Tools on your own domain, or tools not listed above

The script recognizes Calendly, Typeform, and Youform by their web address. If you run one of them on your own domain — a Typeform at `forms.yoursite.com`, a Calendly at `meet.yoursite.com` — or you use a different tool entirely, add `data-cg-passthrough` to the link and tell the script where to put the value:

| Attribute                               | Where the value goes         | Use it when                                                  |
| --------------------------------------- | ---------------------------- | ------------------------------------------------------------ |
| `data-cg-passthrough`                   | `?cg_click=…&cg_visitor=…`   | Your tool accepts any URL parameter. Most form builders do.  |
| `data-cg-passthrough="hash"`            | `#cg_click=…&cg_visitor=…`   | Your tool reads hidden fields after the `#`, like Typeform.  |
| `data-cg-passthrough="utm:utm_content"` | `?utm_content=cg_<click ID>` | Your tool only passes UTM parameters through, like Calendly. |

```html theme={null}
<!-- Calendly on your own domain -->
<a href="https://meet.yoursite.com/intro" data-cg-passthrough="utm:utm_content">
  Book a call
</a>

<!-- A form builder that accepts URL parameters -->
<a href="https://tally.so/r/abc123" data-cg-passthrough>
  Apply now
</a>
```

You can name any UTM field after `utm:` — use `utm:utm_term` if `utm_content` is already used by your ad campaigns.

<Warning>
  Check which one your tool needs. If it only forwards UTM parameters and you use the default, the value is dropped and bookings arrive with no attribution.
</Warning>

## Use the JavaScript API

For embeds you can't add an attribute to, the script exposes `window.contentgrove`:

| Method                            | What it does                                                                                                        |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `getAttribution()`                | Returns the current `clickId`, `visitorId`, and `firstTouch`, or `null`                                             |
| `decorate(urlOrElement, options)` | Adds the values to a URL or an element. Pass `{ shape: "utm:utm_content" }` or `{ shape: "hash" }` to choose where. |
| `onReady(callback)`               | Runs your callback once the script has finished its consent check                                                   |
| `getCalendlyUtm()`                | Returns the UTM object for Calendly's own embed API                                                                 |

Calendly's inline widget builds its own URL, so pass the values through its API rather than relying on the automatic handling:

```html theme={null}
<script>
  window.contentgrove.onReady(function () {
    Calendly.initInlineWidget({
      url: "https://calendly.com/yourname/intro",
      parentElement: document.getElementById("calendly-inline"),
      utm: window.contentgrove.getCalendlyUtm(),
    });
  });
</script>
```

## Consent

This follows the same consent rules as the rest of the script. If a visitor declines, no visitor ID is created and nothing is added to your forms or booking links. If they accept and later withdraw consent, the script removes the values it added from the page.

Visitors who decline can still be matched later by email address if they reach your CRM. See [consent mode](/concepts/how-tracking-works#consent-mode).

## What isn't covered

Some embeds render your form inside an iframe from the tool's own domain. The script can't reach inside those, so the values can't be added. If your booking tool offers a plain link or a URL-based embed, use that instead.
