This documentation is for the Countly Web SDK version 26.8.X. The SDK source code repository can be found here.
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) |
|---|---|---|---|---|---|---|---|---|---|
| 11 | 12 | 21 | 96 | 15 | 64 | 6 | 6 | 23 | 98 |
* With polyfills.
To examine the example integrations please have a look here.
Adding the SDK to the Project
To track your web pages (SPA, MPA or mobile applications that consist of HTML5 views) and browser extensions, you can use Countly Web SDK.
It is automatically hosted on your Countly server as a UMD file:
https://yourdomain.com/sdk/web/countly.min.jshttps://yourdomain.com/sdk/web/countly.jsOptionally, you may also use package managers to gain access to the SDK:
npm install countly-sdk-webyarn add countly-sdk-webYou can also reach the SDK through CDN:
// latest non minified
cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.js
// latest minified
cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.min.js// 26.8.0 non minified
cdn.jsdelivr.net/npm/countly-sdk-web@26.8.0/lib/countly.js
// 26.8.0 minified (JSDelivr or Cloudflare)
cdn.jsdelivr.net/npm/countly-sdk-web@26.8.0/lib/countly.min.js
or
cdnjs.cloudflare.com/ajax/libs/countly-sdk-web/26.8.0/countly.min.jsLastly 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
The SDK is a single UMD file which can be loaded directly to your HTML file or can be imported with a package manager to your project and bundled with your bundler to your build.
You may use the Countly Web SDK synchronously or asynchronously. Asynchronous usage would work 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, which SDK will execute after it is initialized.
Here you would also need to provide your Countly server app's application key and server URL. Please check here for more information on how to acquire your application key (APP_KEY) and server URL.
You can check all the config options you can use to manipulate the SDK behaviour from here.
An example setup would look like this:
import Countly from "countly-sdk-web";
Countly.init({
// server credentials
app_key: "YOUR_APP_KEY",
url: "http://yourdomain.com"
});
// Track sessions and page views automatically (recommended)
Countly.track_sessions();
Countly.track_pageview();<!--inside <head></head>-->
<script type='text/javascript'>
var Countly = Countly || {};
Countly.q = Countly.q || [];
// server credentials
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://yourdomain.com";
// Track sessions and page views automatically (recommended)
Countly.q.push(['track_sessions']);
Countly.q.push(['track_pageview']);
// Load Countly script asynchronously
(function() {
var cly = document.createElement('script'); cly.type = 'text/javascript';
cly.async = true;
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><!--inside <head></head>-->
<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({
// server credentials
app_key: "YOUR_APP_KEY",
url: "http://yourdomain.com"
});
// Track sessions and page views automatically (recommended)
Countly.track_sessions();
Countly.track_pageview();
</script><!--inside <head></head>-->
<script type='text/javascript'>
(function(l,t){function y(c){return function(){p.push([c].concat(Array.prototype.slice.call(arguments,0)));return d}}function u(c,a,e){e=y(e||a);e.__isCountlyStub=!0;c[a]=e}function z(c){if(!v&&!m){m=!0;var a=t.createElement("script");a.type="text/javascript";a.async=!0;a.src=c;a.crossOrigin="anonymous";a.onload=function(){v=!0;m=!1;var h=l.Countly;h&&"function"===typeof h.init&&h.init(w||{});h=l.Countly;var x=p.slice();p=[];for(var q=0;q<x.length;q++){var b=x[q],f=h,A=b.slice(1);b=b[0].split(".");
for(var r=0;r<b.length-1;r++)f=f&&f[b[r]];b=b[b.length-1];b=f&&f[b];"function"!==typeof b||b.__isCountlyStub||b.apply(f,A||[])}};a.onerror=function(){m=!1;console.error("Countly loader failed to load SDK from:",c)};var e=t.getElementsByTagName("script")[0];e.parentNode.insertBefore(a,e)}}var d=l.Countly||(l.Countly={}),p=[],m=!1,v=!1,w=null;d.__clyBootstrap={version:"1.0.0",name:"countly-unified-loader"};for(var n="track_sessions track_pageview track_performance add_event user_details track_errors track_forms track_clicks track_scrolls track_links recordError change_id set_id uploadUserProfilePicture add_consent remove_consent group_features opt_in opt_out begin_session end_session session_duration start_event end_event track_view add_log log_error report_trace enable_offline_mode disable_offline_mode attempt_to_send_stored_requests fetch_remote_config enrollUserToAb recordDirectAttribution collect_from_forms collect_from_facebook get_available_feedback_widgets getFeedbackWidgetData present_feedback_widget reportFeedbackWidgetManually recordRatingWidgetWithID content.enterContentZone content.refreshContentZone content.exitContentZone content.setGlobalContentSegmentation content.setTheme feedback.showNPS feedback.showSurvey feedback.showRating userData.set userData.set_once userData.increment userData.increment_by userData.multiply userData.max userData.min userData.push userData.push_unique userData.pull userData.unset userData.save enable_push_notifications disable_push_notifications record_push_action set_push_notification_listener".split(" "),
k=0;k<n.length;k++){var g=n[k].split(".");2===g.length?(d[g[0]]=d[g[0]]||{},u(d[g[0]],g[1],n[k])):u(d,g[0],n[k])}d.init=function(c,a){"object"===typeof c&&(a=c||{},c=a.app_key);a=a||{};c&&(a.app_key=c);for(var e in a)Object.prototype.hasOwnProperty.call(a,e)&&(d[e]=a[e]);"YOUR_APP_KEY"!==d.app_key&&"https://your.server.ly"!==d.url||console.warn("Please do not use default set of app key and server url");w=a;z(d.sdk_url||"https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.min.js");return d}})(window,
document);
Countly.init("YOUR_APP_KEY", {
url: "https://yourdomain.com"
});
// Track sessions and page views automatically (recommended)
Countly.track_sessions();
Countly.track_pageview();
</script>With the quick loader you can call Countly methods, such as Countly.add_event, right away: the loader keeps the calls and runs them in order once the SDK has loaded. It loads the latest SDK version from jsDelivr. To pin a version or to load the SDK from your own server, add sdk_url with the script's address to the init config.
If your pages use the quick loader snippet of an earlier SDK version, replace it with the one above. With the earlier snippet, calling methods such as add_consent, opt_out or track_view before the SDK loaded throws an error.
If you are in doubt about the correctness of your Countly SDK integration you can learn about the verification methods from here.
SDK Logging
If logging is enabled, then our SDK will print out debug messages about its internal state (events it's recording and sending) and encountered problems.
Set debug option to true at the Countly initialization to enable logging:
Countly.debug = true;Countly.init({
debug:true,
app_key:"YOUR_APP_KEY",
url: "https://try.count.ly",
});For more information on where to find the SDK logs you can check the documentation here.
SDK Log Gathering
If a user reports a problem you cannot reproduce, you can ask the SDK on that user's device to send its own log lines to your Countly server. Start log gathering for the user from their profile on your dashboard (Open SDK Debugger in the profile's menu). The request reaches the SDK with the SDK behavior settings, so gathering starts the next time the SDK starts on that device (when the user loads a page of your site), and stopping works the same way. While gathering is on, the SDK uploads the lines in batches, at least once a minute.
Log gathering works whether debug is enabled or not. When consent is required, a line is sent only once the consent of the feature it comes from is given, for example events for the lines of add_event. Lines that belong to no feature, such as those of the SDK's start-up, need both the events and the users consent. Lines without their consent wait in memory until it is given.
Log lines can contain what the SDK processes, such as event segmentation, user details and the device ID. Nothing is gathered unless someone starts log gathering for that user on your dashboard. Log gathering needs Countly server 26.01.8 or later.
Connection Test
When data from a user does not reach your server, a connection test shows which Countly endpoints that user's browser can reach. Request it from the user's profile on your dashboard (Open SDK Debugger, then Connection test). The request expires after 60 minutes, and the SDK picks it up the next time it starts on that device.
The SDK then makes a short series of requests to your Countly server, one after another, and reports for each endpoint whether it answered, with the HTTP status and the response time. These requests carry no app key, device ID or user data, so most of them get an HTTP 400 answer, which counts as reachable, and they need no consent. A path that a proxy, firewall or ad blocker blocks shows as failed in the result. Connection tests need Countly server 26.01.8 or later.
To turn off log gathering and connection tests, together with all other SDK behavior settings, set disable_sdk_behavior_settings_updates to true at init.
Crash Reporting
To automatically capture and report unhandled JavaScript errors on your website (You can add more information to error reports by providing an object with key/value pairs):
Countly.q.push(['track_errors', {
"your_extra_info_key": your_info
}])Countly.track_errors({
"your_extra_info_key": your_info
})You can also report handled exceptions to the server. Optionally you can provide extra information (an object) with the report (or use the ones provided with the track_errors method as default ones).
try{
//do something here
}
catch(ex){
//report error to Countly
Countly.q.push(['log_error', ex, extra_info]);
}try{
//do something here
}
catch(ex){
//report error to Countly
Countly.log_error(ex, extra_info);
}You can 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");Crash Filtering
You can omit or modify crash reports that SDK generates by crash filtering feature. You can provide a callback function which SDK can call when a crash report it prepared. Then you can modify or omit the crash according to the information present.
// before init
Countly.crash_filter_callback = function (crashObject) {
console.log("Crash object:", crashObject);
// modify, omit or return directly
return crashObject;
};Countly.init({
app_key: "YOUR_APP_KEY",
url: "https://yourdomain.com",
crash_filter_callback: function (crashObject) {
console.log("Crash object:", crashObject);
// modify, omit or return directly
return crashObject;
}
});The crashObject will include some of these metric and data info. Most important parameter is _error, which you can check for string values you are looking for. If you want to prevent sending a crash just return null from the callback. If you want to send the report as is then you can just directly return it. And finally you can remove/obfuscate sensitive information and return the modified crash object in the callback. SDK will send the returned object to the server.
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:
Error: Error at depth 3
at cause_error (http://127.0.0.1:5501/examples/symbolication/dist/main.js:5657:13)
at multiply (http://127.0.0.1:5501/examples/symbolication/dist/main.js:5651:3)
at saveRecords (http://127.0.0.1:5501/examples/symbolication/dist/main.js:5648:3)
at initializeCoreFunctions (http://127.0.0.1:5501/examples/symbolication/dist/main.js:5645:3)
at save (http://127.0.0.1:5501/examples/symbolication/dist/main.js:5642:3)
at removeHashes (http://127.0.0.1:5501/examples/symbolication/dist/main.js:5639:3)
at calculateParams (http://127.0.0.1:5501/examples/symbolication/dist/main.js:5636:3)
at addRecords (http://127.0.0.1:5501/examples/symbolication/dist/main.js:5633:3)
at HTMLButtonElement.unhandled_error (http://127.0.0.1:5501/examples/symbolication/dist/main.js:5675:5)Error: Error at depth 3
at src/index.js:66:12
at cause_error (src/index.js:59:2)
at multiply (src/index.js:56:2)
at saveRecords (src/index.js:53:2)
at initializeCoreFunctions (src/index.js:50:2)
at save (src/index.js:47:2)
at removeHashes (src/index.js:44:2)
at calculateParams (src/index.js:41:2)
at addRecords (src/index.js:87:6)When using any build tool you will need to choose an option that generates the source map as a separate file and not inline with the final js file (devtool option for webpack for example). If you set it up correctly 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 Crashes > 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 (optional) (defaults to 1)
- sum - sum to report with the event (optional)
- dur - duration expressed in seconds (optional)
- segmentation - an object with key/value pairs (optional)
Here is an example of adding an event with all possible properties:
Countly.q.push(['add_event',{
"key": "Survey_success",
"count": 1,
"sum": 0,
"dur": 25,
"segmentation": {
"Answer_1": "I would love to",
"Answer_2": 12
}
}]);Countly.add_event({
"key": "Survey_success",
"count": 1,
"sum": 0,
"dur": 25,
"segmentation": {
"Answer_1": "I would love to",
"Answer_2": 12
}
});Timed Events
Timed events are SDK's convenience methods to calculate the duration of an activity easily. There are three methods available to use to calculate the duration property of an event: start_event, cancel_event, and end_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/refresh occurs.
The start_event method starts an internal timer within the SDK for a given event name. This timer works by taking the current timestamp and storing it in memory.
Countly.q.push(['start_event', 'timedEvent']);Countly.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.
Countly.q.push(['cancel_event', 'timedEvent']);Countly.cancel_event("timedEvent")The end_event method stops the timer and 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.
//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"
}
});If end_event is not called, no event will be sent to the server.
Sessions
Sessions are the continuous time blocks that indicates the user interacts with your website. Each recorded interaction after a session starts will be recorded under that session until a new session starts.
Automatic Session Tracking
To leave session tracking logic to the SDK you can just call:
Countly.q.push(['track_sessions']);Countly.track_sessions();Manual Sessions
If you need a custom session logic then you can use manual session methods to:
- Starting a session at the beginning
- Marking the end of a session
Only use the methods below if you aren’t planning on using the automatic session tracking and set the use_session_cookie setting to false during init for more granular control of the session.
SDK will automatically report elapsed session duration with 60 seconds intervals (this can be modified during init with session_update config option).
Beginning a Session
To begin a session:
Countly.q.push(['begin_session']);Countly.begin_session();Ending a session
To end a session:
Countly.q.push(['end_session']);Countly.end_session(sec)When you change the device ID without merge (change_id, or set_id while the current ID is not one the SDK generated), the SDK ends the previous user's session. It starts a session for the new user only when you use automatic session tracking (track_sessions). With manual sessions, call begin_session for the new user yourself.
Content Zone
The Content Zone feature enhances user engagement by delivering various types of content blocks, such as in-app messaging, ads, or user engagement prompts by utilizing Journeys. These content blocks are dynamically served from the content builder on the server, ensuring that users receive relevant and up-to-date information.
For learning how you can use Journeys & Content Builder to create In-App messages you can check this article.
Only thing you need to do to enable the this feature and start fetching content from the server according to your Journeys, use the following method:
Countly.q.push(() => { Countly.content.enterContentZone(); });
// or
Countly.q.push(['content.enterContentZone']);
// or with filtering you can pick and choose which content to show
function filter(params) {
var shouldContentBeShown = true;
console.log("Content filter called with params:", params);
// Add your content filtering logic here and determine whether to show content
return shouldContentBeShown;
}
Countly.q.push(['content.enterContentZone', filter]);Countly.content.enterContentZone();
// or with filtering you can pick and choose which content to show
function filter(params) {
var shouldContentBeShown = true;
console.log("Content filter called with params:", params);
// Add your content filtering logic here and determine whether to show content
return shouldContentBeShown;
}
Countly.content.enterContentZone(filter);This call will retrieve and display any available content for the user. It will also regularly check if a new content is available, and if it is, will fetch and show it to the user.
Links in content open in a new tab, and the page they open cannot control your site's tab. Content can open web (http and https) links, links relative to your site, contact links such as mailto: or tel:, and app links written as scheme://. It does not open any other link, such as a javascript: link.
If consent is required, the content zone needs the content consent. Until it is given, enterContentZone waits, and the zone is entered as soon as the consent is given. Removing the consent closes the content on screen.
When the device ID changes without merge, the SDK exits the content zone and closes the previous user's content. Call enterContentZone again to show content to the new user.
Fine Tuning Content
If you need to trigger a Journey and display its content after a specific user action you can use the method below to fasten the process:
Countly.q.push(() => { Countly.content.refreshContentZone(); });
// or
Countly.q.push(['content.refreshContentZone']);Countly.content.refreshContentZone();If your Journeys start with an event, you can also list that event's key under Journey Trigger Events in the SDK behavior settings on your dashboard. When the SDK records such an event, it sends it right away and then asks for the journey's content, also when the content zone is not entered.
When you want to exit the content zone and stop the SDK from checking for available content, you can use this method. It also closes the content on screen, and content that arrives afterwards, journey content included, is not shown until you call enterContentZone again:
Countly.q.push(() => { Countly.content.exitContentZone(); });
// or
Countly.q.push(['content.exitContentZone']);Countly.content.exitContentZone();If you need to change the frequency of content zone requests from default 30 seconds, you can give a custom value for intervals, in seconds, during init (min. 15 seconds):
Countly.content_zone_timer_interval = 45; // seconds
// Initialize the SDKCountly.init({
app_key: "YOUR_APP_KEY",
url: "https://your.server.ly",
content_zone_timer_interval: 45, //seconds
});Global Content Segmentation
You can add a segmentation to every event that content and feedback widgets record, for example to tell which part of your site they were shown on. Set it during init with global_content_segmentation, or at any time with content.setGlobalContentSegmentation:
Countly.global_content_segmentation = { site_section: "store", plan: "pro" };
// Initialize the SDK
// or set it later
Countly.q.push(['content.setGlobalContentSegmentation', { site_section: "checkout" }]);Countly.init({
app_key: "YOUR_APP_KEY",
url: "https://your.server.ly",
global_content_segmentation: { site_section: "store", plan: "pro" }
});
// or set it later
Countly.content.setGlobalContentSegmentation({ site_section: "checkout" });Each call replaces the previous segmentation, and null removes it. Values can be strings, numbers, booleans or arrays of these, and the SDK's internal limits for keys and values apply. The segmentation is kept in memory only, so set it on every page. It stays when the device ID changes.
It is added to the events of content (content zone and journey content) and of feedback widgets shown with present_feedback_widget, feedback.showNPS, feedback.showSurvey or feedback.showRating, or reported with reportFeedbackWidgetManually or recordRatingWidgetWithID. The rating popups of presentRatingWidgetWithID, initializeRatingWidgets and enableRatingWidgets do not get it. When an event already has a key of the same name, the event's own value is kept. Keys the server reads on these events, such as rating, comment, closed, campaign_id, content_id, journey_id and keys starting with answ, are left out.
Content Theme
The SDK tells content and feedback widgets whether your site is shown in a light or a dark theme. Content is shown in dark mode when dark mode is enabled for it in the content builder.
By default the SDK follows your site. It uses the color-scheme that your page's CSS sets on the root element, or the color-scheme meta tag, when it names only light or only dark. Otherwise it uses the visitor's browser setting for light or dark mode. To set the theme yourself, use the content_theme init option or content.setTheme with one of these values:
-
"dark"or"light"- always use that theme -
false- do not pass a theme -
null- follow the site (default)
Countly.content_theme = "dark";
// Initialize the SDK
// or set it later, for example when the visitor switches your site's theme
Countly.q.push(['content.setTheme', "light"]);Countly.init({
app_key: "YOUR_APP_KEY",
url: "https://your.server.ly",
content_theme: "dark"
});
// or set it later, for example when the visitor switches your site's theme
Countly.content.setTheme("light");The SDK reads the theme each time content or a feedback widget opens. Content already on screen keeps the theme it opened with.
View Tracking
SDK only provides manual views (you will need to determine when a view starts). Depending on your site's structure (MPA or SPA) you might need to modify how you track page views with the SDK.
Manual View Recording
All views are auto-stopped meaning there can only be one view tracked at a time and it will end at the end of a session or when another one starts.
You can track the current page that the SDK initialized in by using the method below for MPAs. This uses location.path as the page name and then reports it to the server:
Countly.q.push(['track_pageview']);Countly.track_pageview();If location.path as the page name is not useful for you (for Ajax updated contents and single page web applications), pass the page name as a parameter to record the new page view, for example
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);
});If you are relying on the default page name assigned by the SDK 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 way you give a human readable page name, 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;
};Device ID Management
Retrieving Current Device ID
Countly offers a convenience method (get_device_id) for you to get the current user's device ID:
var id = Countly.get_device_id();SDK records the type of an ID. These types are:
DEVELOPER_SUPPLIEDSDK_GENERATEDTEMPORARY_ID
DEVELOPER_SUPPLIED device ID means this ID was assigned by your internal logic. SDK_GENERATED device ID means the ID was generated randomly by the SDK, and TEMPORARY_ID device ID means the SDK is in offline mode.
You can get the device ID type of a user by calling the get_device_id_type function:
var idType = Countly.get_device_id_type();You can use the DeviceIdType enums to evaluate the device ID type you retrieved:
var idType = Countly.get_device_id_type();
if (idType === Countly.DeviceIdType.SDK_GENERATED) {
// ...do something
}
Changing Device ID
You can change the device ID of a user with set_id method:
Countly.q.push(['set_id', "newId"]);Countly.set_id("newId");This method's effect on the server will be different according to the type of the current ID stored in the SDK at the time you call it:
-
If current stored ID is
SDK_GENERATEDthen in the server all the information recorded for that device ID will be merged to the new ID you provide and old user with theSDK_GENERATEDID will be erased. -
If the current stored ID is
DEVELOPER_SUPPLIEDorTEMPORARY_IDthen in the server it will also create a new user with this new ID if it does not exist. An ID given in the page URL withcly_device_idcounts asDEVELOPER_SUPPLIEDhere.
If you need a more complicated logic then you will need to use this method mentioned here instead.
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.
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);The requests recorded in offline mode wait in the browser's local storage. Once offline mode ends, they are sent with the real device ID. This also happens when a later page, in the same tab or in another one, starts with a real device ID.
Device ID Generation
By default Countly generates and assigns a random device ID (UUID) to each device that visits your website. This ID is saved at the localstorage of the browser. Assuming the localstorage is not cleared, this device will be recognized with this stored ID from now on.
Limitations to this method:
- Multiple users using the same device would be seen as a single user
- When a user reaches to your website from multiple devices or browsers in incognito mode then 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 like:
- Adding a login/authentication page and assigning device ID there
- Entering the offline mode (mentioned above) until you identify the user and assigning the ID then
You can also provide an ID during init to prevent the SDK from generating a random ID:
// adding device ID here will prevent the generation of a random ID
Countly.init({
app_key:"YOUR_APP_KEY",
url: "https://your.server",
device_id:"yourDeviceID",
});A device_id given as null or as an empty string counts as not given, so the SDK generates an ID.
You can also provide an ID in the search query of the link that opens your site. If you use the param cly_device_id its value will be set as the device ID instead of generating a random one. Percent-encode the value in the link, for example with encodeURIComponent: the SDK decodes it, so john%40mail.com becomes john@mail.com.
// 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
yoursite.com + ?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:
// 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
Push Notifications
Countly gives you the ability to send push notifications to the visitors of your website with the Web SDK integration. Visitors who allowed notifications receive them from their browser, also while your site is not open. For more information on how to best use this feature you can check this article.
Web push needs a Countly server with web push support (Countly 26.10 or later, set up in the new dashboard), and your site must be served over HTTPS (or from localhost while testing).
Integration
Web push works through a service worker, a script the browser runs in the background to receive notifications and handle clicks on them. The SDK comes with Countly's service worker, countly_sw.js, which you serve from your own site:
- Take
lib/countly_sw.jsof the SDK version your pages use, from thecountly-sdk-webnpm package or from the SDK's GitHub repository, and serve it from your site's root, for example ashttps://yourdomain.com/countly_sw.js. Browsers accept a service worker only from the same origin as your pages. - Replace the file with the new version's copy whenever you upgrade the SDK. With
debugenabled, the SDK warns in the console when the service worker's version is not its own. - Copy your application's VAPID public key from your dashboard (see Setting up Credentials) and give it to the SDK during init with
push_vapid_public_key.
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://yourdomain.com";
Countly.push_vapid_public_key = "YOUR_VAPID_PUBLIC_KEY";
// where you serve the service worker, "/countly_sw.js" by default
Countly.push_service_worker_path = "/countly_sw.js";
// Initialize the SDKCountly.init({
app_key: "YOUR_APP_KEY",
url: "https://yourdomain.com",
push_vapid_public_key: "YOUR_VAPID_PUBLIC_KEY",
// where you serve the service worker, "/countly_sw.js" by default
push_service_worker_path: "/countly_sw.js"
});The SDK registers the service worker under a scope of its own, the countly-push/ folder next to the file (/countly-push/ for /countly_sw.js), so it never replaces your site's own service worker. Do not put pages of your site in that folder. If you serve the file from another path or under another name, give its path with push_service_worker_path. A file name without "countly" or "push" in it stays clear of ad blocker rules, which already block the service worker files of other push providers by name.
Enabling Push
Ask the visitor for permission and subscribe them by calling enable_push_notifications, for example when they click a button. Browsers show the permission prompt only in response to a user action, so call it directly in the click handler, not through Countly.q, after a timer or after another asynchronous step. With asynchronous loading, the method exists only once the SDK has loaded, so enable your button then.
document.getElementById("enable-push").addEventListener("click", function () {
Countly.enable_push_notifications().then(function (result) {
if (result.subscribed) {
console.log("Subscribed to push notifications");
} else {
console.log("Push notifications are not enabled:", result.reason, result.error);
}
});
});The returned promise resolves with an object. subscribed is true when the visitor is subscribed, and endpoint then holds the browser's push address. Otherwise reason tells why:
-
"unsupported"- the browser does not support web push here -
"no_consent"- thepushconsent is not given -
"missing_vapid_key"or"invalid_vapid_key"- no VAPID public key was given, or it is not a valid key -
"denied"or"default"- the visitor refused or dismissed the permission prompt -
"service_worker_conflict"- another service worker file is registered for the scope -
"service_worker_redundant"or"service_worker_missing"- the service worker failed to install, or the registration you gave has no service worker -
"timeout"- the browser did not finish subscribing withinpush_subscribe_timeout(30000 milliseconds by default), so you can try again later -
"subscription_conflict"- another push provider's subscription uses the scope -
"disabled"-disable_push_notificationswas called while subscribing -
"error"- anything else, anderrorthen names the error, for example"AbortError: Registration failed - push service error"
If the visitor refused, browsers do not let the page ask again. The visitor then has to allow notifications for your site in the browser's settings.
You need to call enable_push_notifications only once for each visitor. On later page loads the SDK keeps the subscription up to date by itself while the visitor allows notifications: it registers the visitor without a prompt and sends the push token again when needed, once per session with track_sessions and otherwise once per page load. For this, the browser also checks for a newer countly_sw.js on each page load of a subscribed visitor, which is one small request to your site. To turn the automatic registration off, set push_auto_register to false during init and call enable_push_notifications yourself.
Disabling Push
To unsubscribe the visitor, call disable_push_notifications. It works without consent, and its promise resolves with unsubscribed and, when that failed, a reason and an error. The SDK remembers this choice across page loads, so the automatic registration does not subscribe the visitor again; only a new enable_push_notifications call does. With storage: "none", the choice holds only for the current page.
Countly.q.push(['disable_push_notifications']);Countly.disable_push_notifications().then(function (result) {
console.log("Unsubscribed:", result.unsubscribed);
});opt_out also takes Countly's push subscription off the browser. After opt_in, call enable_push_notifications to subscribe the visitor again.
Deep Links
When a visitor clicks a notification or one of its buttons, the browser opens the URL you gave to that notification or button on the dashboard. Only http and https URLs are opened, and the click is recorded in any case. If a tab of your site already shows that URL, Countly's service worker brings it to the front instead of opening another one. A notification without a URL opens nothing.
Handling Push Callbacks
The SDK records notification clicks by itself, as [CLY]_push_action events. To act on notifications in your own code, set a listener with the push_notification_listener init option or with set_push_notification_listener:
Countly.push_notification_listener = function (event) {
if (event.type === "clicked") {
console.log("Notification clicked:", event.title, event.url, event.payload);
}
};
// Initialize the SDKCountly.init({
app_key: "YOUR_APP_KEY",
url: "https://yourdomain.com",
push_vapid_public_key: "YOUR_VAPID_PUBLIC_KEY",
push_notification_listener: function (event) {
if (event.type === "clicked") {
console.log("Notification clicked:", event.title, event.url, event.payload);
}
}
});
// or set it later
Countly.set_push_notification_listener(function (event) {
console.log(event.type, event.messageId);
});The listener gets an object with these properties:
-
type-"received"when the notification was shown,"clicked"when the visitor clicked it,"closed"when it was dismissed -
messageId,title,message- the message's ID, title and text -
url- for a click, the URL of what was clicked, otherwise the notification's URL -
buttons- the notification's buttons, each with atitleand aurl -
buttonIndex,buttonTitle- for a click, 0 and an empty title when the notification itself was clicked, 1 or 2 and the button's title for a button -
payload- the whole message as your server sent it, including the custom data you added to it
"received" and "closed" reach every open page of your site, and "clicked" reaches one page. A click made while no page of your site was open is recorded by the service worker and reported to the listener of the next page that opens. Whether "closed" is reported depends on the browser and the operating system: some do not report it, and some report it together with a click.
Call record_push_action(messageId, buttonIndex) only if you show Countly's notifications with your own service worker code instead of countly_sw.js. With countly_sw.js the clicks are already recorded, and calling it as well counts them twice.
Using Your Own Service Worker
If your site already runs a service worker, or another push provider manages your site's service worker registrations, you can import countly_sw.js into your own service worker instead of serving it on its own. Serve a copy of countly_sw.js of the SDK version your pages use, import it in your service worker, and pass your service worker's registration to the SDK with push_service_worker_registration:
// your service worker, for example /sw.js
// optional, before the import: keep full control of install and activate
// (otherwise the import calls skipWaiting() on install and clients.claim() on activate)
self.COUNTLY_PUSH_LIFECYCLE = false;
// a copy of countly_sw.js that your site serves, of the SDK version your pages use
importScripts("/js/sw-addon-26.8.0.js");
// your own handlers follow; leave Countly's pushes to the imported code
self.addEventListener("push", function (event) {
var payload = event.data ? event.data.json() : {};
if (payload.c && payload.c.i) {
return;
}
// show your own notification here
});navigator.serviceWorker.register("/sw.js").then(function (registration) {
Countly.init({
app_key: "YOUR_APP_KEY",
url: "https://yourdomain.com",
push_vapid_public_key: "YOUR_VAPID_PUBLIC_KEY",
push_service_worker_registration: registration
});
});The imported code handles only Countly's notifications. Before the import you can also set self.COUNTLY_PUSH_DEBUG = true to log every push, click and close while debugging, and self.COUNTLY_PUSH_PERSIST = false if your pages use storage: "none". You can find a complete example in example_custom_sw.js.
Setting up Credentials
Web push uses VAPID keys instead of the credentials of mobile push services. Generate a VAPID key pair for your application under Web Push (VAPID) in the push settings on your Countly dashboard, download the keys and keep them safe, then copy the public key into push_vapid_public_key. The private key stays on your Countly server.
If you generate new VAPID keys, put the new public key in push_vapid_public_key at the same time. Visitors who allowed notifications are moved to the new key when they next visit your site, and those who do not come back stop receiving notifications.
Consent
If consent is required, web push needs the push consent. Without it, enable_push_notifications resolves with reason "no_consent" and clicks are not recorded; clicks made in the meantime are recorded once the consent is given. Removing the consent takes Countly's push subscription off the browser. disable_push_notifications works without consent.
Browser Support and Limitations
- On iPhone and iPad (iOS and iPadOS 16.4 and later), web push works only in a web app that the visitor added to their Home Screen. In Safari itself,
enable_push_notificationsresolves with reason"unsupported". - Safari shows no notification buttons, and desktop Firefox shows them from version 152 on.
- Web push is not available in sandboxed iframes without
allow-same-origin, in some private windows, or in webviews without service worker support. There the push methods resolve with reason"unsupported", and the rest of the SDK keeps working. - A device ID change without merge moves the browser's push subscription from the previous user to the new one, so on a shared computer the previous user's notifications stop arriving. A change with merge keeps the subscription with the user.
- Use one Countly app with push per service worker scope. If two apps on your site use push, give each its own
push_service_worker_scope, for example/countly-push-other/. Do the same for an app that does not use push but gets thepushconsent and loses it later, for example through a cookie banner that removes all consents: removing the consent there takes away the subscription of the app that uses push.
User Location
With the SDK integrated into your website, you might want to track your user location. By default, the Countly Server uses the GeoIP database to deduce a user's location from the coming requests (you can check details of it from here). However the SDK provides the ability to set custom locations for your users.
Setting Location
There are 3 location parameters that can be provided with the SDK:
- Country code in the two-letter, ISO standard ("jp", "gr" etc.)
- City name ("Kyoto", "Athens" etc.)
- Your user’s IP address
You can provide any of these information while initializing the SDK:
// you can directly provide the city and the country code
Countly.city = "Tokyo";
Countly.country_code = "jp";
// or you can provide the ip address
Countly.ip_address = "198.168.1.1";
// Initialize the SDKCountly.init({
app_key: "YOUR_APP_KEY",
url: "https://your.server.ly",
// you can directly provide the city and the country code
city: "Tokyo",
country_code: "jp",
// or you can provide the ip address
ip_address: "198.168.1.1",
});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 enable these scripts to be loaded from somewhere else other than the Countly server SDK sends information to (for example your internal 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:
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://try.count.ly";
Countly.heatmap_whitelist = ["https://you.domain1.com", "https://you.domain2.com"];
Countly.init({
app_key:"YOUR_APP_KEY",
url: "https://try.count.ly",
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.
Countly.q.push(['track_clicks']);Countly.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 SDK and start sending click data, all generated heatmaps may be viewed under Analytics > Heatmaps, as shown below:
Tracking Scrolls
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 > Heatmaps. You may change the heatmap type on the top bar once a view is open.
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 triggers. For example, after SDK initialization or 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.
The callback is also called when fetching fails. The first parameter is then an error, and the second holds the values stored from the last successful fetch, or an empty object if there are none. This applies to the callbacks of fetch_remote_config as well.
// 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);
}
};// in your Countly init script
Countly.init({
app_key:"YOUR_APP_KEY",
url: "https://try.count.ly",
debug: true,
remote_config: true
});
// OR
// provide a callback to be notified when configs are loaded
Countly.init({
app_key:"YOUR_APP_KEY",
url: "https://try.count.ly",
debug: true,
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. Users can leave feedback through Feedback Widgets (Surveys, NPS, Ratings). With the help of these widgets, you can ask your customers multiple questions and learn about their opinions and preferences in detail.
Feedback Widgets
Feedback Widgets is a Countly Enterprise plugin.
It is possible to display 3 kinds of feedback widgets: NPS, Survey and Rating.
For more detailed information about Feedback Widgets, you can refer to here.
Before any feedback widget can be shown, you need to create them in your Countly dashboard.
To simply show available widgets to your users you can use the methods available through the feedback interface. These methods will take a single parameter:
- nameTagOrID - String value to select a widget according to its name, tag or ID (optional)
If this value is not provided or no widget with the provided parameter can be found it will display the first widget available instead:
Countly.q.push(() => { Countly.feedback.showNPS("nameTagOrID"); });
Countly.q.push(() => { Countly.feedback.showSurvey("nameTagOrID"); });
Countly.q.push(() => { Countly.feedback.showRating("nameTagOrID"); });
// or
Countly.q.push(["feedback.showNPS", "nameTagOrID"]);
Countly.q.push(["feedback.showSurvey", "nameTagOrID"]);
Countly.q.push(["feedback.showRating", "nameTagOrID"]);Countly.feedback.showNPS("nameTagOrID");
Countly.feedback.showSurvey("nameTagOrID");
Countly.feedback.showRating("nameTagOrID");If you need a more complex logic to display your widgets you can check extended documentation from here.
The segmentation you set as global content segmentation is added to the events of feedback widgets too, and the SDK passes your site's light or dark theme to them (see Content Theme).
Manual Reporting
Reporting Feedback Widgets manually consists of 3 main steps:
- Fetching widget list from the server with 'get_available_feedback_widgets'
- Fetching one widget's data from that list with 'getFeedbackWidgetData'
- 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 });
}
}
);
})
}Consent
If consents are enabled, to use Feedback Widgets you have to provide the 'feedback' and the 'star-rating' consents for it to work.
User Profiles
User Details
You can provide Countly with user information like a username or email address. This will allow you to distinguish user on the "User Profiles" tab, which is available with Countly Enterprise Edition.
If a parameter is set as an empty string, it will be deleted on the server side.
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",
...
}
});For uploading a user profile picture file directly (if consent is required, this needs the users consent):
// assuming you have an input element that accepts images
var fileInput = document.getElementById("profilePicInput");
var file = fileInput.files[0];
if (!file) {
alert("Please select an image file.");
return;
}
Countly.q.push(["uploadUserProfilePicture", file]);// assuming you have an input element that accepts images
var fileInput = document.getElementById("profilePicInput");
var file = fileInput.files[0];
if (!file) {
alert("Please select an image file.");
return;
}
Countly.uploadUserProfilePicture(file);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 serverCountly.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 serverOrientation 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.
// in your Countly init script
Countly.enable_orientation_tracking = false;//to disable orientation tracking
Countly.init({
app_key:"YOUR_APP_KEY",
url: "https://try.count.ly",
enable_orientation_tracking: false //this will disable orientation tracking
});Application Performance Monitoring
If you want to record some performance metrics regarding your website there are 2 ways to report these performance traces with Countly. 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 our 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:
//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
}
}]);//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 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
This section talks about how to set up GDPR compliant consent management with the Countly Web SDK.
If consent management is enabled then features of the SDK would require consent to be provided before start working. This way you would have full control over your user tracking in every part of your website.
It should be noted that there is no persistency for consent state in the SDK and the developer is fully responsible for saving/keeping the state of the user's consent before providing it to the SDK.
Feature Names
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 direct attribution tracking
- 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 and rating widgets usage and reporting
- apm - allows performance tracking of application by recording traces
- location - allows a user’s location (country, city area) to be recorded
- remote-config - allows users to download remote config from the server
- content - allows content (content zone and journey content) to be fetched and shown
- push - allows the visitor to be subscribed to push notifications and their notification clicks to be recorded
Setup During Init
To enable consent management mode ( and to disable tracking until consent is given for a specific feature), all you need to do is pass true to the require_consent config flag during SDK initialization.
// in your Countly init script
Countly.require_consent = true;Countly.init({
app_key: "YOUR_APP_KEY",
url: "https://your.server.ly",
require_consent: true // this will enable consent management
});Changing Consent
Upon a visitor’s arrival to your website, you could check if you already have consent from this visitor. If not, you could 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).
Also you can also allow the user to change their mind regarding separate settings and when changes are going to be made you can 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:
// to add consent {string|array}
Countly.q.push(['add_consent', feature]);
// to remove consent {string|array}
Countly.q.push(['remove_consent', feature]);
// to add consent {string|array}
Countly.add_consent(feature)
// to remove consent {string|array}
Countly.remove_consent(feature)Feature Groups
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 featureCountly.group_features({
all:["apm","attribution","clicks","content","crashes","events","feedback","forms","location","push","remote-config","scrolls","sessions","star-rating","users","views"]
});
//After this call Countly.add_consent("all") to allow all featuresFeature lists and groups written for an SDK version before 26.8.0 do not include the content and push features. Add them if you use the content zone or web push.
Security and Privacy
Parameter Tamper Protection
You can set an optional salt for the checksum calculation of request data which will be sent alongside each request, using the &checksum field. You will need to set the exact same salt on the Countly server (Management > Applications> Salt for checksum). When the salt on the Countly server is set, all requests would be checked for the validity of the &checksum field before being processed.
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://yourdomain.com";
Countly.salt = "your_salt";
// init after
Countly.init({
app_key: "YOUR_APP_KEY",
url: "http://yourdomain.com",
salt: "your_salt"
});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)
- disable_sdk_behavior_settings_updates - set it to true to disable SDK behavior setting updates (default: false)
- disable_backoff_mechanism - set it to true to disable request backoff when server is busy logic (default: false)
- 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)
- behavior_settings - an object that includes server config options taken from your server (experimental!)
- content_whitelist - an array that includes urls to your other domains which can serve the Content
- max_breadcrumb_count - the maximum amount of breadcrumbs to store for crash logs (default: 100)
- ignore_referrers - array with referrers to ignore (default: none)
- salt - string salt for checksums (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: true)
- 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 "cookie" to force only specific storage, use "none" to not use any storage and keep everything in memory, or provide an object with
getItem,setItemandremoveItemmethods to use your own storage. Except with "none" or your own storage, a page keeps the events and requests it has not sent yet in its tab's session storage (see SDK Storage and Requests) - metrics - provide metrics override or custom metrics for this user. For more information on the specific metric keys used by Countly, check here.
-
track_form_values - set to false to record the form submissions of
track_formswithout the values of the fields (default: true) - content_zone_timer_interval - how often the content zone checks for new content, in seconds (default: 30, min. 15)
- global_content_segmentation - an object with segmentation to add to the events of content and feedback widgets (default: none)
- content_theme - the theme to pass to content and feedback widgets: "dark", "light", false for none, or null to follow your site (default: null)
- push_vapid_public_key - your application's VAPID public key, needed for web push (default: none)
- push_service_worker_path - the path of the push service worker file on your site (default: "/countly_sw.js")
- push_service_worker_scope - the scope to register the push service worker under (default: the countly-push/ folder next to the service worker file)
-
push_service_worker_registration - the registration of your own service worker that imports countly_sw.js, or the promise
register()returns for it (default: none) - push_auto_register - set to false to turn off the automatic registration of visitors who already allowed notifications (default: true)
-
push_subscribe_timeout - how long
enable_push_notificationswaits for the browser to subscribe, in milliseconds (default: 30000) - push_notification_listener - a function called when a notification is received, clicked or closed (default: none)
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"
});Example Integrations
Examples are located in examples folder.
Angular folder contains a basic Angular integration.
react folder contains a basic React integration.
Symbolication folder covers crash integrations and reporting.
mpa folder contains basic MPA integration.
example_apm is a simple APM integration.
example_apm_async is a simple async APM integration.
example_formdata is a simple integration to collect data from form data.
example_gdpr is a simple integration to show how giving consent works.
example_multi_instances is a simple demonstration for multi instancing the Countly SDK.
examples_feedback_widgets is a simple integration for feedback widgets.
example_remote_config is a simple integration for remote config.
example_sync is a sample integration for sync operations.
example_web_worker is a sample for web worker.
example_async is a sample integration for async usage of the SDK.
example_quick_async is a sample integration with the quick loader.
example_web_push is a sample integration for web push notifications.
example_custom_sw.js is a sample of a site's own service worker that imports Countly's push service worker.
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 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, by providing your own storage, or by 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 (30 days after the value was last written) and the information stored in them would be rendered obsolete after a while. Lastly, if you decide not to store any information, all information will stay in memory and would be gone when the page is closed.
While a page is open and sending works, the events and requests it has not sent yet are kept in its tab's session storage, also when cookies are the main storage. Requests that cannot be sent for now, for example in offline mode or after a failed request, and the requests of a page that is being left wait in local storage. A tab of your site that is in front sends them before its own requests, oldest first, so each request is sent once even when several tabs of your site are open. With "none" or your own storage, the SDK keeps its queues in memory or in that storage instead.
These options can be selected during the initialization:
//possible options are "localstorage", "cookie", "none" or your own storage object
Countly.storage = "localstorage";//possible options are "localstorage", "cookie", "none" or your own storage object
Countly.init({
app_key:"YOUR_APP_KEY",
url: "https://try.count.ly",
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 second 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);The SDK leaves sensitive data out:
- It never reads password fields, also ones the visitor revealed as text, fields whose
autocompleteattribute marks a password, a one-time code or payment card data (cc-values), and values that contain a payment card number. - Fields named or labelled like secrets, payment card, bank or identity data (for example password, pin, cvv, iban, ssn, passport or token) and hidden fields are read only when you mark them with a cly_user_{key} class.
- An email address is taken only from a field that holds one address, and a phone number only from a phone field (
type="tel", or named or labelled like phone, mobile, tel or cell) that holds a phone number. A field named like "number" is not taken as the phone; mark such a field with the cly_user_phone class.
If you would explicitly like to exclude some form input from being processed, just add the css class cly_user_ignore to that element, or to an element that contains several inputs. 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. Unless you set the second parameter as true, only the keys name, username, email, organization, phone, picture, gender and byear are kept.
<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>
<!-- ignore every input in this element -->
<div class="cly_user_ignore">
<p><input type="text" name="note" value="not for analytics"></p>
</div>
<!-- read this input as the phone number -->
<p><input type="text" name="contact_number" value="+1 555 123 4567" class="cly_user_phone"></p>
<!-- get customfield by class (kept when the second parameter is true) -->
<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.
//user stored conversion data
Countly.q.push(['recordDirectAttribution']);
//or provide campaign id yourself
Countly.q.push(['recordDirectAttribution', "MyCampaignID"]);//user stored conversion data
Countly.recordDirectAttribution();
//or provide campaign id yourself
Countly.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.
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 report the values of the form's fields as an event with the formSubmit key, together with the form's id, name, action and method and the current view.
The SDK leaves sensitive data out of this event. It never records password fields, also ones the visitor revealed as text, fields whose autocomplete attribute marks a password, a one-time code or payment card data (cc- values), and values that contain a payment card number. Fields named or labelled like secrets, payment card, bank or identity data (for example password, pin, cvv, iban, ssn, passport or token), and values shaped like a social security number (such as 123-45-6789, or any nine digits), are left out unless the field has the cly_form_allow class. To leave out a field, or all fields inside an element, add the cly_user_ignore class to it.
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);To record form submissions without the values of the fields, set track_form_values to false during init. The event then carries only the form's id, name, action and method and the view.
Countly.track_form_values = false;
// Initialize the SDKCountly.init({
app_key: "YOUR_APP_KEY",
url: "https://your.server.ly",
track_form_values: false
});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>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.
Percent-encode the value, for example with encodeURIComponent(Countly.get_device_id()), so that characters such as @, + or & arrive unchanged. The other site uses the ID from the URL only when it has no device ID stored for the visitor yet.
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. While a page is open, the events and requests it has not sent yet are kept in its tab's session storage. The keys start with your app key, and with your namespace if you set one, for example YOUR_APP_KEY/cly_id.
- cly_queue - in session storage, the requests of the tab that are not sent yet; in local storage, the requests that wait for a tab of your site to send them
- cly_event - a queue of events reported by SDK
- cly_handover - tells the other open tabs of your site that the device ID or the user's profile changed
- 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_id_type - current user's device id type
- cly_cmp_id - last campaign id user came from
- cly_cmp_uid - user identifier for last campaign
- cly_session - when the current session expires, so that the next page continues the session instead of starting a new one
- 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
- cly_config - the SDK behavior settings last received from the server
-
cly_fb_widgets - the rating widgets enabled with
enableRatingWidgets - cly_hc_error_count, cly_hc_warning_count, cly_hc_status_code, cly_hc_error_message, cly_hc_backoff_count, cly_hc_consecutive_backoff_count - counters for the SDK's health check
- cly_push_endpoint, cly_push_vapid_key, cly_push_scope - the push subscription registered for this browser
- cly_push_session_token - the push token sent in the current session, so that it is not sent again
-
cly_push_opt_out - set when push notifications were turned off with
disable_push_notificationsoropt_out - cly_push_merged_ids - up to 20 device IDs merged into other ones, so that notification clicks recorded for a merged device ID count for the new one
Countly's push service worker keeps the notification clicks it has not reported yet, and the server details it needs to report them, in an IndexedDB database named countly_push.
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:
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"
}]);
// 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
var Countly2 = Countly.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
Countly2.add_event({
key:"second_app"
});SDK Internal Limits
If values or keys provided by the user exceeds certain internal limits, they will be truncated. Please have a look here for the list of properties effected by these limits.
You can override these internal limits during initialization.
Key Length
max_key_length - 128 chars by default. Keys that exceed this limit will be truncated.
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_key_length = 50;
Countly.init({
app_key:"YOUR_APP_KEY",
url: "YOUR_SERVER_URL",
max_key_length: 50
});Value Size
max_value_size - 256 chars by default. Values that exceed this limit will be truncated.
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_value_size = 12;
Countly.init({
app_key:"YOUR_APP_KEY",
url: "YOUR_SERVER_URL",
max_value_size: 12
});Segmentation Values
max_segmentation_values - 100 dev entries by default. Key/value pairs that exceed this limit in a single event will be removed.
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_segmentation_values = 67;
Countly.init({
app_key:"YOUR_APP_KEY",
url: "YOUR_SERVER_URL",
max_segmentation_values: 67
});Breadcrumb Count
max_breadcrumb_count - 100 entries by default. If the limit is exceeded, the oldest entry will be removed from stored breadcrumbs.
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_breadcrumb_count = 45;
Countly.init({
app_key:"YOUR_APP_KEY",
url: "YOUR_SERVER_URL",
max_breadcrumb_count: 45
});Stack Trace Lines Per Thread
max_stack_trace_lines_per_thread - 30 lines by default. Crash stack trace lines that exceed this limit (per thread) will be removed.
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_stack_trace_lines_per_thread = 23;
Countly.init({
app_key:"YOUR_APP_KEY",
url: "YOUR_SERVER_URL",
max_stack_trace_lines_per_thread: 23
});Stack Trace Line Length
max_stack_trace_line_length - 200 chars by default. Crash stack trace lines that exceed this limit will be truncated.
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_stack_trace_line_length = 10;
Countly.init({
app_key:"YOUR_APP_KEY",
url: "YOUR_SERVER_URL",
max_stack_trace_line_length: 10
});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.
Countly.queue_size = 5000;Countly.init({
app_key:"YOUR_APP_KEY",
url: "https://try.count.ly",
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:
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
Countly.init({
app_key:"YOUR_APP_KEY",
device_id:"1234-1234-1234-1234",
// add your custom tags and set their value to true
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:
- First integrate Countly into your project (You can reach details from here)
- Then declare the plugin script before your Google Analytics implementation block
- 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:
Running the SDK in Web Worker Context
SDK can be used in a dedicated Worker if needed. Currently Module Workers are not supported so instead the SDK should be imported by importScripts method. It would look something like this:
// Path or URL
importScripts("../path/to/countly.js");After importing the Countly script in your worker, you can call the Countly methods as usual. However, Countly in Web Workers has limited availability. Currently only the manual tracking methods are supported. Moreover the SDK would not use persistent storage by default. But now it extends its storage methods with which you can provide your own storage logic to make things persistent.
A sample worker (let's say 'worker.js') could look like this:
importScripts("../path/to/countly.js"); // CDN is possible
const STORE={}; // in-memory storage for worker
Countly.init({
app_key: "YOUR_APP_KEY",
url: "https://your.domain.countly",
debug: true,
storage: {
// getItem will recieve a string key param with which it should return the item under it
getItem: function (key) {
return STORE[key];
},
// setItem will recieve two params, a string key and a value of any type, then it should store the value under the key
setItem: function (key, value) {
STORE[key] = value;
},
// removeItem will recieve a string key with which it should erase the key and any data under that key
removeItem: function (key) {
delete STORE[key];
}
}
});
onmessage = function (e) {
console.log(`Worker: Message received from main script:[${JSON.stringify(e.data)}]`);
// Get an process messages to worker
const data = e.data.data; const type = e.data.type;
if (type === "event") { // you can send an event
Countly.add_event(data);
} else if (type === "view") { // you can record a view
Countly.track_pageview(data);
} else if (type === "session") { // you can manually control sessions
if (data === "begin_session") {
Countly.begin_session();
return;
}
Countly.end_session(null, true);
}
}In your website, an example communication can happen like this with the worker:
// create a worker
const myWorker = new Worker("worker.js");
// send messages to the Worker
function clickEvent() { // send event
myWorker.postMessage({ type: "event", data: myEvent });
}
function recordView() { // track views
myWorker.postMessage({ type: "view", data: "home_page" });
}
function beginSession() { // start a session
myWorker.postMessage({ type: "session", data: "begin_session" });
}
function endSession() { // end a session
myWorker.postMessage({ type: "session", data: "end_session" });
}
Opt In / Opt Out
The Countly SDK will always be opt in by default, meaning all tracking is enabled, but you may easily disable all tracking by selecting the opt_out method. It will persistently save settings to prevent tracking after page reloads. Then if you want to opt in again you can use opt_in method to resume tracking at next page load.
// to stop tracking user data at next page load
Countly.q.push(['opt_out']);
// to resume tracking user data at next page load
Countly.q.push(['opt_in']);// to stop tracking user data at next page load
Countly.opt_out();
// to resume tracking user data at next page load
Countly.opt_in();Disabling tracking for specific users with these methods can be sufficient for testing or situations where you decide to not track a specific user's information from now on. However, should you desire more granular feature control, checkout the User Consent 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.
opt_out also takes Countly's push subscription off the browser. After opt_in, call enable_push_notifications to subscribe the visitor to push notifications again.
Extended Feedback Widget Options
If you have many widgets and the way you want to show them is intricate you can use the methods shown below.
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. 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).
//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 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);
}
//Fetch user's feedback widgets from the server
Countly.get_available_feedback_widgets(feedbackWidgetsCallback);
// Feedback widget callback function, err is for error and countlyPresentableFeedback contains an array of widget objects
function feedbackWidgetsCallback(countlyPresentableFeedback, err) {
if (err) {
console.log(err);
return;
}
// Decide 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 Widgets' show policies are handled internally by the Web SDK.
Extended Device ID Management
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");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.
A change without merge also:
- sends the events recorded so far, the duration of the current view and unsaved
userDatachanges for the previous device ID, - ends the previous user's session, and starts a session for the new user only with automatic session tracking (
track_sessions); with manual sessions, callbegin_sessionfor the new user, - exits the content zone and closes the previous user's content; call
content.enterContentZoneagain for the new user, - moves this browser's push subscription from the previous user to the new one.
The other open tabs of your site first send what they recorded under the previous device ID, then continue with the new one.
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.
Countly.q.push(['change_id', "myNewId", true]);Countly.change_id("myNewId", true);After a change with merge, the SDK holds back requests such as the remote config, A/B testing, feedback widget and content requests until the merge request has reached the server. This way the server moves the data of the previous device ID to the new one instead of combining two users.
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 and comes with TypeScript type definitions (lib/countly.d.ts in the countly-sdk-web package), so you can import it into your TypeScript project with import Countly from "countly-sdk-web" and get type checking for its methods. 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, the SDK sends its requests with the POST method by default (the force_post option). 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.
What Information is Collected by the SDK?
The data that SDKs gather to carry out their tasks and implement the necessary functionalities is mentioned in here