Run your own code when visitors answer the TermsFeed Free Cookie Consent banner
Cookie Consent calls 7 callbacks and dispatches 12 DOM events, which let your code send consent signals, keep records, and react to every answer. Cookie Consent does not send Google Consent Mode, Microsoft, Meta, TikTok, or HubSpot signals of its own, so every grant and update comes from a callback you write. The denied defaults of Google, Meta, and TikTok sit in your page code.
How to attach a Cookie Consent callback
Pass the callbacks option to run(), with each callback name mapped to a function or to the name of a function on window. A function value runs as a method of the callbacks object. A name runs as a plain function, and it must name a top-level function declaration or a var in a classic script. Functions declared with let, const, class, or inside a module are not properties of window.
This run() call attaches i_agree_button_clicked by name and user_consent_saved as a function.
<script type="text/javascript">
function cookieConsentAgreed() {
console.log('The visitor clicked I agree');
}
document.addEventListener('DOMContentLoaded', function () {
cookieconsent.run({
"notice_banner_type": "headline",
"consent_type": "express",
"palette": "dark",
"language": "en",
"callbacks": {
"i_agree_button_clicked": "cookieConsentAgreed",
"user_consent_saved": function (details, reason) {
console.log('Consent saved', reason, details);
}
}
});
});
</script>
No callback runs inside run(), so cookieconsent.cookieConsentObject and cookieconsent.openPreferencesCenter() exist inside every callback. A value that is neither a function nor the name of a window function does nothing.
The 7 Cookie Consent callbacks
Each callback matches one DOM event. Only scripts_specific_loaded and user_consent_saved receive arguments you use, and the others receive null and undefined. Look up when each callback runs before you decide where your code goes.
| Callback | Arguments | Runs |
|---|---|---|
notice_banner_loaded |
None | Once the consent banner is inserted: on each page load of a visitor who has not answered, and on every page load in demo mode |
i_agree_button_clicked |
None | After a click on the "I agree" button, or the "OK" button in implied mode, once the cookies hold the answer and user_consent_saved ran |
i_decline_button_clicked |
None | After a click on the "I decline" button, once the cookies hold the answer and user_consent_saved ran |
change_my_preferences_button_clicked |
None | After the "Change my preferences" button of the consent banner opens the Preferences Center. Not for a link matched by open_preferences_center_selector, and not for openPreferencesCenter() |
scripts_specific_loaded |
level, a category ID |
Once Cookie Consent finished releasing a category, that is, once its external scripts without async loaded, failed, or timed out. Only for a category that had tagged scripts, or for every released category with callbacks_force. At most once per category per page load |
scripts_all_loaded |
None | Once every category in use is accepted and released, after the scripts_specific_loaded calls of the same answer or page load. At most once per page load. It needs no tagged script and no callbacks_force |
user_consent_saved |
details, reason |
With reason 'choice' on each answer. With 'page_load' on each page load of a visitor who has not answered, and, only with user_consent_saved_on_page_load, on each page load of a visitor who answered |
scripts_all_loaded follows a click on the "I agree" button or a save with every category in use accepted, and each page load after such an answer. It runs on page load for a visitor who has not answered only when every category in use is released on page load, as in implied mode without page_load_consent_levels.
The details and reason arguments of user_consent_saved
details is a copy of the 4 categories at the time of the call, and reason tells a new answer from a page load. A category not in use always reads false, and strictly-necessary always reads true. Each call passes values like these.
// details
{"strictly-necessary": true, "functionality": false, "tracking": true, "targeting": false}
// reason
'choice' // the visitor clicked "I agree", "OK", "I decline", or "Save my preferences"
'page_load' // a page load: the page-load state before the answer, or the saved answer
Write records only on 'choice', because 'page_load' calls repeat a state that is already saved or not yet chosen. A page load before the answer reports the categories released on page load, which is strictly-necessary alone in express mode without page_load_consent_levels.
Re-apply the saved choice on every page with user_consent_saved_on_page_load
Turn on user_consent_saved_on_page_load: true when your user_consent_saved code needs the saved choice on every page load, not only on the page where the visitor answers. Without the option, user_consent_saved runs on answers and on page loads of visitors who have not answered, but never on later page loads of visitors who answered.
With the option, the callback runs once on every page load of a visitor who answered, with reason 'page_load' and the saved choice in details. The callback runs once more on the page where the visitor answers. Use the option for code that copies the saved state into your own app on each page, and keep records behind a reason === 'choice' check.
Send Google Consent Mode updates from user_consent_saved with this option on. The callback runs before Cookie Consent releases any tagged script, on every answer and on every page load. A tagged Google tag starts with the update in place. An untagged Google tag usually receives the update within its wait_for_update time, and a longer wait_for_update covers slow connections. Copy the callback from Set Up Google Consent Mode v2.
When scripts_specific_loaded needs callbacks_force
Set callbacks_force: true on any page that grants consent signals from scripts_specific_loaded, because without the option the callback runs only for categories that had tagged scripts on the page. A page that sends Microsoft Clarity, Meta, TikTok, or HubSpot signals usually loads the vendor tag untagged, so its category often does not hold any tagged script at all.
With callbacks_force on, scripts_specific_loaded runs once for each released category. On every page load of a visitor who answered, it runs once per accepted category, so your switch grants the saved choice again on each page. The categories that page_load_consent_levels or implied mode releases get their call on page load. A click on the "I agree" or "Save my preferences" button calls scripts_specific_loaded for each category the answer releases.
The callback never runs for a category the visitor refused, so your switch (level) only grants. For Meta and TikTok, the denied default covers a refused category, and a withdrawn category from the next page load. On the page of the withdrawal, a vendor granted earlier stays granted.
The callback runs for strictly-necessary on every page load too, which a switch without that case ignores. With debug on, Cookie Consent logs forceCallbacksDispatching is enabled each time it finishes releasing a category. Look up where each vendor takes its default, and which vendors take none, in Where Each Vendor Takes Its Default Before the Callback Runs.
The 12 Cookie Consent DOM events
Cookie Consent dispatches 12 events on document, each a plain Event that bubbles to window. The events are not CustomEvent objects, so data sits in extra properties of the event, not in detail. Each dispatch uses a new event object, and Cookie Consent ignores preventDefault(). Cookie Consent acts on most events itself, while cc_userChangedConsent, cc_userLanguageChanged, and cc_freshUser only print a debug line. Look up what each event carries and what Cookie Consent does after it.
| Event | Extra properties | Dispatched | Cookie Consent then |
|---|---|---|---|
cc_noticeBannerShown |
None | Once the consent banner is inserted | Calls notice_banner_loaded |
cc_noticeBannerOkOrAgreePressed |
None | On a click on the "I agree" or "OK" button | Accepts every category in use, writes the cookies, releases scripts or reloads, hides the consent banner, calls i_agree_button_clicked |
cc_noticeBannerRejectPressed |
None | On a click on the "I decline" button | Rejects every optional category, writes the cookies, hides the consent banner, calls i_decline_button_clicked, reloads if set |
cc_noticeBannerChangePreferencesPressed |
None | On a click on the "Change my preferences" button | Opens the Preferences Center, calls change_my_preferences_button_clicked |
cc_preferencesCenterSavePressed |
None | On a click on the "Save my preferences" button | Saves the switches, releases the accepted categories or reloads, hides both dialogs |
cc_preferencesCenterClosePressed |
None | On a click on the close button, or Escape | Discards unsaved switch changes and hides the Preferences Center |
cc_userChangedConsent |
level, accepted |
When a switch of the Preferences Center is toggled, after the choice in memory changes | Nothing beyond a debug line |
cc_userLanguageChanged |
None | After the language menu changed both dialogs | Nothing beyond a debug line |
cc_freshUser |
None | During run(), when no valid cookie_consent_level cookie exists |
Nothing beyond a debug line |
cc_userConsentSaved |
details, reason |
At each moment listed for user_consent_saved |
Calls user_consent_saved |
cc_scriptsSpecificLoaded |
level |
At each moment listed for scripts_specific_loaded |
Calls scripts_specific_loaded |
cc_scriptsAllLoaded |
None | At each moment listed for scripts_all_loaded |
Calls scripts_all_loaded |
Listen for the saved answer on document with a listener like this one.
document.addEventListener('cc_userConsentSaved', function (event) {
console.log(event.reason, event.details);
});
Inside a cc_userChangedConsent listener, acceptedLevels already holds the new value. The event carries the category ID in level and the new switch state in accepted. The change stays unsaved until a click on the "Save my preferences" button, and closing the Preferences Center without saving discards it.
cc_freshUser fires only while no valid cookie_consent_level cookie exists, usually on the first page load, because Cookie Consent writes that cookie on the same page. In demo mode the event fires on every page load, unless the browser holds a valid cookie_consent_level cookie from a setup without demo mode. Check cookieconsent.cookieConsentObject.userConsent.userAccepted to learn whether the visitor answered.
The order of Cookie Consent callbacks and events
Cookie Consent runs the callbacks of each moment in a fixed order, and that order decides when your code sees the answer.
On a page load before the answer, run() inserts the consent banner, and no tagged script runs inside run(). After run() returns, Cookie Consent calls notice_banner_loaded, then writes the cookie_consent_level cookie and calls user_consent_saved with 'page_load'. It then releases the categories that run on page load, so their inline scripts run. It calls scripts_specific_loaded for each released category that had tagged scripts, or for every released category with callbacks_force, then scripts_all_loaded when its condition holds.
On a page load of a visitor who answered, Cookie Consent releases the accepted categories inside run(). After run() returns, Cookie Consent calls scripts_specific_loaded for each accepted category that had tagged scripts, or for every accepted category with callbacks_force, then scripts_all_loaded once every category in use is accepted. With user_consent_saved_on_page_load, no tagged script runs inside run(). Cookie Consent first calls user_consent_saved with 'page_load' after run() returns, then releases the accepted categories and calls the same callbacks.
In demo mode, a browser that holds an answer saved before demo mode gets notice_banner_loaded right after user_consent_saved with user_consent_saved_on_page_load on, and after the scripts_specific_loaded and scripts_all_loaded calls with it off. A browser without saved answer cookies gets the order of a page load before the answer.
The answer buttons and the "Change my preferences" button call their callbacks in this order.
- The "I agree" or "OK" button. Cookie Consent writes the cookies and calls
user_consent_savedwith'choice'. Cookie Consent then runs the released inline scripts, hides the consent banner, and callsi_agree_button_clicked, thenscripts_specific_loadedper category, thenscripts_all_loaded. - The "I decline" button. Cookie Consent writes the cookies and calls
user_consent_savedwith'choice'. Cookie Consent then hides the consent banner and callsi_decline_button_clicked. - The "Change my preferences" button. Cookie Consent opens the Preferences Center, then calls
change_my_preferences_button_clicked. - The "Save my preferences" button. Cookie Consent writes the cookies and calls
user_consent_savedwith'choice'first. Cookie Consent then releases the accepted categories, hides both dialogs, and callsscripts_specific_loaded, thenscripts_all_loadedwhen every category in use is accepted.
With page_refresh_confirmation_buttons on, Cookie Consent reloads the page instead of releasing the categories. No tagged script runs on the page being left, and no scripts_specific_loaded or scripts_all_loaded call runs on the answer. The reloaded page releases the accepted categories once.
user_consent_saved and i_decline_button_clicked run before the reload starts. i_agree_button_clicked runs right after Cookie Consent starts the reload, and in a Chrome test it still ran before the page unloaded. The page does not wait for responses, so send a request that must arrive, such as a consent record, with navigator.sendBeacon() or fetch() with keepalive: true.
How Cookie Consent handles an error thrown inside a callback
An error thrown by your callback never stops the consent flow, and it still reaches the console. Cookie Consent catches the error, logs Error in Customer Callback <name>: <error> with debug on, and throws it again from a timer. The error then appears in the Console tab as an uncaught error and reaches window.onerror, whether debug is on or off.
Answer the consent banner from your own code
Dispatch one of the answer events on document with bubbles: true, and Cookie Consent handles it as a click on the matching button. Cookie Consent listens on window for most events, and new Event() does not bubble by default, so a non-bubbling event never reaches that listener.
This markup records a refusal from a link of your own. It works in implied mode too, where no "I decline" button exists.
<a href="#" id="my-reject-link">Reject optional cookies</a>
<script type="text/javascript">
document.getElementById('my-reject-link').addEventListener('click', function (event) {
event.preventDefault();
document.dispatchEvent(new Event('cc_noticeBannerRejectPressed', { bubbles: true }));
});
</script>
Dispatch cc_noticeBannerOkOrAgreePressed to accept every category in use, or cc_preferencesCenterSavePressed to save the current switches. Dispatch only after Cookie Consent started, because no listener exists before run() builds it.
Common patterns for Cookie Consent callbacks
Most integrations use a callback to send consent signals, log answers, or delete the first-party cookies vendor scripts set. Each pattern goes in the callbacks option of the run() call that the TermsFeed generator writes.
Send Google Consent Mode signals from user_consent_saved
This user_consent_saved callback, with user_consent_saved_on_page_load: true, sends the Google Consent Mode v2 state of each category. It needs the denied Consent Mode default as the first script in <head>, after only the charset meta. The default sits above the Google tag, the Google Tag Manager container, and the Cookie Consent block. Set that up with Google Consent Mode v2.
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
cookieconsent.run({
"notice_banner_type": "headline",
"consent_type": "express",
"palette": "dark",
"language": "en",
"page_load_consent_levels": ["strictly-necessary"],
"user_consent_saved_on_page_load": true,
"callbacks": {
"user_consent_saved": function (details) {
gtag('consent', 'update', {
'analytics_storage': details.tracking ? 'granted' : 'denied',
'ad_storage': details.targeting ? 'granted' : 'denied',
'ad_user_data': details.targeting ? 'granted' : 'denied',
'ad_personalization': details.targeting ? 'granted' : 'denied',
'functionality_storage': details.functionality ? 'granted' : 'denied',
'personalization_storage': details.functionality ? 'granted' : 'denied'
});
}
}
});
});
</script>
A refusal sends denied for every optional category, and a withdrawal sends denied for the category the visitor turned off. Both reach the Google tags on the page where the visitor answers and on every later page.
The vendor recipes for Microsoft Clarity, the Meta Pixel, the TikTok Pixel, and HubSpot grant their signals from scripts_specific_loaded with callbacks_force: true, which works beside this callback. Copy the scripts_specific_loaded callback for those vendors into the same callbacks object from Map Categories to Vendor Consent Signals.
Log each answer to your own server
Send one record per answer from user_consent_saved, and skip the call when reason is 'page_load', so page loads do not add duplicates. The record carries the consent token, the choice in details, and the page, and your server adds the time it arrives. Copy the callback, which sends the record as JSON with fetch and keepalive, from How to Record Each Answer From user_consent_saved.
Delete first-party vendor cookies after a refusal
Cookie Consent does not delete cookies that other scripts set, so remove the first-party cookies of your site from a callback yourself. Run the cleanup from the strictly-necessary call of scripts_specific_loaded with callbacks_force: true, which runs on every page load. The callbacks object holds one scripts_specific_loaded function, so merge the cleanup into the switch (level) of a page that grants vendor signals there. Copy the callback that removes the Google Analytics 4 cookies from Rejecting Does Not Delete Cookies Already Set.
Publish a page that carries the logging callback, the tagged Google Analytics 4 script, and the cleanup callback, and clear any page cache. Then confirm the logging and cleanup patterns in Chrome DevTools.
- Close every private window, open a new one, open DevTools on the Network tab filtered for the path your consent record goes to, such as
/consent-log, and load your site. - Click the "I agree" button, select the request, and check that the Payload tab shows
choicewith"tracking": true. - Check in the Application tab that the
_gacookies exist. - Open the Preferences Center, set the switch on the "Tracking cookies" tab to Inactive, and save.
- Load the next page, and check in the Application tab, under Cookies, that the
_gacookies are gone.
Common mistakes with Cookie Consent callbacks
Each of these mistakes makes a callback never run, run at other moments than you expect, or read the wrong data, so search your callback code for them.
- The callback name points to a
letorconstfunction. Declare it withfunctionat the top level of a classic script, or pass the function itself. - A page that grants signals from
scripts_specific_loadedhas nocallbacks_force: true. The callback then never runs for a category with no tagged script, so the vendor never receives its grant and stays at its default. Add the option. - The code reads
event.detail. The events are plainEventobjects, so readevent.details,event.reason, orevent.level. - The Consent Mode update in
user_consent_savedstops after the page where the visitor answered. Adduser_consent_saved_on_page_load: true. - The code expects
change_my_preferences_button_clickedafter a footer link. That callback runs only for the consent banner button. Run that code from your own click listener on the footer link, which still receives the click. - A second
run()call carries the callbacks. Cookie Consent ignores every option of a second call, callbacks included. Put all callbacks in the first call. - The code counts every
user_consent_savedcall as an answer. Filter onreason === 'choice'before you count or log.
Other callback failures come from a callback that throws, a misspelled name, or a reload that cancels the requests your callbacks send. Work through those causes with Callbacks Not Firing or Firing Twice.