User behavior tracker

Main git repository with source code:

Scope of this repository

The purpose of this project is to provide an alternative to google analytics using javascript to store information about pages and user interactions in the form of xAPI statements. Those xAPI statements can be stored in a xAPI compatible LRS like, for example, Learning Locker.

Required files

  • JsTracking.js is the main file from this project , which exports all the functions needed to make the tracking work.

  • verbs.js is just a collection of verbs used in the xAPI statements to describe user actions.

  • xapiwrapper.min.js is a script injected automatically in the page head by JsTracking.js. This is needed to create the window.ADL object, which provide all the functions needed to store xAPI data in the LRS.


You will need to set several configuration values in order to communicate with the LRS where the tracking data will be stored:

    endpoint: BASE_URL + "edu/api/v1/xAPIProxy/",
    user: userId,
    password: userToken,
    lmsHomePage: ""

We have implemented (in the same backend repository as the Module Occupation Matching) a proxy to forward xAPI requests from our backend to the LRS in order to keep credentials safe.

  • userId: logged-in user ID.

  • userToken: logged-in user access token

How does it work

When you add this project as a package into your application you can access 2 modules:

  • jsTrackingFunctions which contains all the functions you will need to call to track in your platform. Those functions are:

    • initXapiTrack - This function initializes the LRS and other configurations needed to start tracking. It is an asynchronous function, and it is needed to be called before any other tracking function. It receives one parameter, an object like the one in the section above with the lrs configuration.

    • sendXapiStatement - This one triggers a tracking event, which is stored in the LRS as a statement. It receives a parameter with the name of the event, for example: viewed, progressed, responded, log in...

  • verbsList - This is a group of constants for the available verbs. For example if you want to trigger a viewed event you can use the constant verbsList.viewed.key to access the name of the event.

Some verbs have a callback function to process some extra data, for example when tracking a search event you can include in the xAPI data what the user was looking for and even filters applied to the search. This callback function is optional but each verb will expect different objects in this function.

Before sending any tracking event the identifier of the logged-in user must be stored in window.JsTrackingUserLogged.

Every statement saved in the LRS includes data from the current page. Also, if the page has structured data with format in a application/ld+json script all this data is stored inside the statement.

How to extend and customize statements

To modify statements is as simple as editing the file verbs.js.

How to create new verbs or modify existing ones

Verbs have 3 basic properties:

  • id: A unique identifier of the verb (it needs to be a URL).

  • key: Internal name used when you call the sendXapiStatement function.

  • display: The name displayed on the LRS.

With this in mind you can modify properties of the existing verbs on the file or just add a new one.

First it is recommended to check if there is already a verb on the xAPI registry that fits your case. If you need or want to create your own custom verb you can do it but remember to use always a URL as the id. We always use the same URLs under our own domain, but you can use your own.

To create a new verb just add one JSON object like this on the verbs file and change it as you like:

   id: "",
   display: {
      "en-US": "Custom verb"
   key: "customVerb"

Once you have added the new verb you can use it on the statements you are sending like this:


How to extend a statement

Besides the 3 basic properties of the verb we implemented a new one called customOptions.

It is a function that allows to customize the object of the statement or adding more data to the extensions section inside context.


  • You can send any parameter when you do the call of the tracking function sendXapiStatement to pass the relevant data for that event.


This function is expected to return an object with the following properties:

  • object: This will overwrite the object of the statement.

  • extensions: This will not overwrite but add them to the existing ones.

They are not mandatory but if you do not need to change any of them you should not include this function and keep the verb simple with the basic properties. Some of the already defined verbs use it. For example, the searched verb, with its own objectType and one additional extension:

const searchExtensionKey = ''
const searchObjectType = ''
searched: {
    id: '',
    display: {
      'en-US': 'searched'
    key: 'searched',
    customOptions: (options) => {
      const searchQuery = options.searchQuery || 'empty_string'
      const filters = options.filters || {}
      return {
        object: {
          id: searchObjectType + searchQuery,
          definition: {
            name: {
              'en-Us': searchQuery
            type: searchObjectType
          objectType: 'Activity'
        extensions: {
          [searchExtensionKey]: {

In the section below you can find an example of how to call the track function with custom options.


In this example we are initializing the tracking and sending a viewed event. This is storing an xAPI statement in the LRS for the user tracking-test-user with the data from the current page.

window.JsTrackingUserLogged = 'tracking-test-user'
await jsTrackingFunctions.initXapiTrack({
    endpoint: "",
    user: "64b9634d139ecf5d77ec8bf335c582c8ee374f5",
    password: "password1234",
    lmsHomePage: ""

Also, an example of an event with custom options like the search mentioned before:

const options = {
jsTrackingFunctions.sendXapiStatement(verbsList.viewed.key, options)

Where searchQuery is a string and filters is an object.

Example statement

  "authority": {
    "objectType": "Agent",
    "name": "EduPLEx Client",
    "mbox": ""
  "stored": "2022-09-26T12:06:08.158Z",
  "context": {
    "extensions": {
      "": "929f703a-9e42-4c44-897b-735bd1679aa8",
      "": {
        "hasCourseInstance": {
          "endDate": "2023-01-16T14:00:00+01:00",
          "name": "A test course",
          "startDate": "2022-08-15T01:05:00+02:00",
          "location": {
            "@type": "Place",
            "address": {
              "@type": "PostalAddress",
              "addressCountry": {
                "@type": "Country"
          "offers": {
            "@type": "Offer",
            "url": "",
            "name": "A test course",
            "availability": "InStock",
            "price": 10,
            "priceCurrency": "EUR",
            "validFrom": "2022-06-19T00:00:00+02:00"
          "@type": "CourseInstance",
          "image": "",
          "description": "Learn the basics...",
          "instructor": [],
          "courseMode": "online"
        "name": "A test course",
        "timeRequired": "PT0H30M",
        "url": "",
        "@context": "",
        "provider": {
          "@id": "",
          "@type": "Organization",
          "name": "Nordev",
          "url": ""
        "isAccessibleForFree": "",
        "@type": "Course",
        "@id": "",
        "aggregateRating": {
          "@type": "AggregateRating",
          "ratingValue": 4.67,
          "bestRating": 5,
          "reviewCount": 3
        "description": "Learn the basics...",
        "inLanguage": "en"
  "actor": {
    "account": {
      "homePage": "",
      "name": "dani-test-tracking"
    "objectType": "Agent"
  "timestamp": "2022-09-26T12:06:08.158Z",
  "version": "1.0.0",
  "id": "3a2f0cb5-2ac5-4d17-a2ad-7704785c3d63",
  "verb": {
    "id": "",
    "display": {
      "en-US": "viewed"
  "object": {
    "id": "",
    "definition": {
      "type": ""
    "objectType": "Activity"

Other considerations

In projects with webpack and babel we found a recurring error with optional chaining operator (?.), it shows something like this in build time: Module parse failed: Unexpected token You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.

It can be fixed by adding this plugin to your webpack/babel config:


Also make sure webpack transpile our code, in a nuxt project this is as simple as adding the next line inside the build property in nuxt.config.js.

transpile: ['user-behavior-tracker']


The source code for the site is licensed under the MIT license, which you can find in the LICENSE file.

xapiwrapper.min.js file has been taken from another open source repository ( which is under Apache License

Last updated