---
title: Gainsight PX
description: Gate Gainsight PX product analytics and in-app engagements on
  consent, configure the aptrinsic snippet and disable tracking when consent is
  withdrawn.
seoTitle: Gainsight PX cookie consent with c15t
seoDescription: Load Gainsight PX after analytics consent. Configure user and
  account identity, choose a consent category and disable the tag when consent
  is withdrawn.
icon: chart-no-axes-combined
group: integrations
lastModified: "2026-10-27T15:48:14Z"
lastAuthor: Kaylee
---
The `gainsightPx` helper waits for `measurement` consent before it creates
`window.aptrinsic` or requests the PX SDK. This gate covers both product
analytics and in-app engagements. On revocation, the helper disables the tag
through PX's config API and requests a session reset.

## Configure Gainsight PX

Copy the product/channel key from **Administration > Products** in Gainsight PX.
Remove any existing PX snippet or tag-manager installation so it cannot load
outside c15t.

|Package manager|Command|
|:--|:--|
|npm|`npm install @c15t/integrations`|
|pnpm|`pnpm add @c15t/integrations`|
|yarn|`yarn add @c15t/integrations`|
|bun|`bun add @c15t/integrations`|

```ts title="src/consent-scripts.ts"
import { gainsightPx } from '@c15t/integrations/gainsight-px';

export const scripts = [gainsightPx({ productKey: 'AP-PRODUCT-KEY-2' })];
```

## Register the scripts

Complete your [framework quickstart](/docs/frameworks) first. Keep its Inth
endpoint, policy, styles and consent UI. Remove the vendor's original script,
SDK initializer or tag-manager entry, so the vendor loads only through c15t.

The vendor pages put the helper in `src/consent-scripts.ts`. If your framework
quickstart already has a `scripts` array, such as the one in `c15t.config.ts`
in the Next.js guide, add the helper to that array instead of creating a
second file.
The `scripts` export is a configuration, not an initializer. Add it to the c15t provider you already have, at the registration
point for your framework below. These are edits to that provider, not a second
provider.

**Next.js**

Add the configuration to `scripts` in `c15t.config.ts`, next to
`next.config.ts`:

```ts title="c15t.config.ts"
import { defineConsentConfig } from 'c15t/next';
import { scripts } from './src/consent-scripts';

export default defineConsentConfig({ scripts });
```

Keep the rest of your config, such as `mode` and `routePrefix`, in the
same call. `ConsentRoot` reads the config in the browser, so the layout
keeps passing only `state`. App Router, Pages Router and static export all
read the same file. See
[Next.js scripts and embeds](/docs/frameworks/next/scripts).

**TanStack Start**

Import the configuration into your root route and pass it to the existing
`ConsentRoot` as a top-level prop. Keep the loader from the
[TanStack Start quickstart](/docs/frameworks/tanstack-start/quickstart):

```tsx title="src/routes/__root.tsx"
import { scripts } from '../consent-scripts';

<ConsentRoot state={consent} scripts={scripts}>
```

Import vendor helpers in the root route module, not in a server function.
A server function's return value must be serializable, and script
configurations carry callbacks. See
[TanStack Start scripts](/docs/frameworks/tanstack-start/scripts).

**React**

Add the configuration to the existing `ConsentProvider` options, next to
`mode`:

```tsx title="src/consent.tsx"
import { scripts } from './consent-scripts';

<ConsentProvider options={{ mode, scripts }}>
```

`mode` is the `manifest()` value from the
[React quickstart](/docs/frameworks/react/quickstart). Keep the banner,
dialog and preferences link inside the provider. See
[React scripts and embeds](/docs/frameworks/react/scripts).

**Nuxt**

Register the scripts under the `c15t` key in `app/app.config.ts`. Adjust the
relative import to where you created `consent-scripts.ts`:

```ts title="app/app.config.ts"
import { scripts } from '../src/consent-scripts';

export default defineAppConfig({
  c15t: { scripts },
});
```

The Nuxt module merges this over its options in `nuxt.config.ts` and starts
one script loader in the browser after hydration, once it has applied the
visitor's stored choice and privacy signals. Keep `scripts` out of
`nuxt.config.ts`, which reaches the browser as JSON and drops the vendor
callbacks. Write the vendor IDs into `consent-scripts.ts`. See
[Nuxt scripts and embeds](/docs/frameworks/nuxt/scripts).

**Vue**

Pass the scripts to the existing `c15tVue` plugin call in `src/main.ts`:

```ts title="src/main.ts"
import { c15tVue, manifest } from 'c15t/vue/vue-plugin';
import { scripts } from './consent-scripts';

app.use(c15tVue, {
  mode: manifest(),
  scripts,
});
```

Keep your existing `mode` and other options. The plugin starts one
script loader when the app mounts, after it has applied the visitor's stored
choice. Do not also call `createScriptLoader` from a component. See
[Vue scripts and embeds](/docs/frameworks/vue/scripts).

**Astro**

Add the scripts to the client entrypoint from the
[Astro quickstart](/docs/frameworks/astro/quickstart), `src/c15t.client.ts`,
which the integration finds on its own. Keep `astro.config.mjs` as it is.
If the module already exports scripts, combine the two arrays.

```ts title="src/c15t.client.ts"
import type { C15tClientOptionsExtension } from 'c15t/astro';
import { scripts } from './consent-scripts';

export default { scripts } satisfies C15tClientOptionsExtension;
```

Vendor helpers contain callbacks, and the integration options in
`astro.config.mjs` are serialized into the page, so do not put helpers in
the integration's `scripts` option. The integration passes the client
entrypoint to the one runtime every page shares, including across
`ClientRouter` navigation.

**Svelte**

Import the scripts in the component that owns your existing provider and
pass them as a top-level prop:

```svelte title="src/App.svelte"
<script lang="ts">
  import { ConsentProvider, manifest } from 'c15t/svelte';
  import { scripts } from './consent-scripts';
</script>

<ConsentProvider mode={manifest()} {scripts}>
  <!-- Keep your application, consent UI and preferences link here. -->
</ConsentProvider>
```

Retain the styles and consent UI from the [Svelte quickstart](/docs/frameworks/svelte/quickstart).
The provider owns the loader and disposes it on unmount.

**SvelteKit**

Add the scripts to the existing `ConsentRoot` in the root layout. Keep the
handle and the layout load from the [SvelteKit quickstart](/docs/frameworks/sveltekit/quickstart).

```svelte title="src/routes/+layout.svelte"
<script lang="ts">
  import { ConsentRoot } from 'c15t/svelte';
  import { scripts } from '../consent-scripts';

  let { children, data } = $props();
</script>

<ConsentRoot state={data.consent} {scripts}>
  {@render children()}
  <!-- Keep your consent UI and preferences link here. -->
</ConsentRoot>
```

Import vendor helpers in the layout component, not in `+layout.server.ts`:
a server load cannot send functions to the browser. Prerendered, static and
SPA-mode pages use the same `scripts` prop. If you pass an externally owned
`runtime` to the provider, register scripts when creating that runtime instead.

**HTML**

The helpers in `@c15t/integrations` are ES modules that need a bundler. On a
page that loads the c15t script tag, paste the vendor's own snippet instead
and keep it inert until its category is allowed:

```html
<script type="text/plain" data-c15t-category="measurement">
  // The vendor's snippet, unchanged
</script>
```

Use the category this guide names for the vendor. c15t runs the snippet
once that category is allowed, and reloads the page when the visitor
withdraws it. Helper options on this page, such as `loadMode`, do not apply
to a pasted snippet. See [HTML scripts](/docs/frameworks/html/scripts).

**JavaScript**

Pass the scripts to `init()` from `@c15t/browser`, next to your mode:

```ts
import { init, manifest } from '@c15t/browser';
import { scripts } from './consent-scripts';

const consent = init({
  mode: manifest(),
  scripts,
});
```

Keep the mode from your quickstart. With
`createConsentRuntime` from `c15t/runtime`, pass `scripts` to it instead.
A kernel you create yourself needs a loader from
`c15t/modules/script-loader`. Attach one loader per kernel. See
[JavaScript scripts](/docs/frameworks/javascript/scripts).

## Options

|Option|Default|Behavior|
|--|--|--|
|`productKey`|Required|Product/channel key assigned to `aptrinsic.p` and the loader's `a` query parameter. Changing the key after this helper starts PX throws an error and requires a page reload. Blank values log an error and skip installation.|
|`category`|`measurement`|Consent category or condition for the whole SDK. Choose `experience` if it fits your use of analytics and engagements.|
|`config`|None|JSON-serializable PX startup configuration assigned to `aptrinsic.c`. Changing it after PX starts throws and requires a page reload. c15t owns `enableTag`; do not set it yourself.|
|`user`|None|User identify payload with a required `id`. The helper queues it only after mapped consent.|
|`account`|None|Optional account identify payload with a required `id`. Sent only when `user` is also supplied.|
|`scriptUrl`|`https://web-sdk.aptrinsic.com/api/aptrinsic.js?a=<productKey>`|Full loader override. Other PX requests still use the endpoints configured by the SDK.|

`config` supports PX options such as `fullDomainCookie`, `localStorageCookie`,
`usePOST` and `htmlSanitization`. Keep configuration and identity data
JSON-serializable. Send dates as epoch milliseconds. For custom attributes,
use the API names from **Administration > Attributes**.

```ts
// Options for gainsightPx in src/consent-scripts.ts.
{
	productKey: 'AP-PRODUCT-KEY-2',
	category: 'experience',
	config: { fullDomainCookie: true },
	user: { id: 'user-123', plan: 'paid' },
	account: { id: 'account-456' },
}
```

The optional identity is part of the script configuration. For a later login or
account change, call PX's `identify` API only while the visitor still permits
this vendor. c15t does not gate event calls made elsewhere in your application.

## Loading and revocation

Before mapped consent, the helper creates no PX global, loader, cookies or
localStorage entries. It does not load PX with `enableTag: false` as an
early-loading mode.

After consent, the helper creates the official function queue with `p` for the
product key and `c` for configuration. It enables the tag, optionally queues
`identify`, then appends the asynchronous loader. PX may begin anonymous
tracking before its queued identify call completes.

When mapped consent is withdrawn, the helper sends
`aptrinsic('config', 'enableTag', false)` followed by `aptrinsic('reset')` and
removes the loader element if it is still downloading or failed to load.
After the loader's load event, the helper retains the element. Gainsight
documents `enableTag: false` as stopping PX cookies, network calls and
engagements. Its current SDK resets the session
inside the disable call and ignores a separate `reset` while disabled.

If `reloadOnConsentRevoked` is disabled, granting consent again after a completed
load sends `aptrinsic('config', 'enableTag', true)` and repeats the optional
`identify` call through the existing API. The helper reuses the loaded SDK
because PX rejects a second execution of its bundle. A loader removed before
completion can be requested again on a later grant if PX has not started
executing it.

Before requesting another pending load, the helper rebuilds its function queue
from the current options. The new queue contains only the current product,
configuration and optional identity, so an interrupted setup cannot replay an
earlier user's identify call. This also applies to server-generated startup.
It does not cancel a detached download that may still execute later.

When hydrating a server-started loader, use the same
`productKey`, `config`, `user`, `account` and `scriptUrl` on the server and client.
The loader compares the server setup with the client options before adopting
the server's element. This check also runs when PX finishes startup before
hydration. c15t retains the startup settings separately from PX's queue so the
check still works after PX replaces its function stub. A mismatch throws before
registration or replacement and leaves the existing setup intact. If identity
changes between render and hydration, reload the page with the current options.
Matching options preserve the existing SDK and loader. Equivalent configuration
objects can use different property orders.

Disposing the last provider or script loader removes its element, but PX's code
remains in the page. Later mounts check PX's own bundle execution marker and
use the existing API without requesting the bundle again. A denied mount sends
disable and reset commands without enabling PX or identifying the user. A later
grant enables the tag and repeats the optional identify call. An `aptrinsic`
queue alone does not prove that the SDK ran. The runtime check also survives
customization with object spread, such as
`{ ...gainsightPx(options), anonymizeId: false }`, even when the copy was made
before PX started and the same copy is mounted again after disposal.
Creating a fresh spread copy with equivalent options on each rerender also
preserves a pending loader and its queued setup.

Once this helper starts the SDK, use the same `productKey` for the rest of the
page's lifetime, including after disposal. Constructing a helper with another
key throws an error. The loader also rejects a different-key script created
before startup if it is registered after PX starts, preserving the current
configuration and its consent handling. Reload the page before installing a
different product.
PX's asynchronous `tagKey` API queues commands during reconfiguration and can
defer opt-out until after queued identity calls, so c15t does not use it.

Keep the same `config` after startup too, including after disposal. Adding,
removing or changing a configuration value throws before registration or
replacement, so the current consent handling stays active. This also applies
to scripts created before startup and registered afterward. Equivalent objects
may use a different property order. The reuse path forwards consent and optional
identity calls; it does not apply new startup settings through PX's config API.
Reload the page to change configuration.

PX has one global SDK per window. Mount one configuration at a time. Owners
with different options still control the same SDK and do not share a consent
veto. Equivalent configurations can share owners.

Disposal itself does not disable PX or stop its timers and listeners. Keep a
consent owner mounted while the SDK is in use so it can forward revocation.

Keep `reloadOnConsentRevoked` enabled. Removing a pending loader does not unload
PX's code or cancel requests that already started. A load that finishes after
revocation may still execute vendor code. The helper queues the stop request,
but a page reload ends the SDK's timers and listeners. If consent returns before
that detached download executes, a new loader can race with it and PX can reject
the duplicate bundle. This helper does not
guarantee that every PX feature resumes after another grant without a reload.
Verify recovery with your product key, SDK version and enabled features.

## Cookies and storage

Gainsight documents these first-party cookies:

|Name|Lifetime|Purpose|
|--|--|--|
|`apt.uid`|1 year|Persistent visitor identity.|
|`apt.sid`|30 minutes of inactivity|Current session identity.|

`fullDomainCookie: true` scopes cookie data to the full hostname rather than
the top-level domain. `localStorageCookie: true` moves cookie data to
localStorage. These settings change where PX persists data; they do not replace
the consent gate. Inspect storage for your configured PX version and features.

## Verify Gainsight PX

In a private window, deny analytics and confirm there is no request to
`web-sdk.aptrinsic.com` or `esp.aptrinsic.com`, no `window.aptrinsic`, and no
`apt.uid` or `apt.sid` storage. Allow the category selected by `category`, then
check that the SDK loads and your PX product receives the expected events.
Revoke that category and confirm the reloaded page makes no PX request.

The helper already sends PX's documented opt-out command. The shared advice
about adding a custom stop callback applies when you need more cleanup than
this command provides.

Test in a private window with an opt-in policy. Open DevTools Network, disable
the cache and filter by the vendor's domain:

1. Load the page. No request goes to the vendor before you choose.
2. Click Reject, then reload. There is still no vendor request.
3. Open Privacy settings and allow the helper's category. The vendor script
   loads without a page reload.
4. Turn the category off again and save. c15t reloads the page, and the new
   page makes no vendor request.

c15t reloads on revocation because removing a script element does not stop
code that already ran. The vendor's listeners, timers and queued events stay
alive until the page unloads. If you set `reloadOnConsentRevoked: false`, stop
the vendor yourself. Register a callback-only script whose `onConsentChange`
calls the vendor's opt-out API, as shown in
[custom integrations](/docs/integrations/building-integrations), and check the
permission before each of your own event calls. The reload does not delete
cookies the vendor already set; see
[clear on revocation for your framework](/docs/integrations/overview#vendor-switches-and-cookie-cleanup).

The helper lists its vendor by name in the preference dialog, so a visitor can
turn it off inside an allowed category. See
[vendor consent for your framework](/docs/integrations/overview#vendor-switches-and-cookie-cleanup). The
[consent verification guide](/docs/guides/verify-consent) covers navigation,
expiry and hosting checks.

## Official sources

* [PX Web SDK snippet, identify and reset](https://support.gainsight.com/PX/API_for_Developers/01About/Work_with_Gainsight_PX_Web_SDK)
* [Config API and tracking opt-out](https://support.gainsight.com/PX/API_for_Developers/02Usage_of_Different_APIs/Use_Config_API)
* [JavaScript SDK configuration](https://support.gainsight.com/PX/Administration/General/Javascript_SDK_Configuration)
* [PX analytics cookies](https://support.gainsight.com/PX/Install_PX/Install_PX_Web/Analytics_Cookies)
