Analytics SDK Plugins
Note
Plugins are supported in the latest version of Ampli and the latest SDKs. If you are using an older version of Ampli or a Maintenance Amplitude SDK, see Middleware.
Plugins allow you to extend the Amplitude behavior. This pattern is flexible and you can use it to support event enrichment, transformation, filtering, routing to third-party destinations, and more.
Plugin methods¶
A plugin is an object with methods setup()
and execute()
:
Plugin.setup¶
This method contains logic for preparing the plugin for use and has config as a parameter. The expected return value is undefined. A typical use for this method, is to copy configuration from config or instantiate plugin dependencies. This method is called when the plugin is registered to the client via amplitude.add()
.
Plugin.execute¶
This method contains the logic for processing events and has event as parameter. If used as enrichment type plugin, the expected return value is the modified/enriched event. If used as a destination type plugin, the expected return value is a map with keys: event
(BaseEvent), code
(number), and message
(string). This method is called for each event, including Identify, GroupIdentify and Revenue instrumented using the client interface.
Add plugin to Ampli via amplitude.add()
. You can add as many plugin as you like. Each plugin runs in the order based on the plugin type.
amplitude.add(yourPlugin())
amplitude.add(yourPlugin())
Note
if execute()
doesn't returns an event, the event will NOT propagate through the remaining plugins
Plugin types¶
Enrichment type plugin¶
Enrichment Plugin is for modifying properties in Event object or drop an Event. Here are the availabe keys for Event Object which you can enrich in the Enrichment Plugin. Please check here for more examples.
Destination type plugin¶
Destination Plugin is for sending events to a third-party API. Please check here for more examples.
Enrichment Plugins are executed before Destination Plugins. All Enrichment Plugins are executed in the same order in which they were added, and then all Destination Plugins are executed in the order they were added. This ensures that all data is enriched before being sent to its final destination.
Plugin examples¶
Enrichment type plugin¶
Use an Enrichment Plugin to modify event properties:
Enrichment plugin examples
Drop-event plugin example(click to expand)
import * as amplitude from '@amplitude/analytics-browser';
import {PluginType} from '@amplitude/analytics-types';
class FilterEventsPlugin {
name = 'filter-events-plugin';
type = PluginType.ENRICHMENT;
async setup(config) {
return undefined;
}
async execute(event) {
// ignore events with a certain property
if (event.event_properties['ignore'] === true){
// returning null will prevent this event from being processed by subsequent plugins
return null;
}
// Allow other events to be processed and sent to destination plugins
return event;
}
}
amplitude.add(new FilterEventsPlugin());
amplitude.init('API_KEY');
import * as amplitude from '@amplitude/analytics-browser';
import { EnrichmentPlugin, BrowserConfig, PluginType, Event } from '@amplitude/analytics-types';
class FilterEventsPlugin implements EnrichmentPlugin {
name = 'filter-events-plugin';
type = PluginType.ENRICHMENT as any;
async setup(config: BrowserConfig): Promise<void> {
return undefined;
}
async execute(event: Event): Promise<Event | null> {
// ignore events with a certain property
if (event.event_properties['ignore'] === true){
// returning null will prevent this event from being processed by subsequent plugins
return null;
}
// Allow other events to be processed and sent to destination plugins
return event;
}
}
amplitude.add(new FilterEventsPlugin());
amplitude.init('API_KEY');
Remove PII (Personally Identifiable Information) (click to expand)
import * as amplitude from '@amplitude/analytics-browser';
import {PluginType} from '@amplitude/analytics-types';
class FilterEventsPlugin {
name = 'remove-PII-plugin';
type = PluginType.ENRICHMENT;
async setup(config) {
return undefined;
}
async execute(event) {
// remove PII on the event
if(event.user_properties['phone']) {
delete event.user_properties['phone'];
// set a new prop to mark this event as modified
event.event_properties['pii-removed'] = true;
}
// return modified event with PII removed
return event
}
}
amplitude.init('API_KEY');
amplitude.add(new FilterEventsPlugin());
import * as amplitude from '@amplitude/analytics-browser';
import { EnrichmentPlugin, BrowserConfig, PluginType, Event } from '@amplitude/analytics-types';
class FilterEventsPlugin implements EnrichmentPlugin {
name = 'remove-PII-plugin';
type = PluginType.ENRICHMENT as any;
async setup(config: BrowserConfig): Promise<void> {
return undefined;
}
async execute(event: Event): Promise<Event> {
// remove PII on the event
if(event.user_properties['phone']) {
delete event.user_properties['phone'];
// set a new prop to mark this event as modified
event.event_properties['pii-removed'] = true;
}
// return modified event with PII removed
return event
}
}
amplitude.add(new FilterEventsPlugin());
amplitude.init('API_KEY');
Send event level groups using Ampli v2 (click to expand)
This is an example of how to send event level groups in Ampli V2. How to send event level groups in SDKs(not in Ampli) is different. Please check the specific SDKs for the usage.
import * as amplitude from '@amplitude/analytics-browser';
import {PluginType} from '@amplitude/analytics-types';
class EventLevelGroupPlugin {
name = 'group-plugin';
type = PluginType.ENRICHMENT;
async setup(config) {
return undefined;
}
async execute(event) {
event.groups = event.extra['groups'];
return event;
}
// Allow other events to be processed and sent to destination plugins
return event;
}
ampli.client.add(new EventLevelGroupPlugin());
const extra = {extra: { groups: ["test_group_name": "test_group_value"]}};
ampli.eventWithGroups({requiredNumber: 1.23, requiredBoolean: false}, extra);
import { EnrichmentPlugin, BrowserConfig, PluginType, Event } from '@amplitude/analytics-types';
class EventLevelGroupPlugin implements EnrichmentPlugin {
name = 'group-plugin';
type = PluginType.ENRICHMENT as any;
async setup(config: BrowserConfig): Promise<void> {
return undefined;
}
async execute(event: Event): Promise<Event> {
event.groups = event.extra['groups'];
return event;
}
}
ampli.client.add(new EventLevelGroupPlugin());
// Pass the event level groups info though middleware extra when calling the tracking plan.
const extra = {extra: { groups: ["test_group_name": "test_group_value"]}};
ampli.eventWithGroups({requiredNumber: 1.23, requiredBoolean: false}, extra);
Destination type plugin¶
Use a Destination Plugin to send events to a third-party APIs
Destination plugin examples
Send to Segment (click to expand)
Follow Segment's guide to install Segment Analytics.js 2.0 Web SDK first.
import { AnalyticsBrowser } from '@segment/analytics-next';
import { Types } from '@amplitude/analytics-browser';
export default class SegmentPlugin {
name = 'segment';
type = Types.PluginType.DESTINATION;
constructor(private readonly writeKey) {
// Create Segment tracker
this.segment = new AnalyticsBrowser();
}
async setup(config) {
this.segment.load({
writeKey: this.writeKey,
});
return;
}
execute(context) {
return new Promise(resolve => {
const {
event_type,
event_properties,
user_id,
user_properties,
groups,
group_properties,
} = context;
const callback = (ctx) => {
resolve({ event: context, code: 200, message: '' });
};
switch (event_type) {
case Types.SpecialEventType.IDENTIFY:
case Types.SpecialEventType.GROUP_IDENTIFY:
const groupValues = groups ? Object.values(groups) : [];
if (groupValues.length === 0) {
this.segment.identify(
user_id,
user_properties?.[Types.IdentifyOperation.SET],
{},
callback,
);
} else {
this.segment.group(
groupValues[0],
group_properties?.[Types.IdentifyOperation.SET],
{},
callback,
);
}
break;
case 'page':
// @ts-ignore
const { name, category, ...properties } = event_properties;
this.segment.page(category, name, properties, {}, callback);
break;
default:
this.segment.track(event_type, event_properties, {}, callback);
break;
}
});
}
}
import { AnalyticsBrowser } from '@segment/analytics-next';
import { Types } from '@amplitude/analytics-browser';
export default class SegmentPlugin implements Types.DestinationPlugin {
name = 'segment';
type = Types.PluginType.DESTINATION as any;
segment: AnalyticsBrowser;
constructor(private readonly writeKey: string) {
// Create Segment tracker
this.segment = new AnalyticsBrowser();
}
async setup(config: Types.Config): Promise<undefined> {
this.segment.load({
writeKey: this.writeKey,
});
return;
}
execute(context: Types.Event): Promise<Types.Result> {
return new Promise<Types.Result>(resolve => {
const {
event_type,
event_properties,
user_id,
user_properties,
groups,
group_properties,
} = context;
const callback = (ctx: any) => {
resolve({ event: context, code: 200, message: '' });
};
switch (event_type) {
case Types.SpecialEventType.IDENTIFY:
case Types.SpecialEventType.GROUP_IDENTIFY:
const groupValues = groups ? Object.values(groups) : [];
if (groupValues.length === 0) {
this.segment.identify(
user_id,
user_properties?.[Types.IdentifyOperation.SET],
{},
callback,
);
} else {
this.segment.group(
groupValues[0],
group_properties?.[Types.IdentifyOperation.SET],
{},
callback,
);
}
break;
case 'page':
// @ts-ignore
const { name, category, ...properties } = event_properties;
this.segment.page(category, name, properties, {}, callback);
break;
default:
this.segment.track(event_type, event_properties, {}, callback);
break;
}
});
}
}
Send to Hotjar using their tracking code (click to expand)
import { PluginType } from "@amplitude/analytics-types"
import { default as hj } from "@hotjar/browser"
export class HotjarPlugin {
name = "hotjar"
type = PluginType.DESTINATION
constructor(siteId, hotjarVersion, debug = false) {
this.siteId = siteId
this.hotjarVersion = hotjarVersion
}
async setup() {
hj.init(this.siteId, this.hotjarVersion)
}
async execute(event) {
if (event.event_type === "$identify") {
const { user_id, device_id, user_properties } = event
const hotjarId = user_id || device_id || ""
hj.identify(hotjarId, user_properties || {})
} else {
hj.event(event.event_type)
}
return {
code: 0,
event: event,
message: "Event forwarded to Hotjar SDK"
}
}
}
import { BrowserConfig, DestinationPlugin, Event, PluginType, Result } from '@amplitude/analytics-types';
import { default as hj } from '@hotjar/browser';
export class HotjarPlugin implements DestinationPlugin {
name = 'hotjar';
type = PluginType.DESTINATION as const;
siteId: number;
hotjarVersion: number;
constructor(siteId: number, hotjarVersion: number, debug: boolean = false) {
this.siteId = siteId;
this.hotjarVersion = hotjarVersion;
}
async setup(): Promise<void> {
hj.init(this.siteId, this.hotjarVersion);
}
async execute(event: Event): Promise<Result> {
if (event.event_type === '$identify') {
const { user_id, device_id, user_properties } = event;
const hotjarId = user_id || device_id || '';
hj.identify(hotjarId, user_properties || {});
} else {
hj.event(event.event_type);
}
return {
code: 0,
event: event,
message: 'Event forwarded to Hotjar API',
};
}
}
Send to Google Tag Manager by pushing events onto the data layer (click to expand)
import { PluginType } from "@amplitude/analytics-types"
export class GTMPlugin {
name = "google-tag-manager"
type = PluginType.DESTINATION
constructor(containerId) {
this.containerId = containerId
}
async setup() {
if (!window.dataLayer) {
window.dataLayer = window.dataLayer || []
window.dataLayer.push({
"gtm.start": new Date().getTime(),
event: "gtm.js"
})
const head = document.getElementsByTagName("head")[0],
script = document.createElement("script");
script.async = true
script.src =
`https://www.googletagmanager.com/gtm.js?id=${this.containerId}&l=datalayer`
head.insertBefore(script, head.firstChild)
}
}
async execute(event) {
window.dataLayer.push(event)
return {
code: 200,
event: event,
message: "Event pushed onto GTM Data Layer"
}
}
}
import { DestinationPlugin, Event, PluginType, Result } from '@amplitude/analytics-types';
export class GTMPlugin implements DestinationPlugin {
name = 'google-tag-manager';
type = PluginType.DESTINATION as const;
containerId: string;
constructor(containerId: string) {
this.containerId = containerId;
}
async setup(): Promise<void> {
if (!window.dataLayer) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
const head = document.getElementsByTagName('head')[0],
script = document.createElement('script'),
dataLayer = 'datalayer' != 'dataLayer' ? '&l=' + 'datalayer' : '';
script.async = true;
script.src = 'https://www.googletagmanager.com/gtm.js?id=' + this.containerId + dataLayer;
head.insertBefore(script, head.firstChild);
}
}
async execute(event: Event): Promise<Result> {
window.dataLayer.push(event);
return {
code: 200,
event: event,
message: 'Event pushed onto GTM Data Layer',
};
}
}
Send to Fullstory by forwarding events to their browser SDK (click to expand)
import { PluginType } from '@amplitude/analytics-types';
export class FullstoryPlugin {
constructor(fsOrg) {
this.name = 'fullstory';
this.type = PluginType.DESTINATION;
this.fsOrg = fsOrg;
this.FS = window.FS;
}
async setup() {
window._fs_host || (window._fs_host = "fullstory.com", window._fs_script = "edge.fullstory.com/s/fs.js", window._fs_org = this.fsOrg, window._fs_namespace = "FS", function (n, t, e, o, s, c, i, f) { e in n ? n.console && n.console.log && n.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].') : ((i = n[e] = function (n, t, e) { i.q ? i.q.push([n, t, e]) : i._api(n, t, e); }).q = [], (c = t.createElement(o)).async = 1, c.crossOrigin = "anonymous", c.src = "https://" + _fs_script, (f = t.getElementsByTagName(o)[0]).parentNode.insertBefore(c, f), i.identify = function (n, t, e) { i(s, { uid: n }, e), t && i(s, t, e); }, i.setUserVars = function (n, t) { i(s, n, t); }, i.event = function (n, t, e) { i("event", { n: n, p: t }, e); }, i.anonymize = function () { i.identify(!1); }, i.shutdown = function () { i("rec", !1); }, i.restart = function () { i("rec", !0); }, i.log = function (n, t) { i("log", [n, t]); }, i.consent = function (n) { i("consent", !arguments.length || n); }, i.identifyAccount = function (n, t) { c = "account", (t = t || {}).acctId = n, i(c, t); }, i.clearUserCookie = function () { }, i.setVars = function (n, t) { i("setVars", [n, t]); }, i._w = {}, f = "XMLHttpRequest", i._w[f] = n[f], f = "fetch", i._w[f] = n[f], n[f] && (n[f] = function () { return i._w[f].apply(this, arguments); }), i._v = "1.3.0"); }(window, document, window._fs_namespace, "script", "user"));
this.FS = window.FS;
}
async execute(event) {
if (event.event_type === '$identify') {
this.FS.identify(event.user_id);
}
else {
this.FS.event(event.event_type, event.event_properties);
}
return {
code: 200,
event: event,
message: 'Event forwarded to Fullstory',
};
}
}
import { DestinationPlugin, Event, PluginType, Result } from '@amplitude/analytics-types';
export class FullstoryPlugin implements DestinationPlugin {
name = 'fullstory';
type = PluginType.DESTINATION as const;
fsOrg: string;
FS: Object
constructor(fsOrg: string) {
this.fsOrg = fsOrg;
this.FS = window.FS;
}
async setup(): Promise<void> {
window._fs_host||(window._fs_host="fullstory.com",window._fs_script="edge.fullstory.com/s/fs.js",window._fs_org=this.fsOrg,window._fs_namespace="FS",function(n,t,e,o,s,c,i,f){e in n?n.console&&n.console.log&&n.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].'):((i=n[e]=function(n,t,e){i.q?i.q.push([n,t,e]):i._api(n,t,e)}).q=[],(c=t.createElement(o)).async=1,c.crossOrigin="anonymous",c.src="https://"+_fs_script,(f=t.getElementsByTagName(o)[0]).parentNode.insertBefore(c,f),i.identify=function(n,t,e){i(s,{uid:n},e),t&&i(s,t,e)},i.setUserVars=function(n,t){i(s,n,t)},i.event=function(n,t,e){i("event",{n:n,p:t},e)},i.anonymize=function(){i.identify(!1)},i.shutdown=function(){i("rec",!1)},i.restart=function(){i("rec",!0)},i.log=function(n,t){i("log",[n,t])},i.consent=function(n){i("consent",!arguments.length||n)},i.identifyAccount=function(n,t){c="account",(t=t||{}).acctId=n,i(c,t)},i.clearUserCookie=function(){},i.setVars=function(n,t){i("setVars",[n,t])},i._w={},f="XMLHttpRequest",i._w[f]=n[f],f="fetch",i._w[f]=n[f],n[f]&&(n[f]=function(){return i._w[f].apply(this,arguments)}),i._v="1.3.0")}(window,document,window._fs_namespace,"script","user"));
this.FS = window.FS;
}
async execute(event: Event): Promise<Result> {
if (event.event_type === '$identify') {
this.FS.identify(event.user_id)
} else {
this.FS.event(event.event_type, event.event_properties)
}
return {
code: 200,
event: event,
message: 'Event forwarded to Fullstory',
};
}
}