Web Analytics docs

fmeng.js works on websites, H5 pages, web apps and single-page apps. This guide covers installation, options, custom events and verification.

Quick start

Three steps, no backend changes:

1
Add a siteSign in to the console and add your site under My sites with a name and domain to get its SITE_KEY.
2
Paste the codePut the install snippet before </head> on every page of your site.
3
VerifyClick "Verify installation" in Site settings → Tracking code, then open any page of your site.

Install the script

Paste this line before </head> on every page. Find your SITE_KEY in Site settings → Tracking code. The script loads with defer and never blocks rendering.

Served via Cloudflare (t.t3j.com), best when most visitors are outside mainland China
html
<!-- Fmeng Analytics -->
<script defer src="http://t.t3j.com/fmeng.js" data-site="SITE_KEY"></script>

Options

Tune collection with data-* attributes on the script tag:

AttributeDefaultDescription
data-siteRequiredYour site key.
data-endpoint/collect on the fmeng.js originWhere events are sent.
data-auto"true"Track the first page load and route changes automatically; set to "false" to call fmeng.pageview() yourself.
data-perf"true"Collect page performance; set to "false" to turn it off.
data-errors"true"Collect unhandled JS errors and promise rejections; set to "false" to turn it off.
data-links"true"Collect outbound clicks and file downloads; set to "false" to turn it off.
data-clicks"false"Collect raw click points for heatmaps; off by default, set to "true" to enable.
data-click-sample0.2Sampling rate for heatmap clicks, from 0 to 1.
data-local"false"Allow sending from localhost, 127.0.0.1 and file pages — for local testing.
data-extra—Extra data as a JSON object, e.g. {"channel":"wechat"}: sent with every pageview and custom event (up to 20 keys); see Events → Page extra data. For the async snippet use &extra=<URL-encoded JSON>.
Example: enable click heatmaps with 50% sampling
html
<script
defer
src="http://t.t3j.com/fmeng.js"
data-site="SITE_KEY"
data-clicks="true"
data-click-sample="0.5"
></script>

Async loading & multiple sites

The same async pattern as Baidu Tongji hm.js: declare the command queue _fmq, then insert the script dynamically; put the site key in the site parameter (fmeng.js?SITE_KEY also works). Commands pushed before the script loads run once it has loaded, so there is no need to check window.fmeng:

html
<script>
var _fmq = _fmq || [];
(function () {
var fm = document.createElement('script');
fm.src = 'http://t.t3j.com/fmeng.js?site=SITE_KEY';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(fm, s);
})();
</script>
<script>
// push at any time: queued before load, run immediately after
_fmq.push(['track', 'signup', { plan: 'pro' }]);
_fmq.push(['identify', 'user-10086']);
</script>

One page can load several sites (for example a site-wide property plus one per section or author): insert one script per site and each site receives its own data. Calls through _fmq and window.fmeng go to every site on the page; use fmeng.get(SITE_KEY) to target one.

html
<script>
var _fmq = _fmq || [];
function loadFmeng(siteKey) {
var fm = document.createElement('script');
fm.src = 'http://t.t3j.com/fmeng.js?site=' + siteKey;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(fm, s);
}
loadFmeng('SITE_KEY_ALL'); // site-wide
var siteKeys = { alice: 'SITE_KEY_A', bob: 'SITE_KEY_B' };
if (window.pageAuthor && siteKeys[window.pageAuthor]) {
loadFmeng(siteKeys[window.pageAuthor]); // per-author
}
// send to one site only:
// window.fmeng && fmeng.get('SITE_KEY_A').track('like')
</script>

Other options can go in the URL too, matching the data-* attributes, e.g. fmeng.js?site=SITE_KEY&clicks=true&click_sample=0.2.

Single-page apps

Single-page apps built on the History API (Vue Router, React Router, …) need no extra code: fmeng.js listens to pushState, replaceState and popstate and records a pageview after each route change, deferred to the next tick so that most frameworks have already updated document.title.

To control it yourself, add data-auto="false" to the script and call fmeng.pageview() once navigation completes; calling it repeatedly for the same URL counts only once.

javascript
// With <script ... data-auto="false">, send pageviews yourself
// Vue Router
router.afterEach(() => {
window.fmeng && window.fmeng.pageview()
})
// React Router
const location = useLocation()
useEffect(() => {
window.fmeng && window.fmeng.pageview()
}, [location])

Custom events

Call fmeng.track(name, properties) on button clicks, form submissions and so on. Event names are up to 64 characters; up to 20 properties are kept, and non-string values are sent as JSON strings.

Break events down by property under Events, turn them into goals in Site settings → Goals, or use them as funnel steps.

javascript
// event name + optional properties. fmeng.js loads with defer: until it has run (or if it is blocked)
// window.fmeng is undefined, so check first — calls made before that are not replayed
window.fmeng && window.fmeng.track('signup', { plan: 'pro' })
document.querySelector('#buy').addEventListener('click', () => {
window.fmeng && window.fmeng.track('purchase', { sku: 'A100', price: 199 })
})

User identity

After sign-in, call fmeng.identify(userId) to link the visitor to your user (cross-device identity). IDs are up to 128 characters and stored in the browser. Call fmeng.identify(null) on sign-out to clear it.

Events already queued keep the previous identity. Don't use phone numbers, emails or other personal data as the ID.

javascript
// after a successful sign-in
window.fmeng && window.fmeng.identify('user-10086')
// on sign-out
window.fmeng && window.fmeng.identify(null)

What is collected

After installation the following is collected automatically (except click heatmaps, which you enable explicitly). Time on page and scroll depth ride along with pageviews; everything else can be turned off individually:

DataDescriptionHow to turn off
PageviewsFirst page load and SPA route changes, with page title and referrer.data-auto="false"
Time on pageVisible time of the page; the increment is sent when the page is hidden or left.Tracked with pageviews
Scroll depthMaximum scroll percentage per view (viewport bottom ÷ document height).Tracked with pageviews
PerformanceLCP, FCP, INP, CLS, TTFB and Load, once per full page load.data-perf="false"
JS errorsUnhandled errors and promise rejections; the same error is reported once per page, up to 10 per page.data-errors="false"
Outbound & downloadsClicks on cross-origin links, and downloads (download attribute or common file extensions).data-links="false"
Click heatmapClick positions and elements at the sampling rate, up to 100 per view.Off by default; enable with data-clicks="true"
Automated browsers (navigator.webdriver is true) never send data; crawler traffic is filtered by User-Agent at the collection gateway.

npm module

To control tracking from your bundled code, use createTracker from the ESM module fmeng-sdk; its options mirror the script attributes.

bash
npm install fmeng-sdk
typescript
import { createTracker } from 'fmeng-sdk'
const fmeng = createTracker({
site: 'SITE_KEY',
endpoint: 'http://t.t3j.com/collect',
// defaults shown below; turn off what you don't need
auto: true, // first load and route changes
perf: true, // page performance
errors: true, // JS errors
links: true, // outbound clicks and downloads
clicks: false, // click heatmap (opt-in)
})
fmeng.track('signup', { plan: 'pro' })

Verify installation

Click "Verify installation" in Site settings → Tracking code, then open any page of your site. The console checks every 3 seconds and shows "Data received. Installation successful" as soon as the first event arrives.

Testing locally

To keep data clean, fmeng.js doesn't send from localhost, 127.0.0.1 or file pages by default. Add data-local="true" while testing locally:

html
<script defer src="http://t.t3j.com/fmeng.js" data-site="SITE_KEY" data-local="true"></script>

No data? Check in this order

  1. data-site matches your site key;
  2. the page is not a local address (or has data-local="true");
  3. no ad blocker is blocking fmeng.js or /collect;
  4. the visiting IP or path is not in your exclusion rules.

Data & privacy

No raw IPs in the analytics database
The analytics database only holds a daily salted hash (for the IP count) and the resolved region. For geolocation, IPs are cached for up to 7 days to reuse lookups, and sent to the IP-geolocation provider when online lookup is enabled.
Bots filtered
The gateway filters crawlers by User-Agent keywords, and the SDK stays silent in automated browsers.
Browser storage
The visitor ID lives in localStorage (falling back to a first-party cookie); a session restarts after 30 minutes of inactivity or a new day.
Exclusion rules
Exclude by IP (CIDR supported) or path (* wildcards) in Site settings → Exclusions; changes apply within about a minute.

FAQ

Didn't find an answer?After signing in, check the announcements under the bell at the top right, or find our contact details on the About page.