Troubleshooting
Almost every “my form isn’t showing” report is one of six things, and they are cheap to check in order. Work down the list rather than guessing — each step rules out everything above it.
Turn on debug logging first
Before anything else. The SDK will tell you which check it failed, which usually ends the investigation immediately.
// npm packages
<IntunixProvider apiKey={key} config={{ debug: true }} />
// script tag
intunix('init', 'pk_live_xxx', { debug: true });The checklist
1. Is the form enabled for the Web SDK?
In the console, the form must have Available in Web SDK switched on. The backend enforces this unconditionally — a form without it is excluded from the response no matter what your code asks for.
Check the Network tab for GET /sdk/experiences. If your form is absent from the response body, stop here — the problem is this toggle, not your code.
2. Did you register it?
This is the second, independent gate. Being enabled in the console is not enough; your app must also ask for it.
// One of these must be true:
init(key, { register: ['your_form_slug'] })
init(key, { register: 'all' })
client.register('your_form_slug')
useRegisterForm('your_form_slug') // React / VueWith register omitted or [], the SDK makes no catalog request whatsoever. An empty Network tab is the expected behaviour, not a bug. Only showForm() works in that mode.
3. Is the trigger actually firing?
- Event triggers — is the event name an exact match? Are you calling
track()before or after the form was registered? Registration must come first. - URL triggers — check the match mode.
exactincludes query strings; you usually wantcontains. - Exit intent — desktop pointers only. It will never fire on a touch device.
- Time triggers — the timer resets on navigation.
To take triggers out of the equation entirely, call showForm('slug') from the browser console. If it appears, your trigger config is the problem. If it does not, continue down this list.
4. Does the user match the targeting?
The most common cause here is traits. They are held in memory only and do not survive a page reload — so if you only call identify() in your login handler, every reloaded page has an anonymous user and every trait rule fails to match.
// Call this on every app boot where the user is authenticated,
// not only at the moment they log in.
client.identify(user.id, { plan: user.plan })For context rules, confirm setContext() ran before the trigger fired, and that the key spelling matches the console exactly.
5. Is the user eligible?
minSessions and minDaysSinceFirstSeen are the usual culprits during testing — a fresh browser profile has one session and zero days of tenure, so a form requiring two sessions will never show you anything.
Also check startAt and endAt. A scheduling window that has closed silently disables the form.
6. Is it on cooldown?
If it showed once and then stopped, this is your answer. Cadence applies to manual showForm() calls too — a form set to once opens exactly once per user, forever.
Clear the ledger and try again:
Object.keys(localStorage)
.filter((k) => k.startsWith('intunix:'))
.forEach((k) => localStorage.removeItem(k))
sessionStorage.clear()
location.reload()Specific symptoms
Nothing at all happens, and the Network tab is empty
Either init() was never called, or register is empty. In React, confirm the provider is actually mounted — a provider inside a route that unmounts takes the client with it.
The SDK re-initialises on every render
You are passing an inline config object. It is a new reference each render, and the provider re-runs init() when that reference changes. Hoist it to a module constant.
// Wrong
<IntunixProvider apiKey={key} config={{ register: ['nps'] }} />
// Right
const CONFIG = { register: ['nps'] }
<IntunixProvider apiKey={key} config={CONFIG} />The key is empty in production but works locally
In Next.js, NEXT_PUBLIC_* is inlined at build time. If you build in Docker or CI, the value must be passed as a build argument — a runtime environment variable arrives too late and the browser sees an empty string. See Installation.
A form appears twice, or two forms stack
Two providers are mounted. The SDK queues forms so only one is on screen at a time, but that guarantee is per client instance — a second provider is a second client with its own queue.
The feedback button works once, then does nothing
Cadence. Set that form to always — a button the user deliberately clicks should not be rate-limited. See Cadence.
Targeting works locally but not in production
Usually a timing difference: identify() or setContext() lands after the trigger fires. Set them as early as you have the data, and remember that a URL trigger fires urlTriggerDelayMs (600 ms) after navigation — which is often shorter than an auth round trip.
Still stuck?
Collect these before getting in touch — they usually make the cause obvious:
- The form slug and your workspace name.
- The
GET /sdk/experiencesresponse body. - Console output with
debug: trueenabled. - Which package and version, and which framework.
Then reach us via the contact page.