This documentation shows how to install the Countly JS tracker and use Countly to track your web page in detail. It applies to the SDK version 20.11.
Older documentation
To access the documentation for version 20.04 and older, click here.
In order to track your web server pages, you will need the Countly JavaScript tracking library. This library comes ready & automatically hosted on your Countly server (at http://yourdomain.com/sdk/web/countly.min.js) and can be updated via 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 library (however, you should not have to as it already comes ready):
npm install countly-sdk-web
bower install countly-sdk-web
yarn add countly-sdk-web
Before we begin, the following information is meant for those who have examined our mobile SDKs - we can tell that events or tags that are used in mobile SDKs are quite similar to those we use in JavaScript code. For example, it's possible to modify custom property values of user details with modification commands, such as inc, mul, max, or min. Likewise, any event can be easily sent with segmentation.
What is an APP KEY?
You'll see the APP_KEY definition above. This key is generated automatically when you create a website for tracking on the Countly dashboard. Note that an APP KEY is different than the API KEY, which is used to send data via API calls.
To retrieve your APP_KEY, go to Management -> Applications and select your app. Then you will see the App Key field.
Setting up (Recommended asynchronous usage)
You may use the Countly Web SDK asynchronously without blocking content loading. It may also be used if the Countly script has not yet been loaded by pushing function calls into the Countly.q queue or synchronously allowing the script to load before executing any functions.
Inserting asynchronous code before closing the tag is suggested, while Synchronous code should be added towards the bottom of the page before closing the tag.
An example setup would look like this:
<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";
// 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']);
// Uncomment the following line to track web heatmaps (Countly Enterprise)
// Countly.q.push(['track_clicks']);
// Uncomment the following line to track web scrollmaps (Countly Enterprise)
// Countly.q.push(['track_scrolls']);
// 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>
<!--Countly script-->
<script type='text/javascript' src='https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.min.js'></script>
<script type='text/javascript'>
Countly.init({
// provide your app key that you retrieved from Countly dashboard
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.
url: "http://yourdomain.com"
});
// track sessions automatically
Countly.track_sessions();
// track pageviews automatically
Countly.track_pageview();
</script>
In the above-mentioned example, we used JSDelivr to retrieve the Countly JS SDK. There are two options available here: using Cloudflare (CDNjs) or JSDelivr (both of which are highly available CDNs). If you would like to use CDNjs, here is the line you should be using instead of the one above.
// Note: You should change 19.2.1 below to the version
// of the latest JS SDK to make sure you use latest version.
// Latest version is here:
// https://github.com/Countly/countly-sdk-web/releases
https://cdnjs.cloudflare.com/ajax/libs/countly-sdk-web/19.2.1/countly.min.js
As an alternative, you may also use/sdk/web/countly.min.js
to get this SDK directly from your Countly server.
As the third 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. You would only need to point this minified JS tracker lib in your small code above. This should ideally be done if none of the above-mentioned methods work in your specific use-case.
Then you will be able to make event calls such as:
<script type='text/javascript'>
//send event on button click
function clickEvent(ob){
Countly.q.push(['add_event',{
key:"asyncButtonClick",
segmentation: {
"id": ob.id
}
}]);
}
</script>
<input type="button" id="asyncTestButton" onclick="clickEvent(this)" value="Test Button">
<script type='text/javascript'>
//send event on button click
function clickEvent(ob){
Countly.add_event({
key:"buttonClick",
segmentation: {
"id": ob.id
}
});
}
</script>
<input type="button" id="testButton" onclick="clickEvent(this)" value="Test Button">
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.
Generate custom SDK code snippets
Countly Code Generator may be used to generate custom SDK code snippets simply and quickly. You may provide values for your event, or user profile or just start with basic integration, and this service will generate the necessary code for you to use in your favorite IDE.
Setup properties
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 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 - 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: 10)
- max_logs - 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)
- 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)
- namespace - have a separate namespace for persistent data when using multiple trackers on the same domain
- headers - object to override or add headers to all SDK requests
-
metrics - provide metrics for this user, otherwise, it will try to collect everything which is possible
- _os - the name of platform/operating system
- _os_version - version of platform/operating system
- _device - device model name
- _resolution - screen resolution of the device
- _carrier - carrier or operator used for connection
- _density - screen density of the device
- _locale - locale or language of the device in ISO format
- _store - a source where the user came from
- _browser - browser name
- _browser_version - browser version
- _ua - user agent string
Setting up properties on the Countly Web SDK is as follows (use your own server name if not using try.count.ly below):
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";
Countly.init({
debug:false,
app_key:"YOUR_APP_KEY",
device_id:"1234-1234-1234-1234",
url: "https://try.count.ly",
app_version: "1.2",
country_code: "LV",
city: "Riga",
ip_address: "83.140.15.1"
});
Note that the Countly web SDK automatically captures UTM tags and stores them as user properties together with the corresponding user. This will make users segmentable in all the places around the dashboard, where granular data is used and segmentation capabilities are provided.
Helper methods
Helper methods were created to allow you to easily track the most common actions on your web.
Choose your helper methods carefully.
The helper methods we provide below are both for synchronous and asynchronous tracking code. Choose the right helper method depending on your implementation.
Track Sessions
This method will automatically track user sessions by calling begin, extend, and end session methods.
Countly.q.push(['track_sessions']);
Countly.track_sessions();
Track Pageviews
This method will track the current pageview by using location.path as the page name and then reporting it to the server.
Countly.q.push(['track_pageview']);
Countly.track_pageview();
For Ajax updated contents and single page web applications, pass the page name as a parameter to record the new page view.
Countly.q.push(['track_pageview','pagename']);
Countly.track_pageview("pagename");
Here is a good example if your single-page app uses URL hash.
Countly.q.push(['track_pageview',location.pathname+location.hash]);
$(window).on('hashchange', function() {
Countly.q.push(['track_pageview',location.pathname+location.hash]);
});
Countly.track_pageview(location.pathname+location.hash);
$(window).on('hashchange', function() {
Countly.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.
//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"]]);
//Ignoring specific page
Countly.track_pageview(["/test-page"]);
//Ignoring multiple specific pages
Countly.track_pageview(["/test1", "/test2", "/test3"]);
//Ignoring all /download/{download_id} pages but not /download page
Countly.track_pageview(["/download/*"]);
//Ignoring specific page while providing custom values (like hash value) for page view
Countly.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
//Provide view segments
Countly.q.push(['track_pageview', null, null, {theme:"red", mode:"fullscreen"}]);
//Provide view segments
Countly.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.
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;
}
Countly.getViewUrl = function(){
//we want to have path and query string and hash as url
return location.pathname + location.search + location.hash;
};
Track Clicks / Heatmaps (Countly Enterprise)
This method will automatically track clicks on the last reported view and display them on the heatmap.
Countly.q.push(['track_clicks']);
Countly.track_clicks();
In the event you are facing issues with viewing heatmaps, kindly go through this Troubleshooting guide.
Important notification about 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:
Track Scrolls / Scroll heatmaps (Countly Enterprise)
This method will automatically track scrolls on the last reported view and display them on the heatmap.
Countly.q.push(['track_scrolls']);
Countly.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.
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.
Countly.q.push(['track_links']);
Countly.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.
//will not collect hidden inputs
Countly.q.push(['track_forms']);
//will collect hidden inputs
Countly.q.push(['track_forms', null, true]);
//will not collect hidden inputs
Countly.track_forms();
//will collect hidden inputs
Countly.track_forms(null, true);
Report conversion
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.
//user stored conversion data
Countly.q.push(['report_conversion']);
//or provide campaign id yourself
Countly.q.push(['report_conversion', "MyCampaignID"]);
//user stored conversion data
Countly.report_conversion();
//or provide campaign id yourself
Countly.report_conversion("MyCampaignID");
Report feedback
In case you don't want to use Countly provided feedback and rating UI, you may use your own UI and simply report collected data to Countly.
//user feedback
Countly.q.push(['report_feedback', {
widget_id:"1234567890",
contactMe: true,
rating: 5,
email: "user@domain.com",
comment: "Very good"
}]);
//user feedback
Countly.report_feedback({
widget_id:"1234567890",
contactMe: true,
rating: 5,
email: "user@domain.com",
comment: "Very good"
});
Report NPS and Surveys
There are two types of surveys available - NPS and Basic survey.
Both NPS and Survey use the same API to fetch feedbacks from the server as well as to display them to the end user.
//Fetch user's NPS and Survey feedbacks from the server
Countly.q.push(['get_available_feedback_widgets', feedbackWidgetsCallback]);
//Surveys feedback callback function
function feedbackWidgetsCallback(countlyPresentableFeedback, err) {
if (err) {
console.log(err);
return;
}
//The available feedback types are nps and survey, decide which one to show
var countlyFeedbackWidget = countlyPresentableFeedback[0];
//Display the feedback widget to the end user
Countly.present_feedback_widget(countlyFeedbackWidget);
}
//Fetch user's NPS and Survey feedbacks from the server
Countly.get_available_feedback_widgets(feedbackWidgetsCallback);
//Surveys feedback callback function
function feedbackWidgetsCallback(countlyPresentableFeedback, err) {
if (err) {
console.log(err);
return;
}
//The available feedback types are nps and survey, decide which one to show
var countlyFeedbackWidget = countlyPresentableFeedback[0];
//Display the feedback widget to the end user
Countly.present_feedback_widget(countlyFeedbackWidget);
}
Note: Feedback widget's show policies are handled internally by the web sdk.
Report device orientation
By default, Countly reports the device orientation once a session starts, and at any time the orientation changes. In case you need to force reporting orientation, you may call the following method.
//user stored conversion data
Countly.q.push(['report_orientation', "portrait"]);
//user stored conversion data
Countly.report_orientatio("portrait");
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.
//to stop tracking user data call
Countly.q.push(['opt_out']);
//to resume tracking user data call
Countly.q.push(['opt_in']);
//to stop tracking user data call
Countly.opt_out();
//to resume tracking user data call
Countly.opt_in();
Disabling tracking for specific users is more than sufficient for most cases. However, should you desire more granular feature controls, checkout the GDPR section.
Opt out by default
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.
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.
//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]);
//collect data from forms
Countly.collect_from_forms();
//collect data from specific form
Countly.collect_from_forms(formElement);
//collect from forms and report as custom user properties
Countly.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>
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.
Data passed should be in UTF-8
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:
Countly.q.push(['add_event',{
"key": "click",
"count": 1,
"sum": 1.5,
"dur": 30,
"segmentation": {
"key1": "value1",
"key2": "value2"
}
}]);
Countly.add_event({
"key": "click",
"count": 1,
"sum": 1.5,
"dur": 30,
"segmentation": {
"key1": "value1",
"key2": "value2"
}
});
Timed Events
You may report time or duration with every event by providing the dur property of the event’s object. However, if you would like, you may also let the Web SDK track the duration of some specific events for you. You may use the start_event and end_event methods.
Firstly, you may start tracking an event time by providing the name of the event (which later on will be used as the key for the event object).
Countly.q.push(['start_event', 'timedEvent']);
Countly.start_event("timedEvent")
Countly will internally mark the start of the event and will wait until you end the event using the end_event method, setting up the dur property based on how much time has passed since the start_event for the same event name was selected.
//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"
}
}]);
//end event
Countly.end_event("timedEvent")
//or end event with additional data
Countly.end_event({
"key": "timedEvent",
"count": 1,
"sum": 1.5,
"segmentation": {
"key1": "value1",
"key2": "value2"
}
});
User Profiles and Custom data
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.
The list of possible parameters you may pass:
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",
...
}
}]);
Countly.user_details({
"name": "Arturs Sosins",
"username": "ar2rsawseen",
"email": "test@test.com",
"organization": "Countly",
"phone": "+37112345678",
//Web URL pointing to user 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:
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
Countly.userData.set(key, value) //set custom property
Countly.userData.set_once(key, value) //set custom property only if property does not exist
Countly.userData.increment(key) //increment value in key by one
Countly.userData.increment_by(key, value) //increment value in key by provided value
Countly.userData.multiply(key, value) //multiply value in key by provided value
Countly.userData.max(key, value) //save max value between current and provided
Countly.userData.min(key, value) //save min value between current and provided
Countly.userData.push(key, value) //add value to key as array element
Countly.userData.push_unique(key, value) //add value to key as array element, but only store unique values in array
Countly.userData.pull(key, value) //remove value from array under property with key as name
Countly.userData.save() //send userData to server
Tracking Javascript error
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:
Countly.q.push(['track_errors'])
Countly.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.
Countly.q.push(['track_errors', {
"facebook_sdk": "2.3",
"jquery": "1.8"
}])
Countly.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):
try{
//do something here
}
catch(ex){
//report error to Countly
Countly.q.push(['log_error', ex]);
}
try{
//do something here
}
catch(ex){
//report error to Countly
Countly.log_error(ex);
}
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.
Countly.q.push(['add_log', "user clicked button a"]);
Countly.add_log("user clicked button a");
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.
Countly.q.push(['change_id', "myNewId"]);
Countly.change_id("myNewId");
In some 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.
Countly.q.push(['change_id', "myNewId", true]);
Countly.change_id("myNewId", true);
GDPR Consent management
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.
//before loading Countly script
Countly.require_consent = true;
Countly.init({
debug:false,
app_key:"YOUR_APP_KEY",
device_id:"1234-1234-1234-1234",
url: "https://try.count.ly",
app_version: "1.2",
country_code: "LV",
city: "Riga",
ip_address: "83.140.15.1",
require_consent: true //this will make require consent before tracking
});
Next, you may select all the helper methods you will be using. They won't be tracking or sending anything to the server until consent is given.
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 users to rate the site and leave feedback
- feedback - allows users to take part in surveys and nps ratings and submit feedbacks
- 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.
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
Countly.group_features({
all:["sessions","events","views","scrolls","clicks","forms","crashes","attribution","users"]
});
//After this call Countly.add_consent("all") to allow all features
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:
<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>
<!--Countly script-->
<script type='text/javascript' src='../lib/countly.js'></script>
<script type='text/javascript'>
//initializing countly with params and passing require_consent config as true
Countly.init({
app_key: "YOUR_APP_KEY",
url: "https://try.count.ly", //your server goes here
debug:true,
require_consent: true //this true means consent is required
});
//(optionally) provide custom feature tree if needed
Countly.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.track_sessions();
Countly.track_pageview();
Countly.track_clicks();
Countly.track_links();
Countly.track_forms();
Countly.track_errors({jquery:"1.10", jqueryui:"1.10"});
//Consent Management logic should be implemented and controled 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.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.add_consent(response);
localStorage.setItem("consents", JSON.stringify(response));
}
} else {
// Sorry! No Web Storage support..
// we can fallback to cookie
}
Remote configuration
Remote configuration functionality is disabled by default and needs to be explicitly enabled.
When remote configuration is enabled, the SDK will only try to fetch it once upon SDK initialization and will receive the initially remote configuration and persistently store it.
In the event of one of the following sessions, assuming it would not be possible to load the remote configuration from storage, you will receive an error object in the callback, but you will still have the stored values of the cached remote configuration object.
Enabling Remote configuration
You may enable remote configuration by providing the remote_config setting when initializing the SDK.
If you provide a callback, it will be called when remote configuration is initially loaded and reloaded if you change the device_id.
//to enable remote configuration
//before loading Countly script
Countly.remote_config = true;
//or provide a callback to be notified when configs are loaded
Countly.remote_config = function(err, remoteConfigs){
if (!err) {
//we have our remoteConfigs here
console.log(remoteConfigs);
}
};
//to enable remote configuration
Countly.init({
debug:false,
app_key:"YOUR_APP_KEY",
device_id:"1234-1234-1234-1234",
url: "https://try.count.ly",
remote_config: true //this will enable loading remote configuration
});
//or provide a callback to be notified when configs are loaded
Countly.init({
debug:false,
app_key:"YOUR_APP_KEY",
device_id:"1234-1234-1234-1234",
url: "https://try.count.ly",
remote_config: function(err, remoteConfigs){
if (!err) {
//we have our remoteConfigs here
console.log(remoteConfigs);
}
}
});
Receiving configuration values
You receive the initially loaded remote configuration values in the provided callback, but if you need to get an updated version afterward, you can manually reload it.
You may call get_remote_config each time you would like to receive the remote config object of a value for a specific key.
This method should be called once the remote configurations 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");
Reloading configuration values
Should you need to reload the remote config in order to receive the latest value, call the fetch_remote_config method.
By using this method, you may reload the entire object or simply reload 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.
//reload whole configuration object
Countly.fetch_remote_config(function(err, remoteConfigs){
if (!err) {
console.log(remoteConfigs);
}
});
//reload specific keys only, as `key1` and `key2`
Countly.fetch_remote_config(["key1","key2"], function(err, remoteConfigs){
if (!err) {
console.log(remoteConfigs);
}
});
//reload 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);
}
});
Performance monitoring
There are 2 ways to report performance traces. One way is to construct and report them manually. The other is using a plugin that will use boomerang.js to collect data and report it as a performance trace.
To manually report trace you need to construct the trace and call a method like this:
//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 miliseconds
etz: 1234567890123, //end timestamp in miliseconds
apm_metrics: {
duration: 1000 //duration of trace
}
}]);
//report custom trace
Countly.report_trace({
type: "device", //device or network
name: "test call", //use name to identify trace and group them by
stz: 1234567890123, //start timestamp in miliseconds
etz: 1234567890123, //end timestamp in miliseconds
apm_metrics: {
duration: 1000 //duration of trace
}
});
To automatically report traces you will need to include 2 additional files in your project:
<script type='text/javascript' src='../plugin/boomerang/countly_boomerang.js'></script>
<script type='text/javascript' src="../plugin/boomerang/boomerang.min.js"></script>
After that, you may call a method to start reporting loading and network traces automatically. This method accepts boomerang initialization config (more information on boomerang.js) as a parameter, so if you are familiar with it, you can modify it on your own. In case you are not, you may follow this pattern:
//automatically report traces
Countly.q.push(["track_performance", {
//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
}
}]);
//automatically report traces
Countly.track_performance({
//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
}
});
Tracking a session manually
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.
Countly.q.push(['begin_session']);
Countly.begin_session(noHeartBeat);
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.
Countly.q.push(['session_duration', sec]);
Countly.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.
Countly.q.push(['end_session']);
Countly.end_session(sec)
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.
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.
Countly.debug = false;
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://try.count.ly";
Countly.offline_mode = true;
Countly.init();
Countly.init({
debug:false,
app_key:"YOUR_APP_KEY",
url: "https://try.count.ly",
offline_mode: true
});
Or you can enable offline mode at any point later in the SDK.
Countly.q.push(['enable_offline_mode']);
Countly.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:
Countly.q.push(['disable_offline_mode', device_id]);
Countly.disable_offline_mode(device_id);
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.
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();
Countly.init({
debug:false,
app_key:"YOUR_APP_KEY",
device_id:"1234-1234-1234-1234",
url: "https://try.count.ly",
namespace: "forum"
});
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.
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:
- Defer initializing Countly by putting the initialization code in some function.
- Do not track sessions in Webview as they are already tracked by the native app.
- 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.
myWebView.loadUrl("javascript:InitializeCountly('"+device_id+"');");
#import "CountlyDeviceInfo.h"
NSString *js = [NSString stringWithFormat: @"InitializeCountly('%@');", CountlyDeviceInfo.sharedInstance.deviceID];
[myWebView stringByEvaluatingJavaScriptFromString:js];
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>
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
Symbolication
Enterprise
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:
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)
ReferenceError: undefined_function is not defined
at undefined_function (src/index.js:30:4)
at cause_error (src/index.js:37:10)
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.