npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

freegdpr

v1.0.0

Published

Standalone, zero-dependency GDPR cookie consent banner: script-blocking, Google Consent Mode v2, accessible Shadow DOM UI. Usable via CDN or npm.

Downloads

145

Readme

FreeGDPR.js

Standalone, zero-dependency GDPR cookie-consent banner — script-blocking, Google Consent Mode v2, accessible Shadow-DOM UI. Drop in via CDN or npm.

npm license size

FreeGDPR.js makes any website GDPR/ePrivacy compliant for cookie consent. It shows a consent banner, a granular preferences panel, persists choices, blocks third-party tags until consent is given, and wires up Google Consent Mode v2 automatically — all from a single <script> tag.


Why FreeGDPR.js

Most "cookie banners" only record a choice but still let trackers fire before consent — which is exactly what regulators fine. FreeGDPR.js was designed around the parts that actually matter for compliance:

  • Prior blocking — tagged <script>/<iframe> do not execute until the matching category is granted.
  • Equal prominence — "Reject all" is as visible as "Accept all" on the first layer (EDPB guidance, no dark patterns).
  • No pre-ticked boxes — every non-essential category defaults to off.
  • Easy withdrawal — a floating button + GDPR.openPreferences() reopen the panel any time.
  • Consent expiry — re-asks after a configurable period (default 6 months).
  • Google Consent Mode v2 — pushes default: denied before tags load and update on every change.
  • Accessible — Shadow-DOM isolation, ARIA dialog, focus trap, keyboard + Esc, WCAG-AA contrast.

Installation

CDN (recommended)

<!-- 1) Configure FIRST -->
<script>
  window.GDPRConfig = {
    companyName: 'Acme Inc.',
    privacyPolicyUrl: '/privacy',
    cookiePolicyUrl: '/cookie-policy',
    consentMode: { enabled: true },
  };
</script>

<!-- 2) Load the library (auto-initialises from GDPRConfig) -->
<script src="https://cdn.jsdelivr.net/npm/freegdpr@1/dist/freegdpr.iife.min.js"></script>

Pin a version for production ([email protected]) and add SRI for integrity.

npm

npm install freegdpr
import { init } from 'freegdpr';

const gdpr = init({
  companyName: 'Acme Inc.',
  consentMode: { enabled: true },
});

Blocking third-party tags

Mark any tag you must not run before consent with type="text/plain" and data-gdpr="<category>":

<!-- Google Analytics — runs only after "analytics" consent -->
<script type="text/plain" data-gdpr="analytics"
        src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>

<!-- Inline Meta Pixel — runs only after "marketing" consent -->
<script type="text/plain" data-gdpr="marketing">
  !function(f,b,e,v,n,t,s){ /* fbq bootstrap */ }();
  fbq('init', 'PIXEL_ID'); fbq('track', 'PageView');
</script>

<!-- A YouTube embed — loads only after "marketing" consent -->
<iframe data-gdpr="marketing"
        data-gdpr-src="https://www.youtube.com/embed/VIDEO_ID"></iframe>

When the category is granted, FreeGDPR.js swaps the inert placeholder for a live, executing element. Newly granted categories activate incrementally without a reload.


Google Consent Mode v2

Enable it and FreeGDPR.js handles the rest:

window.GDPRConfig = {
  consentMode: {
    enabled: true,
    urlPassthrough: true,     // pass ad-click info without cookies
    adsDataRedaction: true,   // redact ads data while denied (default)
    waitForUpdate: 500,       // ms Google holds tags before consent
  },
};

Category → signal mapping is configurable per category via consentModeSignals. Defaults:

| Category | Google signals | | ------------- | ---------------------------------------------------------- | | necessary | security_storage | | analytics | analytics_storage | | marketing | ad_storage, ad_user_data, ad_personalization | | preferences | functionality_storage, personalization_storage |


Configuration — window.GDPRConfig

All properties are optional.

| Property | Type | Default | Description | | ----------------------- | --------- | ------------------ | ----------- | | companyName | string | '' | Interpolated into the banner body. | | privacyPolicyUrl | string | '#' | Privacy Policy URL (new tab). | | cookiePolicyUrl | string | '#' | Cookie Policy URL (new tab). | | position | string | 'bottom' | bottom · bottom-left · bottom-right · top · center. | | overlay | boolean | false | Dim + blur the page until the user accepts/rejects. | | theme | string | 'auto' | light · dark · auto. | | colors | object | — | Brand colours: text, background, primary, primaryText. | | locale | string | auto | it · en. Auto-detected (EN default, IT for Italian browsers). | | messages | object | {} | Override any UI string by key. | | categories | object | 4 built-ins | Merged over defaults. | | storage | string | 'localStorage' | localStorage · cookie. | | storageKey | string | 'gdpr_consent_v1'| Key / cookie name. | | cookieDomain | string | — | e.g. .example.com for cross-subdomain (cookie mode). | | consentExpiryDays | number | 180 | Re-ask after N days. 0 disables expiry. | | autoBlock | boolean | true | Auto-activate tagged tags. | | consentMode | object | { enabled:false }| Google Consent Mode v2. | | showPreferencesButton | boolean | true | Floating reopen button. | | preferencesButtonPosition | string | 'bottom-left' | Floating button side: bottom-left · bottom-right. | | preferencesButtonOffset | number | 18 | Floating button distance (px) from the bottom edge. | | autoShow | boolean | true | Render banner on init. | | onAcceptAll | function | — | Fired on "Accept all". | | onRejectAll | function | — | Fired on "Reject all". | | onSave | function | — | Fired on every save. | | onInit | function | — | Fired once with the existing record (or null). |

Categories

window.GDPRConfig = {
  categories: {
    analytics: { label: 'Analytics', description: '…', required: false, default: false },
    functional: {                       // add your own
      label: 'Functional',
      description: 'Chat widget, video embeds…',
      consentModeSignals: ['functionality_storage'],
    },
  },
};

User categories are merged over the four built-ins (necessary, analytics, marketing, preferences).

Branding (name & colours)

  • Company / site name — set companyName; it is injected into the banner text. To replace the whole banner copy instead, override messages['banner.body']. Set just companyName and the default wording adapts around it.

  • Colourscolors lets you match your brand without touching CSS:

    window.GDPRConfig = {
      companyName: 'My Shop',
      colors: {
        text: '#1a1a2e',        // titles + body text
        background: '#ffffff',  // banner / modal background
        primary: '#e2007a',     // buttons, links, active toggles
        // primaryText: '#fff'  // optional — auto-derived from `primary` for contrast
      },
    };

    Accepts any CSS colour (hex, rgb(), hsl(), named). Colours apply over the chosen theme in both light and dark mode; the button text colour is auto-contrasted from primary when you don't set primaryText.


Full HTML example (all options)

Copy-paste this into your page and trim what you don't need. Every option below is optional — the library works with an empty {}.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />

    <!-- 1) CONFIGURE — must come BEFORE the library script -->
    <script>
      window.GDPRConfig = {
        // --- General ---
        companyName: 'Acme Inc.',                 // shown in the banner body
        privacyPolicyUrl: '/privacy',             // opened in a new tab
        cookiePolicyUrl: '/cookie-policy',        // opened in a new tab
        position: 'bottom-right',                 // bottom | bottom-left | bottom-right | top | center
        overlay: false,                           // true = dim + blur the page until accept/reject
        theme: 'auto',                            // light | dark | auto (prefers-color-scheme)
        colors: {                                 // brand colours (any CSS colour value)
          text: '#1a1a2e',                        // titles + body text
          background: '#ffffff',                  // banner / modal background
          primary: '#1b56d3',                     // buttons, links, active toggles
          // primaryText: '#ffffff',              // optional; auto-derived from `primary`
        },
        locale: 'en',                             // it | en — omit to auto-detect from the browser

        // --- Persistence ---
        storage: 'localStorage',                  // localStorage | cookie
        storageKey: 'gdpr_consent_v1',            // localStorage key / cookie name
        cookieDomain: '.example.com',             // only for storage:'cookie' (cross-subdomain)
        consentExpiryDays: 180,                   // re-ask after N days; 0 = never expire

        // --- Behaviour ---
        autoBlock: true,                          // activate tagged <script>/<iframe> on consent
        autoShow: true,                           // show the banner automatically on first visit
        showPreferencesButton: true,              // floating button to reopen preferences
        preferencesButtonPosition: 'bottom-left', // bottom-left | bottom-right
        preferencesButtonOffset: 18,              // px from the bottom edge (e.g. clear other widgets)

        // --- Google Consent Mode v2 ---
        consentMode: {
          enabled: true,
          urlPassthrough: true,                   // pass ad-click info without cookies
          adsDataRedaction: true,                 // redact ads data while denied
          waitForUpdate: 500,                     // ms Google holds tags before consent
          region: [],                             // e.g. ['IT','FR'] to scope the defaults
          defaults: {},                           // force specific signals, e.g. { security_storage: 'granted' }
        },

        // --- Override any UI string (optional) ---
        messages: {
          'banner.acceptAll': 'Got it',
          'banner.rejectAll': 'No thanks',
        },

        // --- Categories (merged over the 4 built-ins) ---
        categories: {
          analytics: {
            label: 'Analytics',
            description: 'Anonymous usage statistics.',
            required: false,
            default: false,
            consentModeSignals: ['analytics_storage'],
          },
          functional: {                           // add your own category
            label: 'Functional',
            description: 'Chat widget, video embeds, saved preferences.',
            required: false,
            default: false,
            consentModeSignals: ['functionality_storage', 'personalization_storage'],
          },
        },

        // --- Callbacks (optional) ---
        onInit: function (consent) {
          // consent is the stored record, or null on first visit
        },
        onAcceptAll: function (consent) {},
        onRejectAll: function (consent) {},
        onSave: function (consent) {
          // fired on EVERY save (accept-all, reject-all and custom)
          if (consent.analytics) {
            /* e.g. boot your own analytics here */
          }
        },
      };
    </script>

    <!-- 2) BLOCKED TAGS — run only after the matching category is granted -->
    <script type="text/plain" data-gdpr="analytics"
            src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>

    <!-- 3) LOAD the library (auto-initialises from window.GDPRConfig) -->
    <script src="https://cdn.jsdelivr.net/npm/freegdpr@1/dist/freegdpr.iife.min.js"></script>
  </head>

  <body>
    <!-- Reopen the panel from anywhere -->
    <footer>
      <a href="#" onclick="GDPR.openPreferences(); return false;">Manage cookie preferences</a>
    </footer>
  </body>
</html>

Minimal version — if you just want the defaults, the whole config is optional:

<script>window.GDPRConfig = { companyName: 'Acme Inc.' };</script>
<script src="https://cdn.jsdelivr.net/npm/freegdpr@1/dist/freegdpr.iife.min.js"></script>

Public API — window.GDPR

| Method | Description | | ------ | ----------- | | getConsent() | Current record, or null if none. | | hasConsent(category) | true/false for a category. | | acceptAll() / rejectAll() | Apply and persist a blanket choice. | | openPreferences() / closePreferences() | Toggle the preferences modal. | | showBanner() | Force-show the banner. | | reset(reload = true) | Clear stored consent (optionally reload). | | on('consent', fn) | Subscribe to consent changes; returns an unsubscribe fn. | | destroy() | Remove all DOM. | | version | Library version string. |

DOM event

window.addEventListener('gdpr:consent', (e) => {
  if (e.detail.analytics) initAnalytics();
});

e.detail matches getConsent(): per-category booleans plus version, timestamp, consentId.


Accessibility

WCAG 2.1 AA: role="dialog" + aria-modal + aria-labelledby, focus trap, Esc to close with focus restore, labelled toggles, AA contrast in both themes, and prefers-reduced-motion support. The whole UI lives in a Shadow DOM so host-page styles can never break or be broken by it.


Development

npm install
npm run build        # ESM + CJS + IIFE (+ .min) + .d.ts
npm test             # vitest (jsdom)
npm run test:e2e     # playwright (run `npx playwright install` first)
npm run typecheck

Open examples/demo.html after a build to try it live.


License

MIT © Daniele De Leon