JavaScript Verify and troubleshoot
Troubleshooting
Why do scripts load twice or before consent?
Check. Each vendor needs one owner. Look for a snippet left in
index.html, a second init() call, or a script loader attached to a kernel
that @c15t/browser or createConsentRuntime already owns. Each of these loads
vendors outside the consent gate or twice.
Fix. Call init() or create the runtime once per page, in the browser
entry point, and pass every vendor through its scripts list.
Why does the page reload when a visitor saves?
Check. Did the visitor turn off a category they had allowed?
@c15t/browser and createConsentRuntime reload the page then, because code
that already ran cannot be unloaded. Allowing categories never reloads.
Fix. To handle withdrawal yourself, pass reloadOnConsentRevoked: false,
and stop each vendor with its own opt-out call. See
scripts.
Why is my custom banner empty?
Check. Confirm that you subscribed before calling runtime.start(), that
you wait for resolution.status to be 'matched', and that you render
promptRequirement.kind 'notice' as well as 'choice'.
Fix. Render the headless UI from the snapshot, not from assumptions. Headless lists what a custom UI must cover.
Why does the build fail to download the manifest?
Check. vite build stops with an error from consentManifest that
starts with the plugin's name, such as
@c15t/core/build: could not fetch the consent manifest from <url> during the build. The name is @c15t/core/build for c15t/build and @c15t/vue/vite
for c15t/vue/vite. vite dev logs the same message as a warning and keeps
going. The part in parentheses names the cause:
| Message | Cause | Fix |
|---|---|---|
fetch failed, with ENOTFOUND, ECONNREFUSED or another network error | The build cannot reach the backend | Build where the backend is reachable |
no response within 10 seconds | The backend did not answer in time | Check the backend, or build where it is reachable |
/manifest responded 404 | The URL is not your project's backend, such as the https://your-project.inth.app placeholder or a URL missing its path prefix | Copy the backend URL exactly as Inth shows it, or set VITE_C15T_BACKEND_URL |
/manifest returned an invalid consent manifest. | The URL answered with something other than a consent manifest, such as an HTML page | Point backendURL at the backend itself, not your site or a dashboard page |
A failed download never reuses an old snapshot. To deploy while the backend
is down, run the build with C15T_ON_BUILD_ERROR=runtime. snapshot is then
undefined, and the browser fetches the policy from the backend at runtime.
Only a build that uses manifest() downloads the manifest. The plugin
fetches after Vite has dropped the code the app does not use, so a hosted()
or offline() build never contacts the backend and never fails this way.
An error or warning that says no backend URL is set means the plugin found
no backendURL option and no variable. Set VITE_C15T_BACKEND_URL (or
VITE_INTH_PROJECT_URL) in .env or the build environment, or pass the absolute backend URL from your Inth
project. The troubleshooting guide
lists every message.
A notice that the build skipped the consent manifest fetch means the backend
URL is relative, such as /api/c15t. Pass the absolute backend URL instead.
With onBuildError: 'fail', this stops the build with build-time manifests require an absolute upstream URL.
Why is snapshot undefined?
Check. snapshot from c15t/generated is undefined, and the browser
requests ${backendURL}/manifest on page load.
Fix. snapshot is undefined when Vite has no policy to serve:
consentManifestis missing frompluginsinvite.config.ts. Without the plugin,c15t/generatedstill resolves, but exportsundefined.- The fetch failed in
vite dev, or in a build withC15T_ON_BUILD_ERROR=runtime. The terminal shows acould not fetch the consent manifestwarning. - The backend URL is relative, so the plugin skipped the fetch.
consentManifest({ source: 'runtime' })is set, so the plugin never fetches.
Fix the cause, then restart vite dev or rebuild. The plugin fetches once
when Vite starts.
Why does /init fail with a CORS or CSP error?
Check. A CORS error means the backend does not trust your app's origin.
Fix. Add the exact origin, including the port in development, to your Inth project's trusted origins.
Check. A CSP error means your policy's connect-src lacks the backend's
origin.
Fix. Add the backend's origin to connect-src. See
Content Security Policy.
Why does a gated script in my HTML never run?
Check. With the nonce option set, @c15t/browser runs only
<script type="text/plain" data-c15t-category> tags that carry the same
nonce. Open the console and look for a warning that c15t skipped a
data-c15t-category script, and check the tag for
data-c15t-activated="untrusted".
Fix. Add the page's nonce to the tag, including tags your code inserts later. See Content Security Policy.
Why is the stock banner unstyled?
Check. Look at your Content Security Policy's style-src. If it does not
allow the <style> element the stock UI adds, the policy blocks it.
Fix. Pass the nonce from your style-src as the nonce option, or import
@c15t/browser/styles.css with ui: { shadow: false, styles: false } as
Content Security Policy
describes.
Why does a fetch return status 451?
Check. A network blocker
rule matched the request and its category is not allowed. The console logs
[c15t] blocked with the rule's ID.
Fix. If the request should not match, check the rule's domain and
pathIncludes.
Inspect the active policy
Call this helper with your kernel: consent.kernel from @c15t/browser,
runtime.kernel from createConsentRuntime, or one you created. It logs the
current state and later changes without recording a choice.
The helper returns an unsubscribe function. Call it when you remove the
diagnostic or dispose the app. Production gates should read
kernel.getSnapshot().effectivePermissions at the point where an optional
feature would run.
More help
Troubleshoot consent covers problems shared by every framework, such as a missing banner, analytics that load before a choice, imports that fail because npm installed c15t v2, choices that disappear on reload, server HTML that differs from the browser, static builds that fail and content blockers that hide the consent UI.