# Uniform plugins

> Plugins can extend tracking capabilities, and are added to the Uniform context object.

Source: https://docs.uniform.app/docs/guides/classification/plugins

Plugins can extend tracking capabilities, and are added to the Uniform context object.

## Standard plugins

Uniform comes with the following plugins. These plugins aren't
enabled by default. You must enable them by adding the required
plugins to the Uniform context object.

### Context DevTools

The Context DevTools plugin causes the Uniform tracker to expose
visitor classification data that the Context DevTools can display.
For information on how to configure and use this plugin, see the
[Context DevTools guide](./context-devtools).

1. Add the following import at the top of the file:

   ```javascript
   import { enableContextDevTools } from "@uniformdev/context";
   ```
2. Find the code where the Uniform context object is created.
   It will look something like the following:

   ```javascript
   const context = new Context({ 
     manifest,
   });
   ```
3. Add the following code:

   ```javascript
   const context = new Context({ 
     manifest,
     plugins: [
       enableContextDevTools(),
     ]
   });
   ```

### Logging

The console log plugin causes the Uniform tracker to write to the console.

1. Add the following import at the top of the file:

   ```javascript
   import { createConsoleLogDrain } from "@uniformdev/context";
   ```
2. Find the code where the Uniform context object is created.
   It will look something like the following:

   ```javascript
   const context = new Context({ 
     manifest,
   });
   ```
3. Add the following code:

   ```javascript
   const context = new Context({ 
     manifest,
     plugins: [
       createConsoleLogDrain("debug"),
     ]
   });
   ```

   > **Note:**
   >
   > The example above sets the logging output severity level to
   > `debug`. For information on other severity levels, see the
   > [output severity reference](https://sdk.uniform.app/context.outputseverity).

## Custom plugins

The Uniform tracker emits events at various points as it controls
classification, personalization, and other processes. You can assign
listeners directly to the tracker to handle these events, but custom
plugins offer a more reusable option.

This section provides examples of custom plugins.

### Visitor scores

This example demonstrates how visitor scores can be sent
to a third-party API. In this example, the Uniform tracker
maintains an enrichment `role`. Depending on the content the
visitor views, they may have scores for multiple roles. This
plug-in sends the name of the role with highest score to the
third-party API.

1. Find the code where the Uniform context object is created.
   It will look something like the following:

   ```javascript
   const context = new Context({ 
     manifest,
   });
   ```
2. Add the following code to the file where the Uniform context object is created:

   ```js
   function enableSendScores() {
     return {
       init: (context) => {
         const isRoleEnrichment = (key) => key.split("_")[0] == "role";
         const getRoleName = (key) => key.split("_")[1];
         //
         //Returns the name of the role with the largest score.
         const getRole = (scores) => {
           let role = undefined;
           Object.keys(scores).forEach(key => {
             if (!isRoleEnrichment(key)) return;
             const score = scores[key];
             if (score > 0 && !role) {
               role = key;
             }
             else if (score > scores[role]) {
               role = key;
             }
           });
           if (!role) return;
           return getRoleName(role);
         }
         //
         //Listen for the context event.
         context.events.on("scoresUpdated", (scores) => {
           const role = getRole(scores);
           if (!role) return;
           //
           //TODO: make API call using the role value.
         });
       },
     }
   }
   ```

   > **Note:**
   >
   > By convention, a function is used to define the plugin. For
   > information on the contract for a Context plugin, see the
   > [ContextPlugin reference](https://sdk.uniform.app/context.contextplugin).
3. Add the following code:

   ```javascript
   const context = new Context({ 
     manifest,
     plugins: [
       enableSendScores(),
     ]
   });
   ```

### Custom plugin example

Here is an example of a custom plugin for Google Analytics.

```jsx
import { ContextPlugin, PersonalizationEvent, TestEvent } from '@uniformdev/context';
declare global {
  interface Window {
    __UF_PATCHED_GTAG__?: boolean;
  }
}
export const enableGoogleGtagAnalytics = (options?: {
  /** If true, disables reflecting events passed to gtag into Uniform Context as event signals */
  disableEventSignals?: boolean;
  /** Override the window object that will have the gtag property added to it */
  theWindow?: { gtag?: Gtag.Gtag; __UF_PATCHED_GTAG__?: boolean };
  /** Whether to emit every personalize and test that occurs, or only if something has actually changed
   * false (default): only emit events when a test assigns a variant the first time, or a personalization chooses different variation than before
   * true: emit an event every time a personalization or test is evaluated (e.g. for vDOM frameworks, each time a re-render occurs)
   */
  emitAll?: boolean;
}): ContextPlugin => {
  const {
    disableEventSignals,
    theWindow = typeof window !== 'undefined' ? window : undefined,
    emitAll,
  } = options || {};

  const isGtagConfigured = (): boolean => {
    return typeof theWindow !== 'undefined' && typeof theWindow.gtag === 'function';
  };

  return {
    init: (context) => {
      // Handle emitting personalization results to GA4
      const onPersonalizationResult = (result: PersonalizationEvent) => {
        if (!isGtagConfigured()) {
          context.log('warn', 700);
          return;
        }

        // only emit analytics events when the placement actually changes
        // (e.g. not every re-render)
        if (!emitAll && !result.changed) {
          return;
        }

        theWindow!.gtag?.('event', result.name, {
          event_category: 'Uniform Personalization',
          event_label: result.variantIds.join(', '),
          is_control_group: result.control ? 1 : 0,
        });
      };

      // Handle emitting test results to GA4
      const onTestResult = (result: TestEvent) => {
        if (!isGtagConfigured()) {
          context.log('warn', 700);
          return;
        }

        // only emit analytics events when the placement actually changes
        // (e.g. not every re-render)
        if (!emitAll && !result.variantAssigned) {
          return;
        }

        theWindow!.gtag?.('event', result.name, {
          event_category: 'Uniform AB Testing',
          event_label: result.variantId ?? 'No Variation',
        });
      };

      context.events.on('personalizationResult', onPersonalizationResult);
      context.events.on('testResult', onTestResult);

      // Handle redirecting events from GA4 to event signals in Uniform Context
      if (!disableEventSignals && isGtagConfigured() && !theWindow?.__UF_PATCHED_GTAG__) {
        const defaultGtag = theWindow!.gtag;
        theWindow!.gtag = (...args: any[]) => {
          // @ts-ignore - we're passing the args through to the default gtag
          defaultGtag(...args);

          // if we get an event passed to gtag, emit it to Context.
          // Except our own test and personalization tracking events
          if (
            args[0] === 'event' &&
            !['Uniform AB Testing', 'Uniform Personalization'].includes(args[2]?.event_category)
          ) {
            context.update({ events: [{ event: args[1] }] });
          }
        };
        context.log('debug', 701);
        theWindow!.__UF_PATCHED_GTAG__ = true;
      }

      return () => {
        context.events.off('personalizationResult', onPersonalizationResult);
        context.events.off('testResult', onTestResult);
      };
    },
  };
};
```
