npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ember-metrics

v3.0.0

Published

Send data to multiple analytics integrations without re-implementing new API

Readme

ember-metrics

Send data to multiple analytics services without re-implementing a new API

Download count all time npm version CI Ember Observer Score

This addon adds a simple metrics service to your app that makes it easy to send data to multiple analytics services without having to implement a new API each time. Track events, page views, and more through one API, and add or remove analytics services just by changing how you activate their adapters.

Writing your own adapter for an unsupported service is easy too — see Writing your own adapters.

Upgrading from the classic (string-name) API? Adapters are now activated by passing adapter classes to metrics.activateAdapters(...) rather than by string name via config/environment. This makes the addon compatible with ember-strict-application-resolver and Embroider's static builds. See Configuration & activation.

Compatibility

  • Ember.js v5.8 or above
  • Embroider or ember-auto-import v2

Installation

ember install ember-metrics

Supported services and options

Each service ships as an adapter class you import from ember-metrics/metrics-adapters/<name> (dasherized), e.g. ember-metrics/metrics-adapters/google-analytics-four. The options below are the values you put in that adapter's config.

  1. GoogleAnalyticsember-metrics/metrics-adapters/google-analytics

  2. GoogleAnalyticsFourember-metrics/metrics-adapters/google-analytics-four

    • id: Measurement Id, e.g. G-XXXX
    • options: optional An object passed directly to the configuration tag, e.g.:
    options = {
      anonymize_ip: true,
      debug_mode: environment === 'development',
    };

    By default GA4 automatically tracks page views when the history location changes. This means this.metrics.trackPage() is ignored by default. If you want to track page views manually, set send_page_view: false inside the options. To avoid double counting, make sure enhanced measurement is configured correctly — typically disabling Page changes based on browser history events under the advanced settings of the page views section.

  3. Mixpanelember-metrics/metrics-adapters/mixpanel

  4. GoogleTagManagerember-metrics/metrics-adapters/google-tag-manager

    • id: Container ID, e.g. GTM-XXXX
    • dataLayer: An array containing a single POJO of information, e.g.:
    dataLayer = [
      {
        pageCategory: 'signup',
        visitorType: 'high-value',
      },
    ];
    • envParams: A string with custom arguments for configuring GTM environments (Live, Dev, etc), e.g.:
    envParams: 'gtm_auth=xxxxx&gtm_preview=env-xx&gtm_cookies_win=x';
  5. Segmentember-metrics/metrics-adapters/segment

  6. Piwikember-metrics/metrics-adapters/piwik

  7. Intercomember-metrics/metrics-adapters/intercom

  8. FacebookPixelember-metrics/metrics-adapters/facebook-pixel

    dataProcessingOptions: {
      method: ['LDU'],
      country: 1,
      state: 1000
    }
  9. Amplitudeember-metrics/metrics-adapters/amplitude

  10. AzureAppInsightsember-metrics/metrics-adapters/azure-app-insights

  11. Pendoember-metrics/metrics-adapters/pendo

  12. MatomoTagManagerember-metrics/metrics-adapters/matomo-tag-manager

  13. Hotjarember-metrics/metrics-adapters/hotjar

Community adapters

  1. Adobe Dynamic Tag Management

  2. Simple Analytics

Configuration & activation

The metrics service doesn't activate anything on its own — you activate adapters explicitly by passing adapter classes (not string names) to metrics.activateAdapters(...). This keeps the addon compatible with ember-strict-application-resolver and Embroider's static builds: there's no container lookup by convention, nothing depends on config:environment being registered on the owner, and only the adapters you actually import end up in your build.

A good place to do this once is your application route — it runs on boot, before anything renders:

// app/routes/application.js
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import ENV from 'my-app/config/environment';
import GoogleAnalytics from 'ember-metrics/metrics-adapters/google-analytics';
import Mixpanel from 'ember-metrics/metrics-adapters/mixpanel';

export default class ApplicationRoute extends Route {
  @service metrics;

  constructor() {
    super(...arguments);

    // Needed only if you gate adapters per environment (see `environments` below).
    this.metrics.appEnvironment = ENV.environment;

    this.metrics.activateAdapters([
      {
        name: 'GoogleAnalytics',
        adapter: GoogleAnalytics,
        environments: ['development', 'production'],
        config: {
          id: 'UA-XXXX-Y',
          // Use `analytics_debug.js` in development
          debug: ENV.environment === 'development',
          // Use verbose tracing of GA events
          trace: ENV.environment === 'development',
          // Ensure development env hits aren't sent to GA
          sendHitTask: ENV.environment !== 'development',
          // Specify Google Analytics plugins
          require: ['ecommerce'],
        },
      },
      {
        name: 'Mixpanel',
        adapter: Mixpanel,
        environments: ['production'],
        config: {
          token: '0f76c037-4d76-4fce-8a0f-a9a8f89d1453',
        },
      },
    ]);
  }
}

Anywhere that runs once early works — activateAdapters is idempotent. The application route is a good default because it needs no legacy initializer API and has clean access to the service.

Each entry in the array has the following shape:

/**
 * @param {String}   name          A label you choose, used to target this
 *                                 adapter with `invoke` and the single-adapter
 *                                 form of the convenience methods.
 * @param {Function} adapter       The adapter class itself.
 * @param {String[]} environments  Environments to activate the adapter in.
 *                                 Defaults to `['all']`.
 * @param {Object}   config        Configuration passed to the adapter's
 *                                 constructor (see the options for each
 *                                 service above).
 */
{
  name: 'GoogleAnalytics',
  adapter: GoogleAnalytics,
  environments: ['all'],
  config: {},
}

To only activate an adapter in specific environments, list them under environments and set metrics.appEnvironment (e.g. to ENV.environment) before calling activateAdapters. Valid values are whatever your app uses for its environment (commonly development, test, production), plus:

  • all — the default; the adapter is activated in every environment.

activateAdapters is idempotent: adapters already active under the same name are not reinstantiated, so it's safe to call it multiple times as more adapters or config become available (see Activating adapters later).

Content Security Policy

If you're using ember-cli-content-security-policy, you'll need to modify the content security policy to allow loading of any remote scripts. In config/environment.js, add this to the ENV hash (modify as necessary):

// example for loading Google Analytics
contentSecurityPolicy: {
  'default-src': "'none'",
  'script-src': "'self' www.google-analytics.com",
  'font-src': "'self'",
  'connect-src': "'self' www.google-analytics.com",
  'img-src': "'self'",
  'style-src': "'self'",
  'media-src': "'self'"
}

Usage

Inject the service into any object registered in the container that you wish to track from. For example, you can call trackPage across all your analytics services whenever you transition into a route:

// app/routes/application.js
import Route from '@ember/routing/route';
import { service } from '@ember/service';

export default class ApplicationRoute extends Route {
  @service metrics;
  @service router;

  constructor() {
    super(...arguments);

    this.router.on('routeDidChange', () => {
      const page = this.router.currentURL;
      const title = this.router.currentRouteName || 'unknown';

      this.metrics.trackPage({ page, title });
    });
  }
}

To only call a single service, pass its name (the label you registered it with) as the first argument:

// only invokes `trackPage` on the adapter registered as `GoogleAnalytics`
metrics.trackPage('GoogleAnalytics', {
  title: 'My Awesome App',
});

Context

Often you may want to include information like the current user's name with every event or page view that's tracked. Any properties set on metrics.context are merged into the options for every service call.

this.metrics.context.userName = 'Jimbo';
this.metrics.trackPage({ page: 'page/1' }); // { userName: 'Jimbo', page: 'page/1' }

Service API

There are four main methods, all with the same argument signature. The optional first argument targets a single adapter by its registered name; omit it to call every activated adapter.

  • trackPage([name], options)

    Commonly used to track page views. Because of how single-page apps route, you'll usually call this yourself (e.g. on routeDidChange) to track page views.

  • trackEvent([name], options)

    A general-purpose method for tracking a named event in your application.

  • identify([name], options)

    For analytics services that support identifying a user.

  • alias([name], options)

    For services that implement it, notifies the service that an anonymous user now has a unique identifier.

If an adapter implements a method beyond the four above, call it with invoke. For example, the Amplitude adapter exposes optOut/optIn:

  • invoke(method, [name], options)

    // Target a single adapter by passing its name *and* an options object — with
    // only two arguments the second is treated as options, not a name.
    metrics.invoke('optOut', 'Amplitude', {});

Adapters that don't implement the method are skipped. The [name] argument may also be an array to target several adapters at once, e.g. metrics.invoke('trackEvent', ['GoogleAnalytics', 'Mixpanel'], { ... }).

Activating adapters later

Because activateAdapters is idempotent, you can call it again at any time — for example once a dynamic API key becomes available from a model:

// app/routes/application.js
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import GoogleAnalytics from 'ember-metrics/metrics-adapters/google-analytics';

export default class ApplicationRoute extends Route {
  @service metrics;

  afterModel(model) {
    this.metrics.activateAdapters([
      {
        name: 'GoogleAnalytics',
        adapter: GoogleAnalytics,
        environments: ['all'],
        config: { id: model.googleAnalyticsKey },
      },
    ]);
  }
}

Adapters already active under the same name won't be reinstantiated, so it's safe to call this from multiple places as more adapters or config become available.

Writing your own adapters

An adapter is a class that extends BaseAdapter and implements, at minimum, install and uninstall:

// app/metrics-adapters/my-adapter.js
import BaseAdapter from 'ember-metrics/metrics-adapters/base';

export default class MyAdapter extends BaseAdapter {
  // Human-readable name used in assertions.
  toStringExtension() {
    return 'MyAdapter';
  }

  // Called when the adapter is activated. Responsible for injecting the
  // provider's script tag and initializing it. Read config off `this.config`.
  install() {
    const { apiKey } = this.config;
    // ...load and initialize the service using `apiKey`
  }

  // Called when the adapter/service is torn down. Remove the script tag and
  // any globals the service created (usually on `window`).
  uninstall() {
    // ...clean up
  }

  // Optional — implement the ones your service supports.
  identify(options) {}
  trackEvent(options) {}
  trackPage(options) {}
  alias(options) {}
}

Then register your class like any bundled adapter:

import MyAdapter from 'my-app/metrics-adapters/my-adapter';

metrics.activateAdapters([
  {
    name: 'MyAdapter',
    adapter: MyAdapter,
    environments: ['all'],
    config: { apiKey: '29fJs90qnfEa' },
  },
]);

ember-metrics ships TypeScript types. BaseAdapter, AdapterConfig, and AdapterOptions are exported from ember-metrics/metrics-adapters/base, so you can author adapters in TypeScript with full type-checking. The shape of a activateAdapters entry is exported as AdapterRegistration from ember-metrics/services/metrics.

If you build an adapter for a service others might use, consider publishing it as a community adapter and opening a PR to list it here.

Contributors

We're grateful to these wonderful people who've contributed to ember-metrics:

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.