Web analytics (JavaScript) (23.6.X)

Follow

This documentation shows how to install the Countly Web SDK and use it to track your web page in detail. It applies to the SDK version 23.6.X.

Click here, to access the documentation for older SDK versions.

Countly can run with all browsers that supports ECMAScript 5. Minimum versions of major internet browsers that fully support ES5 are:

IE Edge Firefox Firefox (Android) Opera Opera (Mobile) Safari Safari (iOS) Chrome Chrome (Android)
10 12 21 96 15 64 6 6 23 98

If you want to get the Countly Web SDK codebase locally you can go to the GitHub repo here and download it inside your project folder by executing the lines:

git clone https://github.com/Countly/countly-sdk-web.git

Additionally to see example integrations of Countly Web SDK within some popular front-end frameworks, you can reach our AngularJS and ReactJS examples from here and here respectively.

Adding the SDK to the Project

To track your web pages, you will need the Countly Web SDK (also known as the Countly JavaScript tracking library). It is automatically hosted on your Countly server as a minified UMD file (at http://yourdomain.com/sdk/web/countly.min.js) and can be updated via the command line. This library also works well with mobile applications that consist of HTML5 views.

Optionally, you may also use package managers to gain access to the SDK:

npm yarn
npm install countly-sdk-web

You can also reach the SDK through CDN:

Latest Specific Version
// latest non minified
countly.js

// latest minified
countly.min.js

Lastly as an alternative option, you may download countly.min.js from our GitHub repository and upload it to any server from where you would like to host it.

SDK Integration

Minimal Setup

You may use the Countly Web SDK synchronously or asynchronously. However the asynchronous usage would benefit from working without blocking content loading. This would also allow you to use Countly while the Countly script has not yet been loaded. This can be done by pushing function calls into the Countly.q queue.

Inserting asynchronous code before closing the "head tags" of your website is suggested, while Synchronous code should be added towards the bottom of the page before closing the head tag. Main logic here is to make the Countly load as soon as possible to start collecting data.

Here you would also need to provide your application key and server URL. Please check here for more information on how to acquire your application key (APP_KEY) and server URL.

An example setup would look like this:

Asynchronous Synchronous
<script type='text/javascript'>
  
// Some default pre init
var Countly = Countly || {};
Countly.q = Countly.q || [];

// Provide your app key that you retrieved from Countly dashboard
Countly.app_key = "YOUR_APP_KEY";

// Provide your server IP or name.
// If you use your own server, make sure you have https enabled if you use
// https below.
Countly.url = "https://yourdomain.com";

// Start pushing function calls to queue
// Track sessions automatically (recommended)
Countly.q.push(['track_sessions']);

//track web page views automatically (recommended)
Countly.q.push(['track_pageview']);

// Load Countly script asynchronously
(function() {
var cly = document.createElement('script'); cly.type = 'text/javascript';
cly.async = true;
// Enter URL of script here (see below for other option)
cly.src = 'https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.min.js';
cly.onload = function(){Countly.init()};
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(cly, s);
})();
</script>

In the above-mentioned example, we used JSDelivr to retrieve the Countly JS SDK. As an alternative, you may also use one of the methods mentioned at the previous section.

If you are in doubt about the correctness of your Countly SDK integration you can learn about the verification methods from here.

SDK Logging

The first thing you should do while integrating our SDK is enabling logging. If logging is enabled, then our SDK will print out debug messages about its internal state and encountered problems.

Set debug option to true at the Countly initialization to enable logging:

Asynchronous Synchronous
//during initialization
Countly.debug = true;

For more information on where to find the SDK logs you can check the documentation here.

Crash Reporting

Countly also provides a way for tracking JavaScript errors on your websites.

Select the following function to automatically capture and report JavaScript errors on your website:

Asynchronous Synchronous
Countly.q.push(['track_errors'])

Additionally, you may add more segments or properties/values to track with error reports by providing an object with a key/values to add to the error reports.

Asynchronous Synchronous
Countly.q.push(['track_errors', {
  "facebook_sdk": "2.3",
  "jquery": "1.8"
}])

Apart from automatically reporting unhandled errors, you may also report handled exceptions to the server, so you may figure out how to handle them later on (or if you even need to figure out how to handle them later on). Optionally, you may once again provide the custom segments to be used in the report (or use the ones provided with the track_error method as default ones).

Countly.log_error (error, segments):

Asynchronous Synchronous
try{
  //do something here
}
catch(ex){
  //report error to Countly
  Countly.q.push(['log_error', ex]);
}

For fatal errors you can use recordError function which takes three parameters; first, an error object with a stack key that has the error message value, second, a boolean which is false to indicate the fatality of the error and lastly segments object (optional) for custom crash segments for any extra information that you want to deliver with custom key value pairs. You can use this same function for nonfatal errors too by just setting the boolean value to true.

Countly.recordError(error, nonFatal, segments):

Asynchronous Synchronous

  const error = {stack: 'Your error message here'};
  //report fatal error to Countly
  Countly.q.push(['recordError', error, nonFatal, segments]);

To better understand what your users did prior to receiving an error, you may leave breadcrumbs throughout the code on different user actions. These breadcrumbs will then be combined in a single log and reported to the server as well.

Asynchronous Synchronous
Countly.q.push(['add_log', "user clicked button a"]);

Symbolication

Crash symbolication is available for Enterprise Edition users.

If the js files you serve are minified, transpiled or otherwise processed from your source files, you can use symbolication. With symbolication, your stacktraces will correctly point to the lines on the source files, which will help your developers debug crashes and errors. Here's an example:

JS Symbolication input sample JS Symbolication output sample
ReferenceError: undefined_function is not defined
    at r (file:///home/atak/Work/Countly/sample-app/dist/main.js:2:140)
    at HTMLButtonElement.document.getElementById.onclick (file:///home/atak/Work/Countly/sample-app/dist/main.js:2:521)

There are a number of JavaScript build tools and countless way to configure them so laying out the steps for producing a source map file for each one would take quite a while. We will focus on webpack for brevity and simplicity.

When using webpack configuration, the devtool option to generate project source maps, keep in mind that options used to attain more verbose information might take longer to build. You must choose an option that generates the source map as a separate file and not inline with the final js file. After setting this, your builds will produce a source map file ending in .map, which is the source map file you will upload to your Countly server.

To start symbolicating your errors you just need to upload your source map file to your Countly instance via the side menu > Improve > Errors > Manage Symbols. In this view, the source map files you have uploaded are listed. To upload one, you click the "Add Debug Symbol File", fill the symbol type and app version fields accordingly and upload your .map file. With your source map file uploaded, you can symbolicate the crash reports for that app version on Countly. For more information, see Crash Symbolication documentation.

Events

Adding an Event

Events are a way to track any custom actions or other data you would like to track from your website. You may also set segments to be able to view a breakdown of the action by providing the segment values.

All data passed to the Countly instance via the SDK or API should be in UTF-8.

An event consists of a JavaScript object with keys:

  • key - the name of the event (mandatory)
  • count - number of events (default: 1)
  • sum - sum to report with the event (optional)
  • dur - duration expressed in seconds, meant for reporting with the event (optional)
  • segmentation - an object with key/value pairs to report with the event as segments

Here is an example of adding an event with all possible properties:

Asynchronous Synchronous
Countly.q.push(['add_event',{
  "key": "click",
  "count": 1,
  "sum": 1.5,
  "dur": 30,
  "segmentation": {
    "key1": "value1",
    "key2": "value2"
  }
}]);

Timed Events

All events contain an optional duration property that can be set manually or with the help of the Countly web SDK's convenience functions. There are three methods available to use to calculate the duration property: start_event, cancel_event, and end_event.

The expected usage of these methods involves calling start_event for a specific event when it begins and then calling end_event to calculate the duration and create the event. In case you need to cancel a previously-called start_event, you can call cancel_event. However, it's important to note that these methods operate on the memory layer and shouldn't be used to calculate durations in situations where a browser restart occurs.

The start_event method is used to initiate an internal timer within the SDK for a given event name. This timer works by taking the current timestamp and storing it in memory.

Asynchronous Synchronous
Countly.q.push(['start_event', 'timedEvent']);

The cancel_event method erases the timestamp associated with a given event name if a start_event was previously called for that event.

Asynchronous Synchronous
Countly.q.push(['cancel_event', 'timedEvent']);

The end_event method calculates the duration value for the given event name by finding the time difference between when the start_event was called and the current time. It then creates an event for the given name with the calculated duration and adds it to the event queue. You can also pass an event object to this method, and in that case, it will use the key value as the event name.

Asynchronous Synchronous
//end event
Countly.q.push(['end_event', 'timedEvent']);

//or end event with additional data
Countly.q.push(['end_event',{
"key": "timedEvent",
"count": 1,
"sum": 1.5,
"segmentation": {
"key1": "value1",
"key2": "value2"
}
}]);

Sessions

Automatic Session Tracking

This method will automatically track user sessions by calling begin, extend, and end session methods.

Asynchronous Synchronous
Countly.q.push(['track_sessions']);

Manual Sessions

Beginning a Session

This method would allow you to control sessions manually. Only use this method if you aren’t planning on calling the track_sessions method and set the use_session_cookie setting to false for more granular control of the session.

If noHeartBeat is true, then the Countly Web SDK will not automatically extend the session, meaning you would be the one to do it automatically.

Asynchronous Synchronous
Countly.q.push(['begin_session']);

Extending a session

The Countly SDK will extend the session itself by default (if noHeartBeat was provided in the begin_session), but if you have not selected this option, you may then extend it using this method and provide the seconds since the last begin_session or session_duration call.

Asynchronous Synchronous
Countly.q.push(['session_duration', sec]);

Ending a session

When a visitor is leaving your app or website, you should end their session with this method or by optionally providing the amount of seconds since the last begin session or session_duration calls, which ever came last.

Asynchronous Synchronous
Countly.q.push(['end_session']);

View Tracking

This method will track the current pageview by using location.path as the page name and then reporting it to the server.

Asynchronous Synchronous
Countly.q.push(['track_pageview']);

For Ajax updated contents and single page web applications, pass the page name as a parameter to record the new page view.

Asynchronous Synchronous
Countly.q.push(['track_pageview','pagename']);

Here is a good example if your single-page app uses URL hash.

Asynchronous Synchronous
Countly.q.push(['track_pageview',location.pathname+location.hash]);

$(window).on('hashchange', function() {
Countly.q.push(['track_pageview',location.pathname+location.hash]);
});

In some cases, you might like to ignore some URLs to exclude from tracking, such as dynamic URLs that include the user ID in the URL, internal URLs, or for any other reason. You may do so by providing another parameter with a list of strings of views to ignore or a list of regular expressions to ignore.

Asynchronous Synchronous
//Ignoring specific page
Countly.q.push(['track_pageview',["/test-page"]]);

//Ignoring multiple specific pages
Countly.q.push(['track_pageview',["/test1", "/test2", "/test3"]]);

//Ignoring all /download/{download_id} pages but not /download page
Countly.q.push(['track_pageview',["/download/*"]]);

//Ignoring specific page while providing custom values (like hash value) for page view
Countly.q.push(['track_pageview', location.pathname+location.hash,["/test-page"]]);

Optionally, you may provide view segments (key/value pairs) to track them with the view (as the third parameter). There is a list of reserved segment keys that should not be used:

  • start
  • visit
  • bounce
  • end
  • name
  • domain
  • view
  • segment
  • platform
Asynchronous Synchronous
//Provide view segments
Countly.q.push(['track_pageview', null, null, {theme:"red", mode:"fullscreen"}]);

Overriding View Name and URL Getters

There are cases when determining the view name requires more complex logic, and in some cases, you will need to separate the URL and the View naming. This is done so you may still have some business logic view names, yet you have the valid URL underneath them to view action maps, such as clicks and scrolls.

To do so you may define the view name and URL getter functions. That way you won’t need to provide separate view names, rather the SDK will use this getter function to receive the proper view name and the URL you would like to use.

Here is an example of how to create a getter for View and URL.

View getter URL getter
Countly.getViewName = function(){
  //get base for our view
  var view = location.pathname;
  
  //if this is a page with dynamic id, we don't need id itself
  if (view.startsWith("/id/")) {
      view = "/id";
  }
  
  //now let's beautify the name
  //remove first character which always is slash (/)
  view = view.substring(1);
  
  //split by sub paths and use spaces instead
  view = view.split("/").join(" ");
  
  //upper case first letter
  view[0].toUpperCase() + view.substring(1)
  
  //return beautified view name
  return view;
}

Device ID Management

Device ID Generation

By default Countly generates and assigns a random device ID to each device that reaches your website. This can be seen as the default way to recognize and capture the user behavior by keeping this ID at the local storage and referring to it when necessary. All activity done from the same device would be recorded under the same device ID and for practical reasons it can be seen as a user identifier.

Limitations to this method comes when there multiple users using the same device where all users using the same device would be seen as a single user, or when a user reaches to your website from multiple devices or browsers in incognito mode where all devices would be seen as separate users. If these scenarios are an issue of concern to you, you can mitigate them with various device management strategies.

While it is possible to change an assigned ID (typically during the user login) and implement your own logic, as explained at the next section, there are some other methods that you can assign an ID to your users. Assigning an ID inside your initialization config or adding a query to a link that you have provided to your user can be named as two of these methods:


// adding device ID here will prevent the generation of a random ID
Countly.init({
    app_key:"YOUR_APP_KEY",
    url: "https://try.count.ly",
    device_id:"yourDeviceID",
});

// you can assign a device ID to a user through a link that you have provided to them by adding cly_device_id to your url query
yourUrl + ?cly_device_id=yourDeviceID

If you want to erase the previously stored device ID from the storage you can set clear_stored_id flag to true at the init config to do so:


// this will erase the stored device ID from the local storage every time the Countly is initialized
Countly.init({
    app_key:"YOUR_APP_KEY",
    url: "https://try.count.ly",
    clear_stored_id: true,
});

Device ID Priority

If you have used multiple methods to set a device ID for your users during your first init, Countly would fall back to the device ID priority hierarchy to assign the the correct ID for your user. This hierarchy is as follows:

URL set ID > Developer set ID > Temp ID (offline mode) > SDK generated ID

Changing Device ID

In some cases, you may want to change the ID of the user/device that you provided or Countly automatically generated, e.g. when a user was changed.

Asynchronous Synchronous
Countly.q.push(['change_id', "myNewId"]);

If device ID is changed without merging and consent was enabled, all previously given consent will be removed. This means that all features will cease to function until new consent has been given again for that new device ID.

In some other cases, you may also need to change a user's device ID in a way so that that server will merge the data of both user IDs (both the new and existing ID you provided) on the server, e.g. when a user used the website without authenticating and recorded some data and then authenticated, and you would like to change the ID to your internal ID of this user to keep tracking it across multiple devices.

This call will merge any data recorded for the current ID and save it as a user with a newly provided ID.

Asynchronous Synchronous
Countly.q.push(['change_id', "myNewId", true]);

NOTE: The call will reject invalid device ID values. A valid value is not null, not undefined, of type string and is not an empty string.

Temporary Device ID (Offline mode)

Some cases do exist when you would like the SDK to collect data but not send it to the server until a certain point. Additionally, this mode allows you to delay providing the device_id property until a later time.

E.g. if you would like to track your users with a custom device_id, such as with your internal customer ID, and you may only receive that value as soon as the user logs in. Yet, you would also like to track what the user did before logging in.

Using offline mode within this context allows you to omit the user merging and server overhead that comes with it, including any possibly skewed aggregation data.

If offline mode is entered and consent was enabled, all previously given consent will be removed. This means that all features will cease to function until new consent has been given again. Therefore after entering the offline mode, you should reestablish consent again.

To launch the SDK in offline mode, simply provide the offline_mode config value as true. At this point you may omit providing the device_id value if you would like.

Asynchronous Synchronous
Countly.debug = false;
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://try.count.ly";
Countly.offline_mode = true;

Countly.init();

Or you can enable offline mode at any point later in the SDK.

Asynchronous Synchronous
Countly.q.push(['enable_offline_mode']);

If you would like to disable offline mode and optionally provide the device_id at a later time, you may do so by adhering to the following:

Asynchronous Synchronous
Countly.q.push(['disable_offline_mode', device_id]);

Retrieving Current Device ID

If you want to execute your functions or to implement your logic depending on the type of device ID that the user has Countly offers a convenience method for you to do so.

You can reach the current device ID of the user with get_device_id:


// you can just check if the device ID of the user is exactly what it should be
var userId = Countly.get_device_id();
if ( userId === "expectedId" ) {
  //... do something
}  

Depending on whether a user is vising your site for the first time or might have just logged in to their account or for many other reasons a user can have a certain device ID that has a different type than another user. These types are:

  • DEVELOPER_SUPPLIED device ID
  • SDK_GENERATED device ID
  • TEMPORARY_ID device ID

For DEVELOPER_SUPPLIED device ID, the assignment of the ID is handled by your internal logic, for SDK_GENERATED device ID, the ID of the user was generated randomly by Countly, and for TEMPORARY_ID device ID, the user is currently offline and no request is being sent to the servers.

You can reach the device ID type of a user any time you want simply by calling the get_device_id_type function:


// You can get and compare if the device ID type is correct, and do something after
var idType = Countly.get_device_id_type();
// here lets check if it is SDK_GENERATED. Other options are DEVELOPER_SUPPLIED and TEMPORARY_ID
if ( idType === Countly.DeviceIdType.SDK_GENERATED ) {
  //... do something
}

Heatmaps

Heatmaps feature is a web exclusive plugin that helps you to visualize user interactions on your website. Web SDK supports this functionality by providing user click and scroll information to your server. Then from your server, you can trigger Heatmaps overlay to visualize these click clusters and scroll zones on your website.

To display this overlay the SDK loads certain scripts from your server. To ensure the source of these scripts and to enable these scripts to be loaded from somewhere else other than your Countly server, the SDK offers a whitelisting option during the initialization. To whitelist domains other than your Countly server you should provide an array of these domains, as String values, under the 'heatmap_whitelist' flag during the initialization:

Asynchronous Synchronous
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://try.count.ly";
Countly.heatmap_whitelist = ["https://you.domain1.com", "https://you.domain2.com"];

Tracking Clicks

This method will automatically track clicks on the last reported view and display them on the heatmap.

Asynchronous Synchronous
Countly.q.push(['track_clicks']);

In the event you are facing issues with viewing heatmaps, kindly go through this Troubleshooting guide.

Viewing heatmaps with HTTP/HTTPS content:

Note that browsers do not allow loading HTTP iframe content on HTTPS websites. For this reason, if you are using HTTPS on your Countly instance, you will only be able to view HTTPS content and no HTTP page content will be visible.

After you integrate the JS SDK and start sending click data, all generated heatmaps may be viewed under Analytics > Page views, as shown below:

001.png

Tracking Scrolls

This method will automatically track scrolls on the last reported view and display them on the heatmap.

Asynchronous Synchronous
Countly.q.push(['track_scrolls']);

As with Click Heatmaps, collected data is viewable under Analytics > Page views. You may change the heatmap type on the top bar once a view is open.

002.png

Remote Config

Remote Config feature enables you to fetch data that you have created in your server. Depending on the conditions you have set, you can fetch data from your server for the specific users that fits those conditions and process the Remote Config data in anyway you want. Whether to change the background color of your site to showing a certain message, the possibilities are virtually endless. For more information on Remote Config please check here.

While fetching Remote Config, the SDK will automatically enroll the user to A/B testing. But you are able to explicitly enroll (or not) your users to the A/B testing while fetching the remote config values or afterwards. For more information on A/B testing please check here.

Automatic Remote Config

Automatic Remote Config functionality is disabled by default and needs to be explicitly enabled. When automatic Remote Config is enabled, the SDK will try to fetch it upon some specific trigers. For example, after SDK initialization, changing device ID.

You may enable this feature by providing to the remote_config flag a callback function or by setting it to true while initializing the SDK.

If you provide a callback, the callback will be called when the Remote Config is initially loaded and when it is reloaded if you change the device_id. This callback should have two parameters, first is for error, and second is for the Remote Config object.

Asynchronous Synchronous
// in your Countly init script
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://try.count.ly";
Countly.debug = true; Countly.remote_config = true;
// OR
// provide a callback to be notified when configs are loaded Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://try.count.ly";
Countly.debug = true; Countly.remote_config = function(err, remoteConfigs){ if (!err) { //we have our remoteConfigs here console.log(remoteConfigs); } };

Manual Remote Config

If you want, you can manually fetch the Remote Config in order to receive the latest value anytime after the initialization. To do so you have to use the fetch_remote_config call. This method is also used for reloading the values for updating them according to the latest changes you made on your server.

By using this method, you can simply load the entire object or load some specific keys or omit some specific keys in order to decrease the amount of data transfer needed, assuming the values for some of the keys are large. This call will automatically save the fetched keys internally.

Fetch All Keys

Here you so not need to provide any parameters to the call but providing a callback is the recommended practice. This callback should have two parameters, first is for error, and second is for the Remote Config object.

// load the whole configuration object with a callback
Countly.fetch_remote_config(function(err, remoteConfigs){
  if (!err) {
    console.log(remoteConfigs);
// or do something else here if you want with remoteConfigs object }
});

// or whole configuration object with no params Countly.fetch_remote_config();

Fetch Specific Keys

Here the keys should be provided as string values in an array, as the first parameter in fetch_remote_config call. You can provide a callback function as a second parameter. This callback should have two parameters, first is for error, and second is for the Remote Config object.

// load specific keys only, as `key1` and `key2`
Countly.fetch_remote_config(["key1","key2"], function(err, remoteConfigs){
  if (!err) {
    console.log(remoteConfigs);
// or do something else here if you want with remoteConfigs object } });

Fetch All Except Specific Keys

Here the first parameter should be set to 'null' or 'undefined' and the keys that you want to omit must be provided as the second parameter as an array of keys as string. As a third parameter you can provide a callback function. This callback should have two parameters, first is for error, and second is for the Remote Config object.

// load all key values except specific keys, as `key1` and `key2'
Countly.fetch_remote_config(null, ["key1","key2"], function(err, remoteConfigs){
  if (!err) {
    console.log(remoteConfigs);
// or do something else here if you want with remoteConfigs object } });

Accessing Remote Config Values

You may call get_remote_config each time you would like to receive the Remote Config object of a value for a specific key or all keys from your local storage.

This method should be called once the Remote Config have been successfully loaded, or it will simply return an empty object or undefined values.

//get whole Remote Config object
var remoteConfig = Countly.get_remote_config();

//or get value for specific key like 'test'
var test = Countly.get_remote_config("test");

A/B Testing

To do so you have to set the use_explicit_rc_api flag to true during init (by default it is false). This will use the new Remote Config API and enroll your users to the A/B testing if they are eligible. However if you want to use the new API without enrolling your users automatically rc_automatic_optin_for_ab flag should be set to false during init (by default it is true).

If you would like to enroll user to A/B testing without going through the Remote Config API, instead you can use the call enrollUserToAb with keys (an array of string values) that you want to enroll the user to.

// enrolling user for 'key1' and 'key2'
Countly.enrollUserToAb(["key1","key2"]);

Consent

If consents are enabled, to fetch the Remote Config data you have to provide the 'remote-config' consent for this feature to work.

User Feedback

If you want to receive feedback from your users there are a couple of ways you can do that in Countly. To get a rating or suggestion from users you can use rating widgets, which gives users flexibility to give a rating, leave a comment or reach you with an e-mail. Another way the users can leave feedback is through the feedback widgets (survey, nps, ratings). With the help of these widgets you can ask your customers multiple questions and learn about their opinions and preferences in detail.

Ratings

While it can be cumbersome for a customer to fill a survey, a quick alternative to get user feedback is to get a numerical user rating. That can be done with the Countly rating widget.

Rating Widget

Rating widgets create a channel for users to interact, through a pop up widget.

While it is possible to customize the text fields of rating widgets according to your needs, the fundamental use case of a rating widget is to enable the user to leave a rating feedback on scale of 1 to 5, let the user contact the developer through e-mail and also to let the user be able to leave some comments or suggestions along the way.

With Countly, if you want, you can create a single rating widget to get feedback on a specific topic or you can create multiple rating widgets that can tackle different topics depending on your needs. Another feature that can prove useful is the ability to show rating widgets on command, as a popup. This can be a very useful tool when interlinked with a form submit button to ask your customers about their experience directly after submitting their form or survey.

For rating widgets to show with proper styling and to be present on the screen first you have to enable them using 'enableRatingWidgets' after you have initialized the Countly Web SDK and gave "star-rating" consent for widgets. Then you can integrate the ratings widget as follows:

Asynchronous Synchronous
//after initializing and giving consent
//for a single rating widget with widget ID '5b86772f7965c435319c79ee'
Countly.q.push([
    'enableRatingWidgets',
    {'popups':['5b86772f7965c435319c79ee']}
]);

//to manually show a rating widget as a popup
Countly.q.push([
    'presentRatingWidgetWithID',
    '6gdd84asc435319c78s4'
]);

To see multiple rating widgets on the screen, after enabling your widgets with 'enableRatingWidgets' you have to use 'initializeRatingWidgets' by passing multiple widget IDs as an argument as follows:

Asynchronous Synchronous
//after enabling rating widgets
//to show multiple rating widgets with an array of different widget IDs
Countly.q.push([
    'initializeRatingWidgets',
    ['4678wetfgb8g79gfdg9221', 'd45a5d8we4f6fs5a546ass']
]);

Manual Rating Reporting

In case you don't want to use Countly provided feedback and rating UI where you may use your own UI and simply report collected data to Countly.

To report the rating widget result manually, you need to give "star-rating" consent (in case consent is required).

Asynchronous Synchronous
//user feedback
Countly.q.push(['recordRatingWidgetWithID', {
    widget_id:"1234567890",
    contactMe: true,
    rating: 5,
    email: "user@domain.com",
    comment: "Very good"
}]);

Feedback Widget

There are three kinds of feedback widgets available. Namely NPS, survey and ratings widgets. Before any feedback widget can be shown, you need to create them in your countly dashboard first.

All three widgets use the same API to fetch feedbacks from the server as well as to display them to the end user. By default, the created widget will be appended to the end of the html document. In some scenarios you might prefer to have the widget injected in a specific element. For those scenarios we have added optional selectors. The first one is used for selecting an element by it's id and the second one is used to select the element by it's class selector. If you want to inject the feedback widget in a specific element, you can do so by specifying the element ID or the class name.

Available starting from version 23.6.3

You can also add custom segmentation while presenting a widget.

To use feedback widgets, you need to give "feedback" consent (in case consent is required).

Asynchronous Synchronous
//Fetch user's feedback widgets from the server
Countly.q.push(['get_available_feedback_widgets', feedbackWidgetsCallback]);

// Feedback widget callback function, err is for error and countlyPresentableFeedback contains an array of widhet objects function feedbackWidgetsCallback(countlyPresentableFeedback, err) { if (err) { console.log(err); return; } // Decide which which widget to show. Here the first rating widget is selected. const widgetType = "rating"; const countlyFeedbackWidget = countlyPresentableFeedback.find(widget = widget.type === widgetType); if (!countlyFeedbackWidget) { console.error(`[Countly] No ${widgetType} widget found`); return; } //Define the element ID and the class name (optional, pass undefined if you don't use) const selectorId = "targetIdSelector"; const selectorClass = "targetClassSelector"; // Define the segmentation (optional) const segmentation = { page: "home_page" }; //Display the feedback widget to the end user Countly.present_feedback_widget(countlyFeedbackWidget, selectorId, selectorClass, segmentation); }

Note: Feedback widget's show policies are handled internally by the web sdk.

Manual Reporting

Reporting feedback widgets manually consists of 3 main steps:

  1. Fetching widget list from the server with 'get_available_feedback_widgets'
  2. Fetching one widget's data from that list with 'getFeedbackWidgetData'
  3. Reporting that single widget's results with 'reportFeedbackWidgetManually'

At first step, by using the 'get_available_feedback_widgets' function, you can fetch the list of available widgets from your server as an Array of widget Objects. This function takes a callback as a parameter and this callback should have two parameters, first one for the returned list and the second one for the error. Inside your callback you should process this array of widget objects and pick one object that you want to report the results for. This array and the objects that you can pick would look like this:

{
  "result":[
      {
        "_id":"614811419f030e44be07d82f",
        "type":"rating",
        "appearance":{
          "position":"mleft",
          "bg_color":"#fff",
          "text_color":"#ddd",
          "text":"Feedback"
          },
        "tg":["/"],
        "name":"Leave us a feedback"
      },
      {
        "_id":"614811419f030e44be07d839",
        "type":"nps",
        "name":"One response for all",
        "tg":[]
      },
      {
        "_id":"614811429f030e44be07d83d",
        "type":"survey",
        "appearance":{
          "position":"bLeft",
          "show":"uSubmit",
          "color":"#0166D6",
          "logo":null,
          "submit":"Submit",
          "previous":"Previous",
          "next":"Next"
          },
        "name":"Product Feedback example",
        "tg":[]
      }
    ]
  }

Here you would want to pick a widget according to its type and name or any other information you are looking for. For more information on this data please check here.

At second step, by using the 'getFeedbackWidgetData' function, you can fetch the data of the widget of your choice. This functions has two parameters, first one is the widget object obtained from the first step and the second one is a callback with two parameters, one for the retrieved data and second one is for the error. Inside your callback you can process the returned object and gather information necessary for you to provide at the next step.

The third and last step is the part where you would report the results for the widget of your choice by using the 'reportFeedbackWidgetManually' function. This function takes three parameters, first the widget object obtained from the first step, second the widget data from the second step and third the result object that you want to report for your widget. This result object would have different key/value pairs depending on the type of widget you are reporting about so you can reach to an in-depth explanation on how to form this object from here.

And example implementation of the mentioned concepts can be seen here:


    // an example of getting the widget list, using it to get widget data and then recording data for it manually. widgetType can be 'nps', 'survey' or 'rating'
    function getFeedbackWidgetListAndDoThings(widgetType) {
      // get the widget list
      Countly.get_available_feedback_widgets(
        // callback function, 1st param is the feedback widget list
        function (feedbackList, err) {
          if (err) { // error handling
            console.log(err);
            return;
          }

          // find the widget object with the given widget type. This or a similar implementation can be used while using fetchAndDisplayWidget() as well
          const countlyFeedbackWidget = feedbackList.find(widget = widget.type === widgetType);
          if (!countlyFeedbackWidget) {
            console.error(`[Countly] No ${widgetType} widget found`);
            return;
          }

          // Get data with the widget object
          Countly.getFeedbackWidgetData(CountlyFeedbackWidget,
            // callback function, 1st param is the feedback widget data
            function (feedbackData, err) {
              if (err) { // error handling
                console.error(err);
                return;
              }

              const CountlyWidgetData = feedbackData;
              // record data according to the widget type
              if (CountlyWidgetData.type === 'nps') {
                Countly.reportFeedbackWidgetManually(CountlyFeedbackWidget, CountlyWidgetData, { rating: 3, comment: "comment" });
              } else if (CountlyWidgetData.type === 'survey') {
                var widgetResponse = {};
                // form the key/value pairs according to data
                widgetResponse["answ-" + CountlyWidgetData.questions[0].id] = CountlyWidgetData.questions[0].type === "rating" ? 3 : "answer";
                Countly.reportFeedbackWidgetManually(CountlyFeedbackWidget, CountlyWidgetData, widgetResponse);
              } else if (CountlyWidgetData.type === 'rating') {
                Countly.reportFeedbackWidgetManually(CountlyFeedbackWidget, CountlyWidgetData, { rating: 3, comment: "comment", email: "email", contactMe: true });
              }
            }

          );
        })
    }
       

User Profiles

User Details

If you have any details about the user/visitor, you may provide Countly with that information. This will allow you to track every specific user on the "User Profiles" tab, which is available with Countly Enterprise Edition.

The list of possible parameters you may pass:

Asynchronous Synchronous
Countly.q.push(['user_details',{
    "name": "Arturs Sosins",
    "username": "ar2rsawseen",
    "email": "test@test.com",
    "organization": "Countly",
    "phone": "+37112345678",
    //Web URL to picture
    "picture": "https://pbs.twimg.com/profile_images/1442562237/012_n_400x400.jpg", 
    "gender": "M",
    "byear": 1987, //birth year
    "custom":{
      "key1":"value1",
      "key2":"value2",
      ...
    }
}]);

Modifying Custom Data

Additionally, you may perform different manipulations on custom data values, such as incrementing the current value on the server or storing an array of values under the same property.

After using modifiers, don't forget to call userData.save to send data to server.

The list of available methods may be found below:

Asynchronous Synchronous
Countly.q.push(['userData.set', key, value]) //set custom property
Countly.q.push(['userData.unset', key]) //remove custom property
Countly.q.push(['userData.set_once', key, value]) //set custom property only if property does not exist
Countly.q.push(['userData.increment', key]) //increment value in key by one
Countly.q.push(['userData.increment_by', key, value]) //increment value in key by provided value
Countly.q.push(['userData.multiply', key, value]) //multiply value in key by provided value
Countly.q.push(['userData.max', key, value]) //save max value between current and provided
Countly.q.push(['userData.min', key, value]) //save min value between current and provided
Countly.q.push(['userData.push', key, value]) //add value to key as array element
Countly.q.push(['userData.push_unique', key, value]) //add value to key as array element, but only store unique values in array
Countly.q.push(['userData.pull', key, value]) //remove value from array under property with key as name
Countly.q.push(['userData.save']) //send userData to server

Orientation Tracking

Orientation tracking is enabled by default and will be sent if the required "user" consent is given (if enabled). Countly will report the device orientation once a session starts, and at any time the orientation changes.

You may disable orientation tracking by providing the enable_orientation_tracking setting when initializing the SDK as follows.

Asynchronous Synchronous
// in your Countly init script
Countly.enable_orientation_tracking = false;

In case you need to force reporting orientation, you may call the following method.

Asynchronous Synchronous
//user stored conversion data
Countly.q.push(['report_orientation', "portrait"]);

Application Performance Monitoring

If you want to record some performance metrics regarding your website there are 2 ways to report these performance traces. One way is to construct and report them manually. The other is using a plugin that will utilize BoomerangJS to collect website's performance data and report it as a performance trace.

You can reach to example implementations of APM with BoomerangJS from the following links:
Async Apm Example
Sync Apm Example

Custom Traces

To manually report trace you need to construct the trace object and call a method like this:

Asynchronous Synchronous
//report custom trace
Countly.q.push(["report_trace",{
    type: "device", //device or network
    name: "test call", //use name to identify trace and group them by
    stz: 1234567890123, //start timestamp in milliseconds
    etz: 1234567890123, //end timestamp in milliseconds
    apm_metrics: {
        duration: 1000 //duration of trace
    }
}]);

Whether you are using Countly synchronously or asynchronously, you should always provide the 'duration' key and its value in apm_metrics, otherwise custom traces won't be recorded.

Automatic Device Traces

Automatic trace reporting has two different implementation depending on if you are using Countly synchronously or asynchronously. Normally we would like Countly script to load first and BoomerangJS related scripts right after. 

Asynchronous Implementation

To use automatic device traces in your async Countly implementation you will need to set loadAPMScriptsAsync flag to true in Countly object. This would ensure that the correct script load order is established. You can provide two additional flags to the Countly object. First one is the BoomerangJS script source path as customSourceBoomerang and the second is the countly_boomerang script source path as customSourceCountlyBoomerang. If not provided the SDK would use the latest CDN scripts as the source:

Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.loadAPMScriptsAsync = true;
// Countly.customSourceBoomerang = "../somewhere/boomerang.min.js";
// Countly.customSourceCountlyBoomerang = "../somewhere/countly_boomerang.js";
// ...

Also, in your Countly init script you need to call a method to start reporting 'loading' and 'network' traces automatically:

// enables APM
Countly.q.push(["track_performance"]);

This method accepts a BoomerangJS config object (more information on BoomerangJS) as an optional second parameter. If you are familiar with it, you can modify it on your own depending on your needs (you can find the used files here). By default the SDK would use this configuration:

{
    //page load timing
    RT:{},
    //required for automated networking traces
    instrument_xhr: true,
    captureXhrRequestResponse: true,
    AutoXHR: {
        alwaysSendXhr: true,
        monitorFetch: true,
        captureXhrRequestResponse: true
    },
    //required for screen freeze traces
    Continuity: {
        enabled: true,
        monitorLongTasks: true,
        monitorPageBusy: true,
        monitorFrameRate: true,
        monitorInteractions: true,
        afterOnload: true
    }
}

Synchronous Implementation

To automatically report traces you will need to include 2 additional files in your project directly after declaring the Countly script like this with the correct paths according to your project structure:

// Option 1: You can provide local paths
<script type='text/javascript' src="../plugin/boomerang/boomerang.min.js"></script> <script type='text/javascript' src='../plugin/boomerang/countly_boomerang.js'></script>

// Option 2: Or you can use CDN for path
<script type='text/javascript' src="https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/plugin/boomerang/boomerang.min.js"></script>
<script type='text/javascript' src="https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/plugin/boomerang/countly_boomerang.js"></script>

After that, you would call a method to start reporting 'loading' and 'network' traces automatically. You can optionally provide here a BoomerangJS config object if you are familiar with it as mentioned above at Async implementation. Default usage inside your Countly init script would be like this:

//automatically report traces
Countly.track_performance();

User Consent

Opt In / Opt Out

The Countly SDK will always be opt in by default, but you may easily disable all tracking by selecting the opt_out method. It will also persistently save settings and prevent tracking after page reloads. Select opt_in to resume tracking.

Asynchronous Synchronous
//to stop tracking user data call
Countly.q.push(['opt_out']);

//to resume tracking user data call
Countly.q.push(['opt_in']);

Disabling tracking for specific users is more than sufficient for most cases. However, should you desire more granular feature controls, checkout the following section.

If you would like to have opt out selected by default, combine these methods with the initial setting ignore_visitor on the Countly init object.

In most cases, the opt_out and opt_in methods are enough to disable the tracking of specific users, such as testers. However, in some cases, you may require a more granular approach.

This section will tell you how to set up GDPR compliant consent management with the Countly Web SDK.

Disable Tracking Until Given Consent

To disable tracking until consent is given for a specific feature, all you need to do is pass true as the require_consent config before or during your selection of the Countly init method.

Asynchronous Synchronous
// in your Countly init script
Countly.require_consent = true;

Features for Consent

The SDK provides different features for consent. You may check all the supported features for the current SDK by checking the Countly.features property. Here is a list containing all the properties with explanations:

  • sessions - tracks when, how often, and how long users use your website
  • events - allows your events to be sent to the server
  • views - allows for the views/pages accessed by a user to be tracked
  • scrolls - allows a user’s scrolls to be tracked on the heatmap
  • clicks - allows a user’s clicks and link clicks to be tracked on the heatmap
  • forms - allows a user’s form submissions to be tracked
  • crashes - allows JavaScript errors to be tracked
  • attribution - allows the campaign from which a user came to be tracked
  • users - allows user information, including custom properties, to be collected/provided
  • star-rating - allows user rating and feedback tracking through rating widgets
  • feedback - allows survey, nps rating and feedback tracking through feedback widgets
  • apm - allows performance tracking of application by recording traces
  • location - allows a user’s location (country, city area) to be recorded

This is the most granular level with control features for which consent may be given. However, depending on your website and use case, you may also want to combine some of the features into one using the group_features method.

Grouping features One group for everything
Countly.group_features({
    activity:["sessions","events","views"],
    interaction:["scrolls","clicks","forms"]
});
//After this call Countly.add_consent("activity") to allow "sessions","events","views"
//or call Countly.add_consent("interaction") to allow "scrolls","clicks","forms"
//or call Countly.add_consent("crashes") to allow some separate feature

Managing Consent

Upon a visitor’s arrival to your website, you should check if you already have consent from this visitor. If not, you should present them with a popup explaining what will be tracked and allow them to consent to tracking. When a user selects the consent preferences, you should persistently store it, and on each Countly load, let Countly know for which features the user gave consent by calling the Countly.add_consent method and passing one or multiple features (as an array). For example, you should also allow the user to change their mind regarding separate settings screens and when changes are going to be made there. Respectively call the Countly.add_consent or Countly.remove_consent methods to allow Countly to track specific features or disable tracking for them.

Here is a high-level example of how it could look:

Asynchronous Synchronous
<script type='text/javascript'>
  
// Some default pre init
var Countly = Countly || {};
Countly.q = Countly.q || [];

// Provide your app key that you retrieved from Countly dashboard
Countly.app_key = "YOUR_APP_KEY";

// Provide your server IP or name. Use try.count.ly or us-try.count.ly
// or asia-try.count.ly for EE trial server.
// If you use your own server, make sure you have https enabled if you use
// https below.
Countly.url = "https://yourdomain.com";

//require consent before tracking anything
Countly.require_consent = true; //this true means consent is required

//(optionally) provide custom feature tree if needed
Countly.q.push(['group_features', {
activity:["sessions","events","views"],
interaction:["scrolls","clicks","forms"]
}]);

//we can call all the helper methods we want, they won't record until consent is provided for specific features
Countly.q.push(['track_sessions']);
Countly.q.push(['track_pageview']);
Countly.q.push(['track_clicks']);
Countly.q.push(['track_links']);
Countly.q.push(['track_forms']);
Countly.q.push(['track_errors', {jquery:"1.10", jqueryui:"1.10"}]);

//Consent Management logic should be implemented and controlled by developer
//this is just a simply example of what logic it could have
if (typeof(localStorage) !== "undefined") {
var consents = localStorage.getItem("consents");
//checking if user already provided consent
if(consents){
//we already have array with consents from previous visit, let's just pass them to Countly
Countly.q.push(['add_consent', JSON.parse(consents)]);
}
else{
//user have not yet provided us a consent
//we need to display popup and ask user to give consent for specific features we want to track
//once we get response, we should store them like this
//example response
var response = ["activity", "interaction", "crashes"];
Countly.q.push(['add_consent', response]);
localStorage.setItem("consents", JSON.stringify(response));
}
} else {
// Sorry! No Web Storage support..
// we can fallback to cookie
}

// Load countly script asynchronously
(function() {
var cly = document.createElement('script'); cly.type = 'text/javascript';
cly.async = true;
// Enter url of script here (see below for other option)
cly.src = 'https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.min.js';
cly.onload = function(){Countly.init()};
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(cly, s);
})();
</script>

Other Features and Notes

SDK Config Parameters Explained

Here are the properties you may set up upon Countly initialization:

  • app_key - mandatory, app key for your app created in Countly
  • device_id - to identify a visitor, will be autogenerated if not provided
  • url - your Countly server URL - you may also use your own server URL or IP here
  • app_version - (optional) the version of your app or website
  • country_code - (optional) country code for your visitor
  • city - (optional) name of the city of your visitor
  • ip_address - (optional) IP address of your visitor
  • debug - output debug info into the console (default: false)
  • ignore_bots - option to ignore traffic from bots (default: true)
  • interval - set an interval for how often inspections should be made to see if there is any data to report and then report it (default: 500 ms)
  • queue_size - the maximum amount of queued requests to store (default: 1000)
  • fail_timeout - set the time to wait in seconds after a failed connection to the server (default: 60 seconds)
  • inactivity_time - the time limit after which a user will be considered inactive if no actions have been made. No mouse movement, scrolling, or keys pressed. Expressed in minutes (default: 20 minutes)
  • session_update - how often a session should be extended, expressed in seconds (default: 60 seconds)
  • max_events - maximum amount of events to send in one batch (default: 100)
  • max_logs - the maximum amount of breadcrumbs to store for crash logs (default: 100)
  • ignore_referrers - array with referrers to ignore (default: none)
  • ignore_prefetch - ignore prefetching and pre-rendering from counting as real website visits (default: true)
  • heatmap_whitelist - Array of trusted domains (as string) that can trigger heatmap script loading. By default the SDK whitelists your server url.
  • force_post - force using post method for all requests (default: false)
  • ignore_visitor - ignore this current visitor (default: false)
  • require_consent - Pass true if you are implementing GDPR compatible consent management. This would prevent running any functionality without proper consent (default: false)
  • utm - object instructing which UTM parameters to track (default: {"source":true, "medium":true, "campaign":true, "term":true, "content":true})
  • use_session_cookie - use cookies to track sessions (default: true)
  • session_cookie_timeout - how long until a cookie session should expire, expressed in minutes (default: 30 minutes)
  • remote_config - enable automatic remote config fetching, provide the callback function to be notified when fetching is complete (default: false)
  • rc_automatic_optin_for_ab - opts in the user for A/B testing while fetching the remote config (default: true)
  • use_explicit_rc_api - set it to true to use the explicit remote config API (default: false)
  • namespace - have a separate namespace for persistent data when using multiple trackers on the same domain
  • track_domains - Set to false to disable domain tracking, so no domain data would be reported (default: true)
  • headers - object to override or add headers to all SDK requests
  • storage - What type of storage to use, by default uses local storage and would fallback to cookies, but you can set values "localstorage" or "cookies" to force only specific storage, or use "none" to not use any storage and keep everything in memory
  • metrics - provide metrics override or custom metrics for this user. For more information on the specific metric keys used by Countly, check here.

Setting up properties on the Countly Web SDK is as follows (use your own server name if not using try.count.ly below):

Asynchronous Synchronous
Countly.debug = false;
Countly.app_key = "YOUR_APP_KEY";
Countly.device_id = "1234-1234-1234-1234";
Countly.url = "https://try.count.ly";
Countly.app_version = "1.2";
Countly.country_code = "LV";
Countly.city = "Riga";
Countly.ip_address = "83.140.15.1";

SDK Storage and Requests

Countly Web SDK stores various information like device ID, request queue, session information and more in your device. This helps Countly to provide data consistency and enable convenience methods like offline mode.

The default storage location of user-specific data, except the session information, is your browser’s local storage. Information stored here is persistent, and as long as it was not erased or overwritten, it will stay on your device indefinitely. However, Countly allows you to change this behavior by selecting persistent cookies as the main storage option or choosing not to store any data at all, depending on your needs. These storage options are mutually exclusive, meaning only one option can be selected at a given time.

If cookies were selected as the main storage medium, persistent cookies have an expiration date and the information stored in them would be rendered obsolete after a while. In case of the session information, it is stored in session cookies and would expire when the tab or browser is closed. Lastly, if you decide not to store any information, all information will stay in memory and would be gone when the memory is cleared.

These options can be selected during the initialization:

Asynchronous Synchronous
//possible options are "localstorage", "cookies" and "none"
Countly.storage = "localstorage";

Automatically Fill User Data

In most cases, you won’t know anything about your users, yet you will still want to try to collect any data possible. We provide 2 helper methods for this exact reason.

Collect User Data From Filled Forms

This method will look into the forms filled out by your users and will try to gather data, such as names, email addresses, usernames, etc.
All forms will automatically be checked, but you have the option to provide a form element if you would like to collect data only from a specific form, or select a method multiple times for different forms. Also, if you are already providing data for users, then you would not want to overwrite it. You may set the third parameter as true to indicate that data found should be stored in custom properties.

Asynchronous Synchronous
//collect data from forms
Countly.q.push(['collect_from_forms']);

//collect data from specific form
Countly.q.push(['collect_from_forms', formElement]);

//collect from forms and report as custom user properties
Countly.q.push(['collect_from_forms', document, true]);

Passwords and other sensitive data will be omitted, however, if you would explicitly like to exclude some form input from being processed, just add the css class cly_user_ignore to that element. Oppositely, you may need to specify data from this input to be collected as the provided key by adding the prefixed css class cly_user_key. Therefore, if you would like to store data as a name, you should specify the cly_user_name css class.

<form method='post' name='test_form'>
  <!-- data will be checked in this input -->
<p><input type="text" name="e" value="myemail@mydomain.com"></p>
  
<!-- ignore this input -->
<p><input type="text" name="e" value="notmyemail@notmydomain.com" class="cly_user_ignore"></p>

<!-- get customfield by class -->
<p><input type="text" name="custom" id="custom" value="value" class="cly_user_key1"></p>

<p><input id="submit-form" type="submit" value="Submit"></p>

</form>

Collect User Data From Facebook

If your website uses the Facebook JavaScript SDK, you may use this helper method to automatically collect user data from their Facebook accounts. Select the method right after Facebook SDK initialization and optionally set the object with custom properties and graph paths for values on where to receive them.

Here is an example how to receive data from Facebook, including locations and time zones as custom properties.

<script src="https://connect.facebook.net/en_US/all.js"></script>
<script type="text/javascript">
FB.init({
    appId: '251676171676751',
    status: true,
    cookie: true,
    oauth: true
});

function CountlyGatherFBData(){
Countly.collect_from_facebook({"location":"location.name", "tz":"timezone"});
};

FB.getLoginStatus(function(stsResp) {
if(stsResp.authResponse) {
CountlyGatherFBData();
} else {
FB.login(function(loginResp) {
if(loginResp.authResponse) {
CountlyGatherFBData();
} else {
alert('Please authorize this application to use it!');
}
});
}
});
</script>

Attribution

When using Countly attribution analytics, you may also report conversions to the Countly server, e.g. when a visitor purchases an item or registers on your site.

If a user visits your website through the Countly campaign link, the campaign information will be automatically stored by default for this user and used when reporting conversion. If conversion has not yet been reported, the campaign information will be overwritten when that user visits through another campaign link. When you report the conversion, it would report the latest campaign user used to access your website.

However, you may also overwrite that data and provide any specific campaign ID for which you would like to report conversion.

Conversion will not be reported if there is no stored campaign data and you have yet to provide a campaign ID.

Note: Conversion for each user may only be reported once, all other conversions will be ignored for that same user.

Asynchronous Synchronous
//user stored conversion data
Countly.q.push(['recordDirectAttribution']);

//or provide campaign id yourself
Countly.q.push(['recordDirectAttribution', "MyCampaignID"]);

Track Link Clicks

This method will track clicks to specific links and will report events with the linkClick key as well as the link's text, ID, and URLs as segments.

All links will automatically be tracked by default for the entire page, but you may provide the parent node as a parameter for which to track link clicks.

Asynchronous Synchronous
Countly.q.push(['track_links']);

As soon as you include this one-liner coder, you will automatically be able to see the "linkClick" event in your dashboard as data flows in with the text, ID, and URLs as segments.

Track Form Submissions

This method will automatically track form submissions and collect form data. It will then input values in the form and report them as a Event with the formSubmit key.

All forms will automatically be tracked by default for the entire page, but you may provide the parent node as a parameter for which to track forms.

The second parameter controls whether to collect hidden inputs or not. Hidden inputs are not collected by default.

Asynchronous Synchronous
//will not collect hidden inputs
Countly.q.push(['track_forms']);

//will collect hidden inputs
Countly.q.push(['track_forms', null, true]);

Using the Web SDK in Webview

If you are going to use the Web SDK in the Webview of your app, there are prerequisites that must be checked to ensure it is fully functioning. There are no known iOS issues at this moment, but some specific settings need to be enabled for Android.

Ensure JavaScript has been enabled for your Webview

myWebView.getSettings().setJavaScriptEnabled(true);

Ensure local storage has been enabled

//change the path to where you want to store local storage data
myWebView.getSettings().setDomStorageEnabled(true);
myWebView.getSettings().setDatabaseEnabled(true);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
    myWebView.getSettings().setDatabasePath("/data/data/" + myWebView.getContext().getPackageName() + "/databases/");
}

If you would like to use Countly both in the native app and Webview, then you would maybe also like to match the device_id between them, so the transitions may be seamless and you may continue to track events and data from both for the same user.

In this case, there are a couple things you should do:

  1. Defer initializing Countly by putting the initialization code in some function.
  2. Do not track sessions in Webview as they are already tracked by the native app.
  3. Pass the device_id to Webview and run the initialization function once Webview has loaded.
<!--Countly script in webview-->
<script type='text/javascript'>
  var Countly = Countly || {};
  Countly.q = Countly.q || [];
  
  //provide countly initialization parameters
  Countly.app_key = "YOUR_APP_KEY";
  Countly.url = "http://yourdomain.com"; 
  
  //track views or anything else you want to track
  Countly.q.push(['track_pageview']);
  
  //function to initialize Countly
  function InitializeCountly(device_id) {
    
    //assign passed device_id
    Countly.device_id = device_id;
    
    //load countly script
    var cly = document.createElement('script'); cly.type = 'text/javascript'; 
    cly.async = true;
    //enter url of script here
    cly.src = 'https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.min.js';
    cly.onload = function(){Countly.init()};
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(cly, s);
  };
</script>

Then, on the native app side, all you have to do is call the JavaScript functions in the Webview and pass on the device_id to complete this function.

Android iOS
myWebView.loadUrl("javascript:InitializeCountly('"+device_id+"');");

Tracking Users with Javascript Disabled

In some cases, a user might have JavaScript disabled, meaning normal ways of tracking those users will prove ineffective. In such a case, you may use the transparent 1px x 1px image hosted on your Countly server as reporting the URL and report all the same parameters as all the SDKs have been described here.

Assuming your Countly is hosted at domain.com and the app_key is "12345", the default setup should look like this:

<noscript><img src='http://domain.com/pixel.png?app_key=12345&begin_session=1'/></noscript>

Simply place it anywhere in your HTML code and it should only work if JavaScript is disabled, which means SDK tracking won't work.

This would then track how many total sessions there are in where users have JavaScript disabled. The server will attach the device_id as a no_js by default and add the user profile name as ‘No JS’, so you may easier identify how many users without JavaScript you have.

However, as mentioned before, this accepts any parameters as a normal SDK endpoint does. Thus, if you dynamically generate data via the server, you may also dynamically generate this URL to provide information that you have about the user. That might be the device_id parameter (for identification), OS, OS version, and any other metrics or information you have, as for example

<noscript><img src='http://domain.com/pixel.png?app_key=12345&device_id=test@test.com&begin_session=1&metrics={"_os":"Android", "_os_version":"4.1"}'/></noscript>

Multiple Trackers on the Same Domain

Sometimes you would like to track different parts of the same domain/website as separate applications.

If you just instantiate the Countly SDK in both places, but with different app IDs, then both of their continuous storages will clash storing information for both apps. There are times when this is exactly what you would like to have happen. Generally, it’s function will share a device_id, and although they might be storing requests together, each will be sent to a different app on the same server.

However, there are situations where you would like to keep them completely separate. To do so you will need to provide a namespace for the different trackers to keep their local storages from clashing.

Simply providing a namespace as the init option will suffice.

Asynchronous Synchronous
Countly.debug = false;
Countly.app_key = "YOUR_APP_KEY";
Countly.device_id = "1234-1234-1234-1234";
Countly.url = "https://try.count.ly";
Countly.namespace = "forum";

Countly.init();

Cross Website/Domain Tracking

You will need to use the same device_id value on the same user in both places to track the same user across different websites or domains.

This may be achieved by passing the device ID as a URL parameter when transferring a user from one website/domain to the other.

Simply take the device ID value from Countly.device_id and pass it as a URL parameter named cly_device_id as follows: http://newdomain.com/?cly_device_id=your-user-device-id.

Tracked Cookie List and Explanations

By default, Countly Web SDK uses local storage to keep information between page views, but if local storage is not available, Web SDK will try to fallback to cookies.

  • cly_queue - a queue of requests that will be made to the server and acknowledged
  • cly_event - a queue of events reported by SDK
  • cly_remote_configs - cached remote config from server
  • cly_ignore - ignore the user and do not track anything if this is set to true
  • cly_id - current user's device id
  • cly_cmp_id - last campaign id user came from
  • cly_cmp_uid - user identifier for last campaign
  • cly_session - timestamp when the last session started so we would not call begin_session on each page load
  • cly_token - token passed for retrieving heat map data from the server
  • cly_old_token - to detect token expiration and reset for action map data

Multi Instancing

You can initialize Countly multiple times at the same page with different app keys to send information to different apps you own and gather data with higher flexibility and precision. These new instances have all functionality of Countly but depending on your init configuration they would behave differently. You can attach different events to different Countly instances to send events to specific applications even from the same button trigger or much more. A simple integration example would be:

Asynchronous Synchronous

Countly = Countly || {};
Countly.q = Countly.q || [];

// initializing first instance, which will be global Countly
Countly.init({
  app_key: "YOUR_APP_KEY_1",
  url: "https://try.count.ly" //your server goes here
})
// report event to first app
Countly.add_event({
  key:"first_app"
});

// initialize second instance for another app 
Countly.q.push(["init", {
  app_key: "YOUR_APP_KEY_2", //must have different APP key
  url: "https://try.count.ly" //your server goes here
}])
// report event to second app asynchronously by passing app key as first argument
Countly.q.push(["YOUR_APP_KEY_2", "add_event", {
  key:"second_app"
}]);
    

SDK Internal Limits

Countly is highly customizable and let's you take a huge part at the control of the system in multiple ways. From customizing segmentation values to changing event keys great liberty comes with the cost of great responsibility. As a sanity check measure Countly relies on internal limits to get a hold of the free flow of values, keys, character and more. These internal limits are again customizable at initialization and current limits and their default values are as follows:

  • maxKeyLength - 128 chars. Keys that exceed this limit will be truncated.
    • This is used for setting the maximum size of all string keys including:
    • - event names
    • - view names
    • - custom trace key name (APM)
    • - custom metric key (apm)
    • - segmentation key (for all features)
    • - custom user property
    • - custom user property keys that are used for property modifiers (mul, push, pull, set, increment, etc)
  • maxValueSize - 256 chars. Values that exceed this limit will be truncated.
    • This is used for setting the maximum size of all values in key-value pairs including:
    • - segmentation value in case of strings (for all features)
    • - custom user property string value
    • - user profile named key (username, email, etc) string values. Except "picture" field, that has a limit of 4096 chars
    • - custom user property modifier string values. For example, for modifiers like "push", "pull", "setOnce", etc.
    • - breadcrumb text
    • - manual feedback widget reporting fields (reported as event)
    • - rating widget response (reported as event)
  • maxSegmentationValues - 100 dev entries. Entries that exceed this limit will be removed.
    To set the maximum amount of custom segmentation that can be recorded in one event.
  • maxBreadcrumbCount - 100 entries. If the limit is exceeded, the oldest entry will be removed.
    To limit the amount of breadcrumbs that can be recorded before the oldest one is deleted from the logs.
  • maxStackTraceLinesPerThread - 30 lines. Lines that exceed this entry will be removed.
    Sets the maximum number of stack trace lines that can be recorded per thread.
  • maxStackTraceLineLength - 200 chars. Lines that exceed this limit will be truncated.
    This can set the maximum number of characters that is allowed per stack trace line. This also limits the crash message length.

To change these default values all you have to do is to set the properties during the initialization:

Asynchronous Synchronous
Countly.debug = false;
Countly.app_key = "YOUR_APP_KEY";
Countly.device_id = "1234-1234-1234-1234";
Countly.url = "https://try.count.ly";
Countly.max_key_length = 500;
Countly.max_value_size = 12;
Countly.max_segmentation_values = 23;
Countly.max_breadcrumb_count = 80;
Countly.max_stack_trace_lines_per_thread = 50;
Countly.max_stack_trace_line_length = 300;

Setting Maximum Request Queue Size

When you initialize Countly, you can specify a value for the queue_size flag. This flag limits the number of requests that can be stored in the request queue when the Countly server is unavailable or experiencing connection problems.

If the server is down, requests sent to it will be queued on the device. If the number of queued requests becomes excessive, it can cause problems with delivering the requests to the server, and can also take up valuable storage space on the device. To prevent this from happening, the queue_size flag limits the number of requests that can be stored in the queue.

If the number of requests in the queue reaches the queue_size limit, the oldest requests in the queue will be dropped, and the newest requests will take their place. This ensures that the queue doesn't become too large, and that the most recent requests are prioritized for delivery.

If you do not specify a value for the queue_size flag, the default setting of 1,000 will be used.

Asynchronous Synchronous
Countly.queue_size = 5000;

UTM Tags

If you are providing possible users links to your website, and if you would like to track those users who have clicked those links, you can do so by using some utm tags within your links and Countly web SDK would recognize and record these users for you.

The web SDK offers you two options to set these tags in your links. First you can use the default tags that has been set to be recognized by the SDK by default. These tags are 'source', 'medium', 'campaign', 'term' and 'content'. If you add any or all of these tags into your links as query Countly would recognize and record them accordingly. An example usage of the default tags is as follows:


// basic structure of a link that contains all available default tags
yourUrl + ?utm_source=someValue&utm_medium=someValue&utm_campaign=someValue&utm_term=someValue&utm_content=someValue

//or you can omit tags
yourUrl + ?utm_source=someValue&utm_campaign=someValue

// yourUrl is the url of your site and someValue is any string value you want to pass

Second option is to define your custom utm tags during the initialization of Countly and use those tags in your links. You have to provide a map of key value pairs where the key is the tag and the value is 'true' if you want the Countly to check for that key in your links:

Asynchronous Synchronous

Countly.app_key = "YOUR_APP_KEY";
Countly.device_id = "1234-1234-1234-1234";
// add your custom tags and set their value to true
Countly.utm = {tag1: true, tag2: true};

// then your links should look like this:
yourUrl + ?utm_tag1=someValue&utm_tag2=someValue

The values you have provided will be captured and stored as user properties, under the keys 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term' and 'utm_content' if you have used the default tags or as 'utm_yourCustomTag1', 'utm_yourCustomTag1'... if you have used custom tags, together with the corresponding user when a user clicks that link. This will enable you to segment users in all places around the dashboard, where granular data is used and segmentation capabilities are provided.

GA Adapter

If you are using Google Universal Analytics in your website and you would also like to integrate Countly to your project, GA Adapter plugin can help you send your Google Analytics data also to your Countly server without any extra integration. This plugin will recognize your Google Analytics data and convert them into Countly events and send it to your server, so you can observe your analytics data from your Countly dashboard with ease.

There are 3 steps you have to follow to enable the GA Adapter plugin:

  1. First integrate Countly into your project (You can reach details from here)
  2. Then declare the plugin script before your Google Analytics implementation block
  3. Finally, add 'CountlyGAAdapter();' into your Google Analytics snippet

A simple implementation would look something like this:

// ...
// ...    

// ... Countly implementation was here
// </script>

// write the correct path to the GA plugin depending on your project structure
<script src="../plugin/ga_adapter/ga_adapter.js"></script>

// Google Analytics implementation
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

// add this line into your google analytics snippet to use the GA plugin
CountlyGAAdapter();

// now Countly will recognize the GA commands like below and send them to your Countly server too
ga('create', 'UA-56295140-3', 'auto');
ga('send','event','category','action','label');
ga('send','pageview','page.html');
</script>

You can reach to an example implementation of this plugin from the following link:

GA Adapter Example

FAQ

Can I integrate Countly Web SDK to my TypeScript Project

TypeScript is a strict syntactical superset of JavaScript. It helps you catch errors early by adding static typing to the language. It compiles down to basic JavaScript so it can be used anywhere JavaScript can run, whether Node.js or browser.

Countly Web SDK is written in basic JavaScript so it is compatible with your TypeScript projects by enabling allowJs in your project's tsconfig.json file. However as we use javascript features that can run in the browser, your project must also be runnable on the browser.

Ignoring your own bots

The default behavior of Countly Web SDK is to ignore bots crawling your site to provide you a more accurate user analytics data. However, Countly can't detect and block all bots crawling the internet as we use the userAgent string to detect bots and spammy bots can hide by providing conventional UserAgent. However, this is not the case for a bot that you have created your own.

To include your bots to be also ignored by the Countly Web SDK you have to include 'CountlySiteBot' in your userAgent string. This enables your SDK to recognize your bot as one of the bots to be ignored and the SDK would stop recording data for your bot.

Why aren’t I able to see AngularJS errors on the Countly dashboard?

AngularJs swallows errors by default. You will need to extend Angular's $exceptionHandler to call Countly.log_error(). For more information, see this blog post.

Incognito mode and ad blockers

Incognito mode prevents the browser from storing cookies and other site data. Cookies are small files that websites use to track user activity, remember preferences, and provide personalized experiences. In incognito mode, cookies are not saved. This means that any information our SDK saves in the browser's local storage would be gone the next time the user opens their incognito browser. These things include things like device ID and event/request queues. Each time a person visits a Countly integrated website in incognito mode, they would be perceived as a new user. This can be mitigated by having an authentication page on your website.

Ad blockers employ various techniques to identify and block unwanted requests, such as analyzing URL patterns, known tracking domains, or specific JavaScript code snippets. They can also detect and block requests made to known analytics or tracking services, making it possible for them to block requests made to the Countly server from our SDKs. This can result in incomplete or missing data, as the blocked requests may not reach the Countly server for processing. This is a possibility, but not all ad blockers would be blocking our requests, depending on their filter settings.

To mitigate this issue to a certain extent, you can enable force using the POST method for all requests with the SDK. Switching to a POST request can make it slightly harder for ad blockers to detect and block the request, as the parameters are sent in the request body instead of the URL. However, it doesn't make the request invisible or immune to blocking. Ad blockers can still analyze network traffic and inspect the request headers and payload, so if your server is Countly hosted and the domain name is in the filter of the ad blocker, it would still be blocked.

Comments

0 comments

Please sign in to leave a comment.