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.

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.

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.

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.

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.

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.

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_saved with 'choice'. Cookie Consent then runs the released inline scripts, hides the consent banner, and calls i_agree_button_clicked, then scripts_specific_loaded per category, then scripts_all_loaded.
  • The "I decline" button. Cookie Consent writes the cookies and calls user_consent_saved with 'choice'. Cookie Consent then hides the consent banner and calls i_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_saved with 'choice' first. Cookie Consent then releases the accepted categories, hides both dialogs, and calls scripts_specific_loaded, then scripts_all_loaded when 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.

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.

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.

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.

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.

  1. 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.
  2. Click the "I agree" button, select the request, and check that the Payload tab shows choice with "tracking": true.
  3. Check in the Application tab that the _ga cookies exist.
  4. Open the Preferences Center, set the switch on the "Tracking cookies" tab to Inactive, and save.
  5. Load the next page, and check in the Application tab, under Cookies, that the _ga cookies are gone.

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 let or const function. Declare it with function at the top level of a classic script, or pass the function itself.
  • A page that grants signals from scripts_specific_loaded has no callbacks_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 plain Event objects, so read event.details, event.reason, or event.level.
  • The Consent Mode update in user_consent_saved stops after the page where the visitor answered. Add user_consent_saved_on_page_load: true.
  • The code expects change_my_preferences_button_clicked after 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_saved call as an answer. Filter on reason === '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.