Skip to content

Commit 4838509

Browse files
harlan-zwclaude
andauthored
feat(registry): add TikTok Pixel script (#569)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 78367b1 commit 4838509

6 files changed

Lines changed: 358 additions & 0 deletions

File tree

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
---
2+
title: TikTok Pixel
3+
description: Use TikTok Pixel in your Nuxt app.
4+
links:
5+
- label: Source
6+
icon: i-simple-icons-github
7+
to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/tiktok-pixel.ts
8+
size: xs
9+
---
10+
11+
[TikTok Pixel](https://ads.tiktok.com/help/article/tiktok-pixel) lets you measure, optimize and build audiences for your TikTok ad campaigns.
12+
13+
Nuxt Scripts provides a registry script composable `useScriptTikTokPixel` to easily integrate TikTok Pixel in your Nuxt app.
14+
15+
### Nuxt Config Setup
16+
17+
The simplest way to load TikTok Pixel globally in your Nuxt App is to use Nuxt config. Alternatively you can directly
18+
use the [useScriptTikTokPixel](#usescripttiktokpixel) composable.
19+
20+
If you don't plan to send custom events you can use the [Environment overrides](https://nuxt.com/docs/getting-started/configuration#environment-overrides) to
21+
disable the script in development.
22+
23+
::code-group
24+
25+
```ts [Always enabled]
26+
export default defineNuxtConfig({
27+
scripts: {
28+
registry: {
29+
tiktokPixel: {
30+
id: 'YOUR_PIXEL_ID'
31+
}
32+
}
33+
}
34+
})
35+
```
36+
37+
```ts [Production only]
38+
export default defineNuxtConfig({
39+
$production: {
40+
scripts: {
41+
registry: {
42+
tiktokPixel: {
43+
id: 'YOUR_PIXEL_ID'
44+
}
45+
}
46+
}
47+
}
48+
})
49+
```
50+
51+
::
52+
53+
#### With Environment Variables
54+
55+
If you prefer to configure your id using environment variables.
56+
57+
```ts [nuxt.config.ts]
58+
export default defineNuxtConfig({
59+
scripts: {
60+
registry: {
61+
tiktokPixel: true,
62+
}
63+
},
64+
// you need to provide a runtime config to access the environment variables
65+
runtimeConfig: {
66+
public: {
67+
scripts: {
68+
tiktokPixel: {
69+
id: '', // NUXT_PUBLIC_SCRIPTS_TIKTOK_PIXEL_ID
70+
},
71+
},
72+
},
73+
},
74+
})
75+
```
76+
77+
```text [.env]
78+
NUXT_PUBLIC_SCRIPTS_TIKTOK_PIXEL_ID=<YOUR_ID>
79+
```
80+
81+
## useScriptTikTokPixel
82+
83+
The `useScriptTikTokPixel` composable lets you have fine-grain control over when and how TikTok Pixel is loaded on your site.
84+
85+
```ts
86+
const { proxy } = useScriptTikTokPixel({
87+
id: 'YOUR_PIXEL_ID'
88+
})
89+
90+
// Track an event
91+
proxy.ttq('track', 'ViewContent', {
92+
content_id: '123',
93+
content_name: 'Product Name',
94+
value: 99.99,
95+
currency: 'USD'
96+
})
97+
```
98+
99+
Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage.
100+
101+
### TikTokPixelApi
102+
103+
```ts
104+
export interface TikTokPixelApi {
105+
ttq: TtqFns & {
106+
push: TtqFns
107+
loaded: boolean
108+
queue: any[]
109+
}
110+
}
111+
112+
type TtqFns =
113+
& ((cmd: 'track', event: StandardEvents | string, properties?: EventProperties) => void)
114+
& ((cmd: 'page') => void)
115+
& ((cmd: 'identify', properties: IdentifyProperties) => void)
116+
& ((cmd: string, ...args: any[]) => void)
117+
118+
type StandardEvents =
119+
| 'ViewContent' | 'ClickButton' | 'Search' | 'AddToWishlist'
120+
| 'AddToCart' | 'InitiateCheckout' | 'AddPaymentInfo' | 'CompletePayment'
121+
| 'PlaceAnOrder' | 'Contact' | 'Download' | 'SubmitForm'
122+
| 'CompleteRegistration' | 'Subscribe'
123+
```
124+
125+
### Config Schema
126+
127+
You must provide the options when setting up the script for the first time.
128+
129+
```ts
130+
export const TikTokPixelOptions = object({
131+
id: string(),
132+
trackPageView: optional(boolean()), // default: true
133+
})
134+
```
135+
136+
## Example
137+
138+
Using TikTok Pixel to track a purchase event.
139+
140+
::code-group
141+
142+
```vue [PurchaseButton.vue]
143+
<script setup lang="ts">
144+
const { proxy } = useScriptTikTokPixel()
145+
146+
function trackPurchase() {
147+
proxy.ttq('track', 'CompletePayment', {
148+
content_id: 'product-123',
149+
content_name: 'Awesome Product',
150+
value: 49.99,
151+
currency: 'USD'
152+
})
153+
}
154+
</script>
155+
156+
<template>
157+
<button @click="trackPurchase">
158+
Complete Purchase
159+
</button>
160+
</template>
161+
```
162+
163+
::
164+
165+
## Identifying Users
166+
167+
You can identify users for advanced matching:
168+
169+
```ts
170+
const { proxy } = useScriptTikTokPixel()
171+
172+
proxy.ttq('identify', {
173+
email: 'user@example.com',
174+
phone_number: '+1234567890'
175+
})
176+
```
177+
178+
## Disabling Auto Page View
179+
180+
By default, TikTok Pixel tracks page views automatically. To disable:
181+
182+
```ts
183+
export default defineNuxtConfig({
184+
scripts: {
185+
registry: {
186+
tiktokPixel: {
187+
id: 'YOUR_PIXEL_ID',
188+
trackPageView: false
189+
}
190+
}
191+
}
192+
})
193+
```

playground/pages/index.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ function getPlaygroundPath(script: any): string | null {
2929
'x-pixel': '/third-parties/x-pixel/nuxt-scripts',
3030
'reddit-pixel': '/third-parties/reddit-pixel/nuxt-scripts',
3131
'snapchat-pixel': '/third-parties/snapchat/nuxt-scripts',
32+
'tiktok-pixel': '/third-parties/tiktok-pixel/nuxt-scripts',
3233
'google-adsense': '/third-parties/google-adsense/nuxt-scripts',
3334
'carbon-ads': '/third-parties/carbon/nuxt-scripts',
3435
'clarity': '/third-parties/clarity/nuxt-scripts',
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<script lang="ts" setup>
2+
import { useHead, useScriptTikTokPixel } from '#imports'
3+
4+
useHead({
5+
title: 'TikTok Pixel',
6+
})
7+
8+
const { status, proxy } = useScriptTikTokPixel({
9+
id: 'YOUR_PIXEL_ID',
10+
})
11+
12+
function trackEvent() {
13+
proxy.ttq('track', 'ViewContent', {
14+
content_name: 'Test Product',
15+
content_type: 'product',
16+
})
17+
}
18+
19+
function trackPurchase() {
20+
proxy.ttq('track', 'CompletePayment', {
21+
value: 99.99,
22+
currency: 'USD',
23+
})
24+
}
25+
</script>
26+
27+
<template>
28+
<div>
29+
<ClientOnly>
30+
<div>
31+
status: {{ status }}
32+
</div>
33+
<div class="flex gap-2 mt-4">
34+
<UButton @click="trackEvent">
35+
Track ViewContent
36+
</UButton>
37+
<UButton @click="trackPurchase">
38+
Track Purchase
39+
</UButton>
40+
</div>
41+
</ClientOnly>
42+
</div>
43+
</template>

src/registry.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { PlausibleAnalyticsInput } from './runtime/registry/plausible-analy
88
import type { RegistryScript } from './runtime/types'
99
import type { GoogleAdsenseInput } from './runtime/registry/google-adsense'
1010
import type { ClarityInput } from './runtime/registry/clarity'
11+
import type { TikTokPixelInput } from './runtime/registry/tiktok-pixel'
1112

1213
// avoid nuxt/kit dependency here so we can use in docs
1314

@@ -111,6 +112,20 @@ export async function registry(resolve?: (path: string, opts?: ResolvePathOption
111112
from: await resolve('./runtime/registry/x-pixel'),
112113
},
113114
},
115+
{
116+
label: 'TikTok Pixel',
117+
category: 'tracking',
118+
logo: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path d="M224 72a52.059 52.059 0 0 1-52-52a4 4 0 0 0-4-4h-40a4 4 0 0 0-4 4v132a28 28 0 1 1-40.567-25.019a4 4 0 0 0 2.567-3.734V80a4 4 0 0 0-4.652-3.949A84.032 84.032 0 1 0 156 152v-43.047a99.432 99.432 0 0 0 52 14.586a4 4 0 0 0 4-4V76a4 4 0 0 0-4-4z" fill="currentColor"/></svg>`,
119+
import: {
120+
name: 'useScriptTikTokPixel',
121+
from: await resolve('./runtime/registry/tiktok-pixel'),
122+
},
123+
scriptBundling(options?: TikTokPixelInput) {
124+
if (!options?.id)
125+
return false
126+
return withQuery('https://analytics.tiktok.com/i18n/pixel/events.js', { sdkid: options.id, lib: 'ttq' })
127+
},
128+
},
114129
{
115130
label: 'Snapchat Pixel',
116131
src: 'https://sc-static.net/scevent.min.js',
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { withQuery } from 'ufo'
2+
import { useRegistryScript } from '../utils'
3+
import { object, string, optional, boolean } from '#nuxt-scripts-validator'
4+
import type { RegistryScriptInput } from '#nuxt-scripts/types'
5+
6+
type StandardEvents
7+
= 'ViewContent'
8+
| 'ClickButton'
9+
| 'Search'
10+
| 'AddToWishlist'
11+
| 'AddToCart'
12+
| 'InitiateCheckout'
13+
| 'AddPaymentInfo'
14+
| 'CompletePayment'
15+
| 'PlaceAnOrder'
16+
| 'Contact'
17+
| 'Download'
18+
| 'SubmitForm'
19+
| 'CompleteRegistration'
20+
| 'Subscribe'
21+
22+
interface EventProperties {
23+
content_id?: string
24+
content_type?: string
25+
content_name?: string
26+
contents?: Array<{ content_id: string, content_type?: string, content_name?: string, price?: number, quantity?: number }>
27+
currency?: string
28+
value?: number
29+
description?: string
30+
query?: string
31+
[key: string]: any
32+
}
33+
34+
interface IdentifyProperties {
35+
email?: string
36+
phone_number?: string
37+
external_id?: string
38+
}
39+
40+
type TtqFns
41+
= ((cmd: 'track', event: StandardEvents | string, properties?: EventProperties) => void)
42+
& ((cmd: 'page') => void)
43+
& ((cmd: 'identify', properties: IdentifyProperties) => void)
44+
& ((cmd: string, ...args: any[]) => void)
45+
46+
export interface TikTokPixelApi {
47+
ttq: TtqFns & {
48+
push: TtqFns
49+
loaded: boolean
50+
queue: any[]
51+
}
52+
}
53+
54+
declare global {
55+
interface Window extends TikTokPixelApi {}
56+
}
57+
58+
export const TikTokPixelOptions = object({
59+
id: string(),
60+
trackPageView: optional(boolean()), // default true
61+
})
62+
63+
export type TikTokPixelInput = RegistryScriptInput<typeof TikTokPixelOptions, true, false, false>
64+
65+
export function useScriptTikTokPixel<T extends TikTokPixelApi>(_options?: TikTokPixelInput) {
66+
return useRegistryScript<T, typeof TikTokPixelOptions>('tiktokPixel', options => ({
67+
scriptInput: {
68+
src: withQuery('https://analytics.tiktok.com/i18n/pixel/events.js', {
69+
sdkid: options?.id,
70+
lib: 'ttq',
71+
}),
72+
crossorigin: false,
73+
},
74+
schema: import.meta.dev ? TikTokPixelOptions : undefined,
75+
scriptOptions: {
76+
use() {
77+
return { ttq: window.ttq }
78+
},
79+
},
80+
clientInit: import.meta.server
81+
? undefined
82+
: () => {
83+
const ttq: TikTokPixelApi['ttq'] = window.ttq = function (...params: any[]) {
84+
// @ts-expect-error untyped
85+
if (ttq.callMethod) {
86+
// @ts-expect-error untyped
87+
ttq.callMethod(...params)
88+
}
89+
else {
90+
ttq.queue.push(params)
91+
}
92+
} as any
93+
ttq.push = ttq
94+
ttq.loaded = true
95+
ttq.queue = []
96+
if (options?.id) {
97+
ttq('init', options.id)
98+
if (options?.trackPageView !== false) {
99+
ttq('page')
100+
}
101+
}
102+
},
103+
}), _options)
104+
}

src/runtime/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import type { UmamiAnalyticsInput } from './registry/umami-analytics'
3131
import type { RybbitAnalyticsInput } from './registry/rybbit-analytics'
3232
import type { RedditPixelInput } from './registry/reddit-pixel'
3333
import type { PayPalInput } from './registry/paypal'
34+
import type { TikTokPixelInput } from './registry/tiktok-pixel'
3435
import { object } from '#nuxt-scripts-validator'
3536

3637
export type WarmupStrategy = false | 'preload' | 'preconnect' | 'dns-prefetch'
@@ -152,6 +153,7 @@ export interface ScriptRegistry {
152153
redditPixel?: RedditPixelInput
153154
segment?: SegmentInput
154155
stripe?: StripeInput
156+
tiktokPixel?: TikTokPixelInput
155157
xPixel?: XPixelInput
156158
snapchatPixel?: SnapTrPixelInput
157159
youtubePlayer?: YouTubePlayerInput

0 commit comments

Comments
 (0)