@grainql/tag
v4.7.1
Published
Grain Tag - Lightweight analytics SDK for Grain
Maintainers
Readme
Installation
Script tag
<script src="https://tag.grainql.com/v4/your-tenant-id.js"></script>The Cloudflare Worker adds your tenant config to the script. The SDK starts on its own. No setup is necessary.
npm
npm install @grainql/tagQuick start
Script tag
<script src="https://tag.grainql.com/v4/your-tenant-id.js"></script>
<script>
// The SDK is ready. Track an event.
GrainTag.track('button_clicked', { button: 'signup' });
// Identify a user after login.
GrainTag.identify('user-123');
</script>npm (React, Next.js, Vue, Svelte, and more)
import { init, track, identify } from '@grainql/tag';
// Call init() one time.
init({ tenantId: 'your-tenant-id' });
// Events sent before init() go into a queue. The SDK sends them after init().
track('page_viewed', { page: '/home' });
// Identify a user after login.
identify('user-123');SSR (server-side rendering)
import { init, track } from '@grainql/tag';
// On the server, init() returns a stub that does nothing. It does not crash.
const grain = init({ tenantId: 'your-tenant-id' });
track('page_viewed'); // Ignored on the server. Works in the browser.Config
init({
tenantId: 'your-tenant-id', // Required
apiUrl: 'https://clientapis.grainql.com', // Default
debug: false, // Write debug logs to the console
consentMode: 'auto', // 'auto' | 'opt-in' | 'opt-out'
enablePageViews: true, // Track page views
enableHeatmaps: true, // Track clicks and scrolls
enableSnapshots: true, // Capture DOM snapshots for heatmap overlays
enableRescue: true, // Show exit-intent rescue popups
batchSize: 50, // Events per batch
flushInterval: 5000, // Time between batches, in ms
retryAttempts: 3, // Retries after a failed request
});API reference
Events
track(eventName, properties?) // Send a custom eventIdentity
identify(userId) // Set the user IDConsent
grain.consent.grant(['analytics']) // Grant consent for the given categories
grain.consent.revoke() // Revoke consent
grain.consent.status() // Get the current consent stateLifecycle
getInstance() // Get the current SDK instance
isInitialized() // True if init() has run
destroy() // Stop the SDK. You can call init() again after this.
grain.flush() // Send all queued events now
grain.isReady() // True if the SDK runs in a browserConsent modes
| Mode | Before consent | After consent |
|------|----------------|---------------|
| auto | Cookieless. IDs rotate daily. | Permanent IDs |
| opt-in | Cookieless. IDs rotate daily. | Permanent IDs |
| opt-out | Permanent IDs | Cookieless after the user revokes consent |
How it works
track()puts the event in a queue in memory. It does not block.- Every 5 seconds, or when the queue has 50 events, the SDK sends the batch to the Grain API.
- If a request fails, the SDK retries up to 3 times with exponential backoff.
- When the page unloads or the tab is hidden, the SDK sends the remaining events with the Beacon API.
- By default, the user ID rotates daily and no cookie is set. After consent, the SDK stores a permanent ID in localStorage.
The SDK adds device, browser, os, UTM parameters, first-touch attribution, and session data to every event.
Build from source
npm run build:sdk # Build the SDK (IIFE, ESM, CJS, and type declarations)
npm run build:worker # Build the Cloudflare Worker
npm run build # Build both
npm run build:dev # Development build (not minified)
npm run typecheck # TypeScript type checkGrain SDKs
| Use case | SDK |
|----------|-----|
| Browser analytics, heatmaps, snapshots | @grainql/tag (this package) |
| Script tag delivery (Cloudflare Workers) | @grainql/tag (this package) |
| Remote config | @grainql/analytics-web |
| Server-side events (Node.js) | @grainql/analytics-web |
Migrate from @grainql/analytics-web
If you use @grainql/analytics-web in the browser, change your code as follows:
// Before
import { createGrainAnalytics } from '@grainql/analytics-web';
const grain = createGrainAnalytics({ tenantId: '...' });
grain.track('event');
// After
import { init, track } from '@grainql/tag';
init({ tenantId: '...' });
track('event');| Before | After |
|--------|-------|
| createGrainAnalytics() | init() |
| grain.setUserId() | identify() |
| grain.grantConsent() | grain.consent.grant() |
Contributing
Read CONTRIBUTING.md.
