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.
A plugin is an object with methods setup()
and execute()
:
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()
.
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.
1amplitude.add(yourPlugin())
If execute()
doesn't returns an event, the event will NOT propagate through the remaining plugins
Enrichment plugins modify properties in Event objects or drop an Event. Here are the available keys for Event Object which you can enrich in the Enrichment Plugin.
1import * as amplitude from '@amplitude/analytics-browser'; 2 3import {PluginType} from '@amplitude/analytics-types'; 4 5class FilterEventsPlugin { 6 name = 'filter-events-plugin'; 7 type = PluginType.ENRICHMENT; 8 9 async setup(config) {10 return undefined;11 }12 13 async execute(event) {14 // ignore events with a certain property15 if (event.event_properties['ignore'] === true){16 // returning null will prevent this event from being processed by subsequent plugins17 return null;18 }19 20 // Allow other events to be processed and sent to destination plugins21 return event;22 }23}24 25amplitude.add(new FilterEventsPlugin());26amplitude.init('API_KEY');
1import * as amplitude from '@amplitude/analytics-browser'; 2 3import { EnrichmentPlugin, BrowserConfig, PluginType, Event } from '@amplitude/analytics-types'; 4 5class FilterEventsPlugin implements EnrichmentPlugin { 6 name = 'filter-events-plugin'; 7 type = PluginType.ENRICHMENT as any; 8 9 async setup(config: BrowserConfig): Promise<void> {10 return undefined;11 }12 13 async execute(event: Event): Promise<Event | null> {14 // ignore events with a certain property15 if (event.event_properties['ignore'] === true){16 // returning null will prevent this event from being processed by subsequent plugins17 return null;18 }19 20 // Allow other events to be processed and sent to destination plugins21 return event;22 }23}24 25amplitude.add(new FilterEventsPlugin());26amplitude.init('API_KEY');
1import * as amplitude from '@amplitude/analytics-browser'; 2 3import {PluginType} from '@amplitude/analytics-types'; 4 5class PropertiesEnrichmentPlugin { 6 name = 'properties-plugin'; 7 type = PluginType.ENRICHMENT; 8 9 async setup(_, amplitude) {10 if (shouldSetUserProperties) {11 const identifyEvent = new amplitude.Identify();12 identifyEvent.set("testKey", "testValue");13 amplitude.identify(identifyEvent);14 }15 return undefined;16 }17 18 async execute(event) {19 return event20 }21}22 23amplitude.add(new PropertiesEnrichmentPlugin());24amplitude.init('API_KEY');
1import * as amplitude from '@amplitude/analytics-browser'; 2 3import { EnrichmentPlugin, BrowserConfig, PluginType, Event } from '@amplitude/analytics-types'; 4 5class PropertiesEnrichmentPlugin implements EnrichmentPlugin { 6 name = 'filter-events-plugin'; 7 type = PluginType.ENRICHMENT as any; 8 9 async setup(config: BrowserConfig, amplitude: Amplitude): Promise<void> {10 if (shouldSetUserProperties) {11 const identifyEvent = new amplitude.Identify();12 identifyEvent.set("testKey", "testValue");13 amplitude.identify(identifyEvent);14 }15 return undefined;16 }17 18 async execute(event: Event): Promise<Event> {19 return event20 }21}22 23amplitude.add(new PropertiesEnrichmentPlugin());24amplitude.init('API_KEY');
1import * as amplitude from '@amplitude/analytics-browser'; 2import {PluginType} from '@amplitude/analytics-types'; 3 4class FilterEventsPlugin { 5 name = 'remove-PII-plugin'; 6 type = PluginType.ENRICHMENT; 7 8 async setup(config) { 9 return undefined;10 }11 12 async execute(event) {13 // remove PII on the event14 if(event.user_properties['phone']) {15 delete event.user_properties['phone'];16 17 // set a new prop to mark this event as modified18 event.event_properties['pii-removed'] = true;19 }20 21 // return modified event with PII removed22 return event23 }24}25 26amplitude.init('API_KEY');27amplitude.add(new FilterEventsPlugin());
1import * as amplitude from '@amplitude/analytics-browser'; 2 3import { EnrichmentPlugin, BrowserConfig, PluginType, Event } from '@amplitude/analytics-types'; 4 5class FilterEventsPlugin implements EnrichmentPlugin { 6 name = 'remove-PII-plugin'; 7 type = PluginType.ENRICHMENT as any; 8 9 async setup(config: BrowserConfig): Promise<void> {10 return undefined;11 }12 13 async execute(event: Event): Promise<Event> {14 // remove PII on the event15 if(event.user_properties['phone']) {16 delete event.user_properties['phone'];17 18 // set a new prop to mark this event as modified19 event.event_properties['pii-removed'] = true;20 }21 22 // return modified event with PII removed23 return event24 }25}26 27amplitude.add(new FilterEventsPlugin());28amplitude.init('API_KEY');
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.
1import * as amplitude from '@amplitude/analytics-browser'; 2 3import {PluginType} from '@amplitude/analytics-types'; 4 5class EventLevelGroupPlugin { 6 name = 'group-plugin'; 7 type = PluginType.ENRICHMENT; 8 9 async setup(config) {10 return undefined;11 }12 13 async execute(event) {14 event.groups = event.extra['groups'];15 return event;16 }17 18 // Allow other events to be processed and sent to destination plugins19 return event;20}21 22ampli.client.add(new EventLevelGroupPlugin());23 24const extra = {extra: { groups: ["test_group_name": "test_group_value"]}};25ampli.eventWithGroups({requiredNumber: 1.23, requiredBoolean: false}, extra);
1import { EnrichmentPlugin, BrowserConfig, PluginType, Event } from '@amplitude/analytics-types'; 2 3class EventLevelGroupPlugin implements EnrichmentPlugin { 4 name = 'group-plugin'; 5 type = PluginType.ENRICHMENT as any; 6 7 async setup(config: BrowserConfig): Promise<void> { 8 return undefined; 9 }10 11 async execute(event: Event): Promise<Event> {12 event.groups = event.extra['groups'];13 return event;14 }15}16 17ampli.client.add(new EventLevelGroupPlugin());18 19// Pass the event level groups info though middleware extra when calling the tracking plan.20const extra = {extra: { groups: ["test_group_name": "test_group_value"]}};21ampli.eventWithGroups({requiredNumber: 1.23, requiredBoolean: false}, extra);
Use a Destination Plugin to send events to a third-party APIs
Follow Segment's guide to install Segment Analytics.js 2.0 Web SDK first.
1import { AnalyticsBrowser } from '@segment/analytics-next'; 2import { Types } from '@amplitude/analytics-browser'; 3 4export default class SegmentPlugin { 5 name = 'segment'; 6 type = Types.PluginType.DESTINATION; 7 8 constructor(private readonly writeKey) { 9 // Create Segment tracker10 this.segment = new AnalyticsBrowser();11 }12 13 async setup(config) {14 this.segment.load({15 writeKey: this.writeKey,16 });17 return;18 }19 20 execute(context) {21 return new Promise(resolve => {22 const {23 event_type,24 event_properties,25 user_id,26 user_properties,27 groups,28 group_properties,29 } = context;30 const callback = (ctx) => {31 resolve({ event: context, code: 200, message: '' });32 };33 34 switch (event_type) {35 case Types.SpecialEventType.IDENTIFY:36 case Types.SpecialEventType.GROUP_IDENTIFY:37 const groupValues = groups ? Object.values(groups) : [];38 if (groupValues.length === 0) {39 this.segment.identify(40 user_id,41 user_properties?.[Types.IdentifyOperation.SET],42 {},43 callback,44 );45 } else {46 this.segment.group(47 groupValues[0],48 group_properties?.[Types.IdentifyOperation.SET],49 {},50 callback,51 );52 }53 break;54 55 case 'page':56 // @ts-ignore57 const { name, category, ...properties } = event_properties;58 this.segment.page(category, name, properties, {}, callback);59 break;60 61 default:62 this.segment.track(event_type, event_properties, {}, callback);63 break;64 }65 });66 }67}
1import { AnalyticsBrowser } from '@segment/analytics-next'; 2import { Types } from '@amplitude/analytics-browser'; 3 4export default class SegmentPlugin implements Types.DestinationPlugin { 5 name = 'segment'; 6 type = Types.PluginType.DESTINATION as any; 7 segment: AnalyticsBrowser; 8 9 constructor(private readonly writeKey: string) {10 // Create Segment tracker11 this.segment = new AnalyticsBrowser();12 }13 14 async setup(config: Types.Config): Promise<undefined> {15 this.segment.load({16 writeKey: this.writeKey,17 });18 return;19 }20 21 execute(context: Types.Event): Promise<Types.Result> {22 return new Promise<Types.Result>(resolve => {23 const {24 event_type,25 event_properties,26 user_id,27 user_properties,28 groups,29 group_properties,30 } = context;31 const callback = (ctx: any) => {32 resolve({ event: context, code: 200, message: '' });33 };34 35 switch (event_type) {36 case Types.SpecialEventType.IDENTIFY:37 case Types.SpecialEventType.GROUP_IDENTIFY:38 const groupValues = groups ? Object.values(groups) : [];39 if (groupValues.length === 0) {40 this.segment.identify(41 user_id,42 user_properties?.[Types.IdentifyOperation.SET],43 {},44 callback,45 );46 } else {47 this.segment.group(48 groupValues[0],49 group_properties?.[Types.IdentifyOperation.SET],50 {},51 callback,52 );53 }54 break;55 56 case 'page':57 // @ts-ignore58 const { name, category, ...properties } = event_properties;59 this.segment.page(category, name, properties, {}, callback);60 break;61 62 default:63 this.segment.track(event_type, event_properties, {}, callback);64 break;65 }66 });67 }68}
Before you begin, see Hotjar's tracking code.
1import { PluginType } from "@amplitude/analytics-types" 2import { default as hj } from "@hotjar/browser" 3export class HotjarPlugin { 4 name = "hotjar" 5 type = PluginType.DESTINATION 6 constructor(siteId, hotjarVersion, debug = false) { 7 this.siteId = siteId 8 this.hotjarVersion = hotjarVersion 9 }10 async setup() {11 hj.init(this.siteId, this.hotjarVersion)12 }13 async execute(event) {14 if (event.event_type === "$identify") {15 const { user_id, device_id, user_properties } = event16 const hotjarId = user_id || device_id || ""17 hj.identify(hotjarId, user_properties || {})18 } else {19 hj.event(event.event_type)20 }21 return {22 code: 0,23 event: event,24 message: "Event forwarded to Hotjar SDK"25 }26 }27}
1import { BrowserConfig, DestinationPlugin, Event, PluginType, Result } from '@amplitude/analytics-types'; 2import { default as hj } from '@hotjar/browser'; 3export class HotjarPlugin implements DestinationPlugin { 4 name = 'hotjar'; 5 type = PluginType.DESTINATION as const; 6 siteId: number; 7 hotjarVersion: number; 8 9 constructor(siteId: number, hotjarVersion: number, debug: boolean = false) {10 this.siteId = siteId;11 this.hotjarVersion = hotjarVersion;12 }13 14 async setup(): Promise<void> {15 hj.init(this.siteId, this.hotjarVersion);16 }17 18 async execute(event: Event): Promise<Result> {19 if (event.event_type === '$identify') {20 const { user_id, device_id, user_properties } = event;21 const hotjarId = user_id || device_id || '';22 hj.identify(hotjarId, user_properties || {});23 } else {24 hj.event(event.event_type);25 }26 return {27 code: 0,28 event: event,29 message: 'Event forwarded to Hotjar API',30 };31 }32}
Before you begin, see more information about Google's Data Layer
1import { PluginType } from "@amplitude/analytics-types" 2 3export class GTMPlugin { 4 name = "google-tag-manager" 5 type = PluginType.DESTINATION 6 7 constructor(containerId) { 8 this.containerId = containerId 9 }10 11 async setup() {12 if (!window.dataLayer) {13 window.dataLayer = window.dataLayer || []14 window.dataLayer.push({15 "gtm.start": new Date().getTime(),16 event: "gtm.js"17 })18 const head = document.getElementsByTagName("head")[0],19 script = document.createElement("script");20 script.async = true21 script.src =22 `https://www.googletagmanager.com/gtm.js?id=${this.containerId}&l=datalayer`23 head.insertBefore(script, head.firstChild)24 }25 }26 27 async execute(event) {28 window.dataLayer.push(event)29 30 return {31 code: 200,32 event: event,33 message: "Event pushed onto GTM Data Layer"34 }35 }36}
1import { DestinationPlugin, Event, PluginType, Result } from '@amplitude/analytics-types'; 2 3export class GTMPlugin implements DestinationPlugin { 4 name = 'google-tag-manager'; 5 type = PluginType.DESTINATION as const; 6 containerId: string; 7 8 constructor(containerId: string) { 9 this.containerId = containerId;10 }11 12 async setup(): Promise<void> {13 if (!window.dataLayer) {14 window.dataLayer = window.dataLayer || [];15 window.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });16 const head = document.getElementsByTagName('head')[0],17 script = document.createElement('script'),18 dataLayer = 'datalayer' != 'dataLayer' ? '&l=' + 'datalayer' : '';19 script.async = true;20 script.src = 'https://www.googletagmanager.com/gtm.js?id=' + this.containerId + dataLayer;21 head.insertBefore(script, head.firstChild);22 }23 }24 25 async execute(event: Event): Promise<Result> {26 window.dataLayer.push(event);27 28 return {29 code: 200,30 event: event,31 message: 'Event pushed onto GTM Data Layer',32 };33 }34}
Before you begin, see information about FullStory's browser SDK.
1import { PluginType } from '@amplitude/analytics-types'; 2 3export class FullstoryPlugin { 4 constructor(fsOrg) { 5 this.name = 'fullstory'; 6 this.type = PluginType.DESTINATION; 7 this.fsOrg = fsOrg; 8 this.FS = window.FS; 9 }10 11 async setup() {12 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"));13 this.FS = window.FS;14 }15 16 async execute(event) {17 if (event.event_type === '$identify') {18 this.FS.identify(event.user_id);19 }20 else {21 this.FS.event(event.event_type, event.event_properties);22 }23 return {24 code: 200,25 event: event,26 message: 'Event forwarded to Fullstory',27 };28 }29}
1import { DestinationPlugin, Event, PluginType, Result } from '@amplitude/analytics-types'; 2 3export class FullstoryPlugin implements DestinationPlugin { 4 name = 'fullstory'; 5 type = PluginType.DESTINATION as const; 6 fsOrg: string; 7 FS: Object 8 9 constructor(fsOrg: string) {10 this.fsOrg = fsOrg;11 this.FS = window.FS;12 }13 14 async setup(): Promise<void> {15 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"));16 this.FS = window.FS;17 }18 19 async execute(event: Event): Promise<Result> {20 if (event.event_type === '$identify') {21 this.FS.identify(event.user_id)22 23 } else {24 this.FS.event(event.event_type, event.event_properties)25 }26 27 return {28 code: 200,29 event: event,30 message: 'Event forwarded to Fullstory',31 };32 }33}
Thanks for your feedback!
November 8th, 2024
Need help? Contact Support
Visit Amplitude.com
Have a look at the Amplitude Blog
Learn more at Amplitude Academy
© 2024 Amplitude, Inc. All rights reserved. Amplitude is a registered trademark of Amplitude, Inc.