Skip to main content

Svelte Consent API

Context getters

Where getters work

Svelte has no consent hooks or stores. @c15t/svelte exports context getters instead. Call one at the top level of a component's <script>, inside ConsentProvider, and read its properties in markup or in $derived. The properties read the provider's $state, so the component updates when consent changes.

A getter uses Svelte's getContext, so it throws when you call it:

  • in an event handler, an effect or a timer,
  • in a component rendered outside the provider,
  • in a plain .ts module.

Call it once at the top level and keep the returned object; its methods work from event handlers later. The same getters are exported from @c15t/svelte/headless.

GetterReturnsUse it to
getConsentManager()ConsentManagerStateRead permissions and choices, and record choices. Start here.
getHeadlessConsent()Surface state and actionsBuild your own banner. See headless.
getIAB()SvelteIABState or nullRead or change IAB TCF consent.
getConsentKernel()ConsentKernelSubscribe to raw events or call low-level commands.
getSnapshot()ConsentSnapshotRead the full kernel snapshot once.

Read a permission or a recorded choice

A permission answers "may this run now?" A recorded choice answers "what did the visitor decide?" They differ. Under an opt-out policy a category can be allowed before the visitor chooses anything, and Global Privacy Control can deny a category the visitor allowed. Load scripts and render optional features from permissions; use the recorded choice only to show or report what the visitor decided.

src/lib/consent-status.svelte
<script lang="ts">
	import { getConsentManager } from '@c15t/svelte';

	// Call getters at the top level of the component, inside the provider.
	const consent = getConsentManager();

	// A permission: may measurement code run now?
	const measurementAllowed = $derived(consent.has('measurement'));
	// A recorded choice: what did the visitor decide? `null` until they choose.
	const measurementChoice = $derived(
		consent.explicitChoice?.categories.measurement?.value
	);
</script>

<dl>
	<dt>Measurement may run</dt>
	<dd data-testid="measurement-permission">
		{measurementAllowed ? 'Yes' : 'No'}
	</dd>
	<dt>Visitor's measurement choice</dt>
	<dd data-testid="measurement-choice">
		{measurementChoice === undefined
			? 'Not chosen'
			: measurementChoice
				? 'Allowed'
				: 'Denied'}
	</dd>
</dl>

Before the visitor chooses, this component shows "No" and "Not chosen". How consent works explains the difference.

Property or methodAnswers
has(condition)Whether a category, or a condition such as { and: ['measurement', 'marketing'] } or { or: [...] }, is allowed now.
effectivePermissionsEvery category's permission, such as effectivePermissions.measurement.
explicitChoiceThe visitor's recorded choice, or null before they choose. explicitChoice.categories.measurement.value is one category's answer.
noticeDismissalWhether and when a notice was dismissed. Dismissing a notice is not consent.
privacySignalsSignals from the browser, such as privacySignals.gpc.detected.
restrictionsPer category, the reasons a recorded grant is overridden, such as GPC.
vendorChoice, selectedVendorsRecorded and draft grants for individual vendors outside IAB.

Read the policy and the UI state

PropertyValue
hasPolicytrue once a policy resolved for the visitor. Optional categories stay denied until then.
policyPendingtrue while the policy request is in flight.
policyRuleThe resolved rule: model, prompt, scope, rights and more.
resolutionThe resolution outcome, with status of matched, failed or another state.
model'opt-in', 'opt-out', 'iab' or 'none'.
promptRequirementWhether the visitor owes a choice, a notice acknowledgement or nothing.
hasConsentUiWhether the policy calls for any consent surface.
hasConsentPreferencesWhether a way back to preferences should show. ConsentDialogLink renders on this.
activeUI'banner', 'dialog' or 'none'.
locationThe country and region the policy resolved for.
overridesThe forced country, region, language or GPC value, if any.
consentCategories, consentTypesThe categories the preference UI offers, as names and as full definitions.
translationConfig, translationsThe active copy and language.
legalLinks, presentationThe provider's legalLinks and presentation props.
user, subjectThe identified user and the backend's subject for this visitor.

Record a choice

Call the methods from event handlers. Save methods return a promise.

src/lib/privacy-controls.svelte
<script lang="ts">
	import { ConsentDialog, getConsentManager } from '@c15t/svelte';

	const consent = getConsentManager();

	// Record one category from your own button. setConsent changes the draft;
	// saveConsents('custom') records it.
	const allowAnalytics = async () => {
		consent.setConsent('measurement', true);
		await consent.saveConsents('custom');
	};
</script>

<button type="button" onclick={() => consent.setActiveUI('dialog')}>
	Privacy settings
</button>
<button type="button" onclick={allowAnalytics}> Allow analytics </button>
<ConsentDialog />

Clicking Allow analytics records measurement as allowed and leaves the other categories as they were. Privacy settings opens the dialog.

MethodEffect
setActiveUI(surface)'dialog' opens preferences, 'banner' shows the banner, 'none' closes the open surface. Closing preferences brings the banner back while the policy still owes a choice. Records nothing.
setConsent(category, value)Changes one category in the unsaved draft. setSelectedConsent is the same method.
setSelectedVendor(vendorId, granted)Changes one vendor in the draft. Ignored for undeclared or disabled vendors.
saveConsents('custom')Records the draft. Throws when the policy changed since the draft started; see the draft.
saveConsents('all')Records every category in scope as allowed.
saveConsents('necessary')Records only necessary, rejecting the rest.
dismissNotice()Records that the visitor dismissed a notice. Only works while a notice is due.
setLanguage(code)Switches the language and resolves the policy again for it.
getDisplayedConsents()The categories to render in your own preference UI.
getDisplayedVendors(category)The vendors to list under one category. Empty under an IAB policy.
isVendorAllowed(vendorId)true while a declared vendor's category is allowed and the visitor has not switched it off. false for an id nothing declares.
subscribeToConsentChanges(listener)Calls listener with the permissions after every snapshot change. Returns an unsubscribe function.

A save updates permissions in the same task, so registered scripts and ConsentGate react before the backend request finishes. A failed request is kept and retried; it does not undo the choice, and the provider's onError callback reports it. When a save withdraws a category that was granted, the provider reloads the page by default so code that already ran is gone. Set reloadOnConsentRevoked={false} on the provider to handle that yourself.

Only call save methods from a visitor's action. Saving on page load records a choice the visitor never made.

Work with the draft

consent.draft is the unsaved state every preference surface on the page shares: the dialog, ConsentWidget and your own UI.

MemberBehavior
valuesEach category's draft value. Starts from the recorded choice, or from the policy's defaults before a choice. A choice saved elsewhere moves the values the visitor has not touched. Categories outside consentCategories read false. selectedConsents is the same object.
vendorsEach declared vendor's draft grant.
isStaletrue when the policy, the displayed categories or the vendor list changed while the draft held an unsaved change. A draft with no unsaved change follows the policy and is never stale.
set(category, value), setVendor(id, granted)The same as setConsent and setSelectedVendor.
reset()Discards unsaved changes.

The draft's code loads with the first preference surface, so a page that only shows the banner does not ship it. ConsentWidget and ConsentDialog bring it with them, and their switches render seeded on the first render, on the server too. Headless code that reads or writes the draft without either one starts the load itself: until it lands, values and vendors are empty objects and isStale is false, and they update reactively once it does. Writes made before then apply in order, draft.reset() drops them, and saveConsents('custom') waits for the draft before it records.

When isStale is true, saveConsents('custom') throws "The policy changed. Review your preferences before saving." Show the visitor the current categories, call draft.reset(), and let them save again.

getConsentManager().iab is the live IAB state, or null when IAB is off. getIAB() returns the same object at the moment you call it; read .iab on the manager for values that update.

MemberValue or effect
config.enabled, config.cmpIdWhether the add-on is on, and the CMP ID.
gvl, isLoadingGVLThe Global Vendor List, and whether it is still loading.
purposeConsents, vendorConsents, specialFeatureOptInsThe visitor's TCF choices, staged or saved.
tcStringThe saved TC String, or null.
nonIABVendorsCustom vendors outside the IAB list.
acceptAll(), rejectAll()Accept All sets vendor and purpose consent for declared consent purposes, separate vendor and purpose legitimate-interest signals for declared legitimate-interest purposes, and opt-ins for declared special features. Reject All clears these signals.
setPurposeConsent(id, value), setPurposeLegitimateInterest(id, value)Change one purpose.
setVendorConsent(id, value), setVendorLegitimateInterest(id, value)Change one vendor.
setSpecialFeatureOptIn(id, value)Change one special feature.
save()Writes the TC String and records the choice.
preferenceCenterTab, setPreferenceCenterTab(tab)The tab IABConsentDialog shows.

Until the TCF add-on finishes loading, the action methods queue and replay. See IAB TCF for setup.

getConsentKernel() returns the consent kernel behind the provider. Its events.on(type, listener) subscribes to one event and returns an unsubscribe function. Subscribe in $effect so Svelte removes the listener when the component unmounts:

src/lib/consent-events.svelte
<script lang="ts">
	import { getConsentKernel } from '@c15t/svelte';

	// Read the kernel at the top level; subscribe in an effect so the
	// listener is removed when the component unmounts.
	const kernel = getConsentKernel();
	let lastRecorded = $state<string | null>(null);

	$effect(() =>
		kernel.events.on('choice:recorded', ({ snapshot }) => {
			const { measurement } = snapshot.effectivePermissions;
			lastRecorded = measurement ? 'measurement allowed' : 'measurement denied';
		})
	);
</script>

<p data-testid="last-recorded">
	{lastRecorded ?? 'No choice recorded on this page yet'}
</p>

choice:recorded fires for an explicit accept, reject or save. permissions:changed fires for any change to what may run. For callbacks that cover the whole app, use the provider's callbacks prop instead; see callbacks.

The kernel also has subscribe(listener), which runs on every snapshot change, getSnapshot(), and commands for init(), save(), dismissNotice() and identify(). Prefer the manager's methods; they keep the draft and the open surface in step, which the raw commands do not. getSnapshot() from @c15t/svelte returns the snapshot at the moment you call it and does not update.

Other exports

ExportPurpose
manifest()The mode that resolves the policy in the browser from the manifest consentManifest() downloaded.
hosted({ backendURL })The mode that asks an Inth or self-hosted backend's /init for every visitor.
offline({ policyRules })The mode that resolves policies in the browser with no backend. Not recommended for production environments.
custom(transport)The mode for your own transport.
createConsentRuntime(options)Creates a runtime to share between providers, through their runtime prop.
resolveConsentPresentation(input)Resolves the banner or dialog shape and actions a policy asks for, as getHeadlessConsent() does.
defaultTranslationConfig, mergeTranslationConfigs, prepareTranslationConfig, detectBrowserLanguageTranslation helpers.