{"id":10229,"date":"2026-08-15T00:05:24","date_gmt":"2026-08-14T22:05:24","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/toolbox\/tcf-consent-string-decoder\/"},"modified":"2026-08-15T00:05:24","modified_gmt":"2026-08-14T22:05:24","slug":"tcf-consent-string-decoder","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/tcf-consent-string-decoder\/","title":{"rendered":"TCF v2.2 Consent String Decoder"},"content":{"rendered":"<div class=\"gtm-analyser-container\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 20px;\">A TC string from the euconsent-v2 cookie or from the __tcfapi response is decoded here bit by bit, inside this browser. Nothing is sent anywhere.<\/p>\n<p>    <label for=\"tcf-eingabe\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">TC string<\/label><br \/>\n    <textarea id=\"tcf-eingabe\" class=\"form-control\" style=\"width: 100%; height: 110px; padding: 12px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; font-family: monospace; font-size: 0.85rem; box-sizing: border-box;\" placeholder=\"CQVLVAAQ...\"><\/textarea><\/p>\n<p>    <label for=\"tcf-gvl\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin: 15px 0 5px;\">Global Vendor List as JSON (optional, for vendor names)<\/label><br \/>\n    <textarea id=\"tcf-gvl\" class=\"form-control\" style=\"width: 100%; height: 70px; padding: 12px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; font-family: monospace; font-size: 0.8rem; box-sizing: border-box;\" placeholder=\"Contents of vendor-list.json - without it only vendor IDs appear\"><\/textarea><\/p>\n<div style=\"display: flex; gap: 10px; flex-wrap: wrap; margin: 18px 0 0;\">\n        <button id=\"tcf-btn\" class=\"button\" style=\"background: var(--accent, #7ee787); color: #0b1114; border: none; padding: 12px 24px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Decode<\/button><br \/>\n        <button id=\"tcf-beispiel\" class=\"button\" type=\"button\" style=\"background: transparent; color: var(--text-primary, #e8e8ee); border: 1px solid var(--border, #2a2a35); padding: 12px 20px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Load example<\/button>\n    <\/div>\n<div id=\"tcf-ergebnis\" style=\"margin-top: 22px;\"><\/div>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"A TC string from the euconsent-v2 cookie or from the __tcfapi response is decoded here bit by bit, inside this browser. Nothing is sent anywhere.\",\"label_string\":\"TC string\",\"ph_string\":\"CQVLVAAQ...\",\"label_gvl\":\"Global Vendor List as JSON (optional, for vendor names)\",\"ph_gvl\":\"Contents of vendor-list.json - without it only vendor IDs appear\",\"knopf\":\"Decode\",\"beispiel\":\"Load example\",\"fehler_leer\":\"No TC string was entered.\",\"fehler_zeichen\":\"The string contains characters outside the web-safe base64 alphabet.\",\"fehler_kurz\":\"The string ends before the core segment is complete.\",\"fehler_version\":\"Version %s is not TCF v2 - only version 2 strings can be read here.\",\"h_ueberblick\":\"Header\",\"h_zwecke\":\"Purposes\",\"h_merkmale\":\"Special features\",\"h_anbieter\":\"Vendors\",\"h_beschraenkungen\":\"Publisher restrictions\",\"h_verleger\":\"Publisher segment\",\"h_segmente\":\"Segments\",\"h_warnungen\":\"Findings\",\"l_version\":\"Version\",\"l_erstellt\":\"Created\",\"l_geaendert\":\"Last updated\",\"l_cmp\":\"CMP ID\",\"l_cmpversion\":\"CMP version\",\"l_bildschirm\":\"Consent screen\",\"l_sprache\":\"Consent language\",\"l_gvl\":\"Vendor list version\",\"l_richtlinie\":\"TCF policy version\",\"l_dienst\":\"Service specific\",\"l_stapel\":\"Non-standard stacks\",\"l_zweckeins\":\"Purpose one treatment\",\"l_land\":\"Publisher country\",\"ja\":\"yes\",\"nein\":\"no\",\"s_zweck\":\"Purpose\",\"s_zustimmung\":\"Consent\",\"s_li\":\"Legitimate interest\",\"s_nicht_erlaubt\":\"Legitimate interest is no longer permitted for this purpose under TCF 2.2.\",\"a_zustimmung\":\"consent\",\"a_li\":\"legitimate interest\",\"a_anzahl\":\"Vendors set\",\"a_max\":\"Highest vendor ID\",\"a_kodierung\":\"Encoding\",\"a_bitfeld\":\"bit field\",\"a_bereich\":\"ranges\",\"a_keine\":\"No vendor is set in this section.\",\"a_mehr\":\"and %s more\",\"b_zweck\":\"Purpose\",\"b_keine\":\"The publisher has not restricted any purpose.\",\"b_art0\":\"not allowed\",\"b_art1\":\"consent required\",\"b_art2\":\"legitimate interest required\",\"b_art3\":\"unassigned\",\"v_zwecke\":\"Purposes with consent\",\"v_zweckeLI\":\"Purposes with legitimate interest\",\"v_eigene\":\"Custom purposes\",\"v_keine\":\"none\",\"seg_kern\":\"Core\",\"seg_offengelegt\":\"Disclosed vendors\",\"seg_erlaubt\":\"Allowed vendors (dropped in TCF 2.2)\",\"seg_verleger\":\"Publisher data\",\"seg_unbekannt\":\"Unknown segment\",\"seg_bits\":\"%s bits\",\"w_richtlinie\":\"Policy version %s is below 4, so the string was written under the rules that applied before TCF 2.2.\",\"w_li_verboten\":\"Legitimate interest is set for purposes %s, which TCF 2.2 no longer permits.\",\"w_unbekannt\":\"Bits are set for purposes %s, which do not exist in TCF 2.2.\",\"w_reihenfolge\":\"The update timestamp lies before the creation timestamp.\",\"w_gvl0\":\"The vendor list version is zero, which points to a test string or a placeholder.\",\"w_alt\":\"The last update was about %s months ago, beyond the thirteen months that are customary for a consent.\",\"w_keine_anbieter\":\"No vendor holds consent, so nothing may fire on that basis.\",\"w_zweckeins\":\"Purpose one treatment is set: purpose 1 is handled outside the string in that jurisdiction.\",\"hinweis_namen\":\"Only vendor IDs are shown. Names come from the Global Vendor List, which is deliberately not fetched here so that the page stays free of outside requests.\",\"hinweis_gvl_kaputt\":\"The vendor list could not be read as JSON, so names remain unavailable.\",\"zweck1\":\"Store and\\\/or access information on a device\",\"zweck2\":\"Use limited data to select advertising\",\"zweck3\":\"Create profiles for personalised advertising\",\"zweck4\":\"Use profiles to select personalised advertising\",\"zweck5\":\"Create profiles to personalise content\",\"zweck6\":\"Use profiles to select personalised content\",\"zweck7\":\"Measure advertising performance\",\"zweck8\":\"Measure content performance\",\"zweck9\":\"Understand audiences through statistics or combinations of data from different sources\",\"zweck10\":\"Develop and improve services\",\"zweck11\":\"Use limited data to select content\",\"merkmal1\":\"Use precise geolocation data\",\"merkmal2\":\"Actively scan device characteristics for identification\"};<\/p>\n<p>    \/\/ --- reine Entschluesselung, ohne jeden Zugriff auf das Dokument ---<\/p>\n<p>    const ZEICHEN = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';<\/p>\n<p>    \/\/ Zwecke und Sondermerkmale nach TCF 2.2. Zwecke 12 bis 24 sind im\n    \/\/ Format vorgesehen, aber nicht vergeben - gesetzte Bits dort sind ein\n    \/\/ Befund und keine Zweckangabe.\n    const ZWECKE = [\n        T.zweck1, T.zweck2, T.zweck3, T.zweck4, T.zweck5, T.zweck6,\n        T.zweck7, T.zweck8, T.zweck9, T.zweck10, T.zweck11\n    ];\n    const MERKMALE = [T.merkmal1, T.merkmal2];<\/p>\n<p>    \/\/ Berechtigtes Interesse ist seit TCF 2.2 fuer diese Zwecke nicht mehr\n    \/\/ zulaessig - ein gesetztes Bit deutet auf einen alten String oder eine\n    \/\/ fehlerhafte Erzeugung hin.\n    const LI_VERBOTEN = [1, 3, 4, 5, 6];<\/p>\n<p>    const ARTEN = [T.b_art0, T.b_art1, T.b_art2, T.b_art3];<\/p>\n<p>    function bitsAus(text) {\n        let bits = '';\n        for (let i = 0; i < text.length; i++) {\n            const wert = ZEICHEN.indexOf(text.charAt(i));\n            if (wert < 0) {\n                return null;\n            }\n            let teil = wert.toString(2);\n            while (teil.length < 6) { teil = '0' + teil; }\n            bits += teil;\n        }\n        return bits;\n    }\n\n    function leser(bits) {\n        let pos = 0;\n        return {\n            rest: function () { return bits.length - pos; },\n            stelle: function () { return pos; },\n            zahl: function (anzahl) {\n                if (pos + anzahl > bits.length) { throw new Error(T.fehler_kurz); }\n                const teil = bits.substr(pos, anzahl);\n                pos += anzahl;\n                return parseInt(teil, 2);\n            },\n            bit: function () {\n                if (pos + 1 > bits.length) { throw new Error(T.fehler_kurz); }\n                const wert = bits.charAt(pos) === '1';\n                pos += 1;\n                return wert;\n            },\n            datum: function () {\n                \/\/ 36 Bit Zehntelsekunden seit dem 1. Januar 1970.\n                if (pos + 36 > bits.length) { throw new Error(T.fehler_kurz); }\n                const teil = bits.substr(pos, 36);\n                pos += 36;\n                return parseInt(teil, 2) * 100;\n            },\n            buchstaben: function (anzahl) {\n                let text = '';\n                for (let i = 0; i < anzahl; i++) {\n                    text += String.fromCharCode(65 + this.zahl(6));\n                }\n                return text;\n            },\n            feld: function (anzahl) {\n                \/\/ Bitfeld: Bit n steht fuer die Nummer n, gezaehlt ab eins.\n                const treffer = [];\n                for (let i = 1; i <= anzahl; i++) {\n                    if (this.bit()) { treffer.push(i); }\n                }\n                return treffer;\n            }\n        };\n    }\n\n    function anbieterLesen(l) {\n        const raus = { maxId: 0, kodierung: 'bitfeld', ids: [], bereiche: [] };\n        raus.maxId = l.zahl(16);\n        const bereichKodiert = l.bit();\n        if (!bereichKodiert) {\n            raus.kodierung = 'bitfeld';\n            for (let i = 1; i <= raus.maxId; i++) {\n                if (l.bit()) { raus.ids.push(i); }\n            }\n            return raus;\n        }\n        raus.kodierung = 'bereich';\n        const anzahl = l.zahl(12);\n        for (let i = 0; i < anzahl; i++) {\n            const istBereich = l.bit();\n            const start = l.zahl(16);\n            if (istBereich) {\n                const ende = l.zahl(16);\n                raus.bereiche.push([start, ende]);\n                \/\/ Sehr grosse Bereiche nicht aufloesen, sonst waechst die\n                \/\/ Liste ins Unermessliche; die Anzeige nutzt ohnehin Bereiche.\n                if (ende - start <= 5000) {\n                    for (let v = start; v <= ende; v++) { raus.ids.push(v); }\n                }\n            } else {\n                raus.bereiche.push([start, start]);\n                raus.ids.push(start);\n            }\n        }\n        return raus;\n    }\n\n    function beschraenkungenLesen(l) {\n        const liste = [];\n        const anzahl = l.zahl(12);\n        for (let i = 0; i < anzahl; i++) {\n            const eintrag = { zweck: l.zahl(6), art: l.zahl(2), anbieter: [] };\n            const posten = l.zahl(12);\n            for (let p = 0; p < posten; p++) {\n                const istBereich = l.bit();\n                const start = l.zahl(16);\n                if (istBereich) {\n                    eintrag.anbieter.push([start, l.zahl(16)]);\n                } else {\n                    eintrag.anbieter.push([start, start]);\n                }\n            }\n            liste.push(eintrag);\n        }\n        return liste;\n    }\n\n    function kernLesen(bits) {\n        const l = leser(bits);\n        const k = {};\n        k.version = l.zahl(6);\n        if (k.version !== 2) {\n            throw new Error(T.fehler_version.replace('%s', String(k.version)));\n        }\n        k.erstellt = l.datum();\n        k.geaendert = l.datum();\n        k.cmpId = l.zahl(12);\n        k.cmpVersion = l.zahl(12);\n        k.bildschirm = l.zahl(6);\n        k.sprache = l.buchstaben(2);\n        k.gvlVersion = l.zahl(12);\n        k.richtlinie = l.zahl(6);\n        k.dienstspezifisch = l.bit();\n        k.nichtStandard = l.bit();\n        k.merkmale = l.feld(12);\n        k.zwecke = l.feld(24);\n        k.zweckeLI = l.feld(24);\n        k.zweckEins = l.bit();\n        k.land = l.buchstaben(2);\n        k.anbieter = anbieterLesen(l);\n        k.anbieterLI = anbieterLesen(l);\n        k.beschraenkungen = beschraenkungenLesen(l);\n        k.bits = bits.length;\n        return k;\n    }\n\n    function verlegerLesen(l) {\n        const v = {};\n        v.zwecke = l.feld(24);\n        v.zweckeLI = l.feld(24);\n        const anzahl = l.zahl(6);\n        v.anzahlEigene = anzahl;\n        v.eigene = anzahl > 0 ? l.feld(anzahl) : [];\n        v.eigeneLI = anzahl > 0 ? l.feld(anzahl) : [];\n        return v;\n    }<\/p>\n<p>    function warnungenSammeln(kern, segmente, jetzt) {\n        const warnungen = [];<\/p>\n<p>        if (kern.richtlinie < 4) {\n            warnungen.push(T.w_richtlinie.replace('%s', String(kern.richtlinie)));\n        }\n        const verboten = [];\n        LI_VERBOTEN.forEach(function (nummer) {\n            if (kern.zweckeLI.indexOf(nummer) !== -1) { verboten.push(nummer); }\n        });\n        if (verboten.length) {\n            warnungen.push(T.w_li_verboten.replace('%s', verboten.join(', ')));\n        }\n        const unbekannt = kern.zwecke.concat(kern.zweckeLI).filter(function (n) { return n > 11; });\n        if (unbekannt.length) {\n            warnungen.push(T.w_unbekannt.replace('%s', unbekannt.sort(function (a, b) { return a - b; }).join(', ')));\n        }\n        if (kern.geaendert < kern.erstellt) {\n            warnungen.push(T.w_reihenfolge);\n        }\n        if (kern.gvlVersion === 0) {\n            warnungen.push(T.w_gvl0);\n        }\n        \/\/ Dreizehn Monate sind die uebliche Obergrenze fuer die Gueltigkeit\n        \/\/ einer Einwilligung; darueber ist der String erneuerungsbeduerftig.\n        const dreizehnMonate = 13 * 30.5 * 24 * 60 * 60 * 1000;\n        if (jetzt &#038;&#038; kern.geaendert > 0 && (jetzt - kern.geaendert) > dreizehnMonate) {\n            warnungen.push(T.w_alt.replace('%s', String(Math.floor((jetzt - kern.geaendert) \/ (30.5 * 24 * 60 * 60 * 1000)))));\n        }\n        if (kern.anbieter.ids.length === 0 && kern.anbieter.bereiche.length === 0) {\n            warnungen.push(T.w_keine_anbieter);\n        }\n        if (kern.zweckEins) {\n            warnungen.push(T.w_zweckeins);\n        }\n        return warnungen;\n    }<\/p>\n<p>    function gvlLesen(text) {\n        if (!text || !String(text).trim()) { return null; }\n        let roh;\n        try {\n            roh = JSON.parse(text);\n        } catch (e) {\n            return { fehler: true };\n        }\n        if (!roh || typeof roh !== 'object' || !roh.vendors) { return { fehler: true }; }\n        const namen = {};\n        Object.keys(roh.vendors).forEach(function (schluessel) {\n            const eintrag = roh.vendors[schluessel];\n            if (eintrag && eintrag.name) { namen[String(eintrag.id || schluessel)] = String(eintrag.name); }\n        });\n        return { namen: namen, version: roh.vendorListVersion || 0, anzahl: Object.keys(namen).length };\n    }<\/p>\n<p>    function dekodieren(text, gvlText, jetzt) {\n        const ergebnis = { ok: false, fehler: '', kern: null, segmente: [], warnungen: [], gvl: null };<\/p>\n<p>        const roh = String(text === undefined || text === null ? '' : text).trim();\n        if (roh === '') {\n            ergebnis.fehler = T.fehler_leer;\n            return ergebnis;\n        }<\/p>\n<p>        const teile = roh.split('.');\n        const bits = bitsAus(teile[0]);\n        if (bits === null) {\n            ergebnis.fehler = T.fehler_zeichen;\n            return ergebnis;\n        }\n        if (bits.length < 213) {\n            ergebnis.fehler = T.fehler_kurz;\n            return ergebnis;\n        }\n\n        try {\n            ergebnis.kern = kernLesen(bits);\n        } catch (e) {\n            ergebnis.fehler = e &#038;&#038; e.message ? e.message : T.fehler_kurz;\n            return ergebnis;\n        }\n\n        ergebnis.segmente.push({ art: 0, name: T.seg_kern, bits: bits.length });\n\n        for (let i = 1; i < teile.length; i++) {\n            if (teile[i] === '') { continue; }\n            const sBits = bitsAus(teile[i]);\n            if (sBits === null || sBits.length < 3) {\n                ergebnis.segmente.push({ art: -1, name: T.seg_unbekannt, bits: sBits ? sBits.length : 0 });\n                continue;\n            }\n            const l = leser(sBits);\n            let art = 0;\n            try {\n                art = l.zahl(3);\n                if (art === 1 || art === 2) {\n                    ergebnis.segmente.push({\n                        art: art,\n                        name: art === 1 ? T.seg_offengelegt : T.seg_erlaubt,\n                        bits: sBits.length,\n                        anbieter: anbieterLesen(l)\n                    });\n                } else if (art === 3) {\n                    ergebnis.segmente.push({\n                        art: 3,\n                        name: T.seg_verleger,\n                        bits: sBits.length,\n                        verleger: verlegerLesen(l)\n                    });\n                } else {\n                    ergebnis.segmente.push({ art: art, name: T.seg_unbekannt, bits: sBits.length });\n                }\n            } catch (e) {\n                ergebnis.segmente.push({ art: art, name: T.seg_unbekannt, bits: sBits.length, fehler: true });\n            }\n        }\n\n        ergebnis.gvl = gvlLesen(gvlText);\n        ergebnis.warnungen = warnungenSammeln(ergebnis.kern, ergebnis.segmente, jetzt);\n        ergebnis.ok = true;\n        return ergebnis;\n    }\n\n    function bereicheKuerzen(ids) {\n        \/\/ Aus einer Nummernliste zusammenhaengende Bereiche machen, damit aus\n        \/\/ 900 Zahlen ein lesbarer Ausdruck wird.\n        const sortiert = ids.slice().sort(function (a, b) { return a - b; });\n        const raus = [];\n        let start = null;\n        let vorher = null;\n        for (let i = 0; i < sortiert.length; i++) {\n            const wert = sortiert[i];\n            if (start === null) { start = wert; vorher = wert; continue; }\n            if (wert === vorher + 1) { vorher = wert; continue; }\n            raus.push(start === vorher ? String(start) : start + '-' + vorher);\n            start = wert;\n            vorher = wert;\n        }\n        if (start !== null) {\n            raus.push(start === vorher ? String(start) : start + '-' + vorher);\n        }\n        return raus;\n    }\n\n    function datumText(ms) {\n        if (!isFinite(ms) || ms <= 0) { return '-'; }\n        const d = new Date(ms);\n        function zwei(n) { return (n < 10 ? '0' : '') + n; }\n        return d.getUTCFullYear() + '-' + zwei(d.getUTCMonth() + 1) + '-' + zwei(d.getUTCDate())\n            + ' ' + zwei(d.getUTCHours()) + ':' + zwei(d.getUTCMinutes()) + ':' + zwei(d.getUTCSeconds()) + ' UTC';\n    }\n\n    if (typeof window !== 'undefined') {\n        window.LW_TEST = window.LW_TEST || {};\n        window.LW_TEST.TCF = {\n            dekodieren: dekodieren,\n            bitsAus: bitsAus,\n            leser: leser,\n            bereicheKuerzen: bereicheKuerzen,\n            datumText: datumText\n        };\n    }\n\n    \/\/ --- Anbindung an das Dokument ---\n\n    const btn = document.getElementById('tcf-btn');\n    const out = document.getElementById('tcf-ergebnis');\n    const feld = document.getElementById('tcf-eingabe');\n    const gvlFeld = document.getElementById('tcf-gvl');\n    const beispielBtn = document.getElementById('tcf-beispiel');\n    if (!btn || !out || !feld || !gvlFeld || !beispielBtn) { return; }\n\n    const KASTEN = 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 6px; padding: 14px; margin-bottom: 18px;';\n    const UEBERSCHRIFT = 'color: var(--text-primary, #e8e8ee); font-weight: 700; margin-bottom: 8px;';\n\n    function el(tag, css, text) {\n        const knoten = document.createElement(tag);\n        if (css) { knoten.setAttribute('style', css); }\n        if (text !== undefined &#038;&#038; text !== null) { knoten.textContent = text; }\n        return knoten;\n    }\n\n    function zeile(kasten, name, wert, hervor) {\n        const z = el('div', 'display: flex; justify-content: space-between; gap: 14px; flex-wrap: wrap; padding: 5px 0;');\n        z.appendChild(el('span', 'color: var(--text-secondary, #a0a0b0);', name));\n        z.appendChild(el('span', 'color: ' + (hervor ? 'var(--accent, #7ee787)' : 'var(--text-primary, #e8e8ee)') + '; font-family: monospace; text-align: right; word-break: break-all;', wert));\n        kasten.appendChild(z);\n    }\n\n    function meldung(text) {\n        out.textContent = '';\n        out.appendChild(el('div', 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-left: 3px solid var(--accent, #7ee787); color: var(--text-secondary, #a0a0b0); padding: 14px; border-radius: 6px;', text));\n    }\n\n    function anbieterKasten(titel, anbieter, namen) {\n        const kasten = el('div', KASTEN);\n        zeile(kasten, T.a_anzahl, String(anbieter.ids.length));\n        zeile(kasten, T.a_max, String(anbieter.maxId));\n        zeile(kasten, T.a_kodierung, anbieter.kodierung === 'bereich' ? T.a_bereich : T.a_bitfeld);\n        if (anbieter.ids.length === 0) {\n            kasten.appendChild(el('div', 'color: var(--text-secondary, #a0a0b0); padding-top: 6px;', T.a_keine));\n            return kasten;\n        }\n        const kurz = bereicheKuerzen(anbieter.ids).join(', ');\n        const feldText = el('div', 'color: var(--text-primary, #e8e8ee); font-family: monospace; font-size: 0.82rem; line-height: 1.6; padding-top: 8px; word-break: break-all;', kurz);\n        kasten.appendChild(feldText);\n\n        if (namen) {\n            const liste = el('div', 'padding-top: 10px;');\n            const gezeigt = anbieter.ids.slice(0, 40);\n            gezeigt.forEach(function (id) {\n                const name = namen[String(id)];\n                liste.appendChild(el('div', 'color: var(--text-secondary, #a0a0b0); font-size: 0.85rem; padding: 2px 0;', id + '  ' + (name ? name : '?')));\n            });\n            if (anbieter.ids.length > gezeigt.length) {\n                liste.appendChild(el('div', 'color: var(--text-secondary, #a0a0b0); font-size: 0.85rem; padding-top: 4px;', T.a_mehr.replace('%s', String(anbieter.ids.length - gezeigt.length))));\n            }\n            kasten.appendChild(liste);\n        }\n        return kasten;\n    }<\/p>\n<p>    function zweckeKasten(kern) {\n        const kasten = el('div', KASTEN);\n        const kopf = el('div', 'display: flex; gap: 10px; padding: 4px 0 8px; border-bottom: 1px solid var(--border, #2a2a35); font-size: 0.8rem; color: var(--text-secondary, #a0a0b0);');\n        kopf.appendChild(el('span', 'flex: 1;', T.s_zweck));\n        kopf.appendChild(el('span', 'width: 110px; text-align: center;', T.s_zustimmung));\n        kopf.appendChild(el('span', 'width: 110px; text-align: center;', T.s_li));\n        kasten.appendChild(kopf);<\/p>\n<p>        for (let i = 1; i <= ZWECKE.length; i++) {\n            const hatZ = kern.zwecke.indexOf(i) !== -1;\n            const hatL = kern.zweckeLI.indexOf(i) !== -1;\n            const z = el('div', 'display: flex; gap: 10px; align-items: center; padding: 6px 0; border-bottom: 1px solid var(--border, #2a2a35);');\n            z.appendChild(el('span', 'flex: 1; color: var(--text-primary, #e8e8ee); font-size: 0.88rem;', i + '. ' + ZWECKE[i - 1]));\n            z.appendChild(el('span', 'width: 110px; text-align: center; font-family: monospace; color: ' + (hatZ ? 'var(--accent, #7ee787)' : 'var(--text-secondary, #a0a0b0)') + ';', hatZ ? T.ja : T.nein));\n            const liText = LI_VERBOTEN.indexOf(i) !== -1 &#038;&#038; !hatL ? '-' : (hatL ? T.ja : T.nein);\n            const liFarbe = hatL &#038;&#038; LI_VERBOTEN.indexOf(i) !== -1 ? '#ffb86c' : (hatL ? 'var(--accent, #7ee787)' : 'var(--text-secondary, #a0a0b0)');\n            const liFeld = el('span', 'width: 110px; text-align: center; font-family: monospace; color: ' + liFarbe + ';', liText);\n            if (hatL &#038;&#038; LI_VERBOTEN.indexOf(i) !== -1) { liFeld.title = T.s_nicht_erlaubt; }\n            z.appendChild(liFeld);\n            kasten.appendChild(z);\n        }\n        return kasten;\n    }\n\n    function zeigen(erg) {\n        out.textContent = '';\n        const kern = erg.kern;\n\n        if (erg.warnungen.length) {\n            out.appendChild(el('div', UEBERSCHRIFT, T.h_warnungen));\n            const kasten = el('div', 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-left: 3px solid #ffb86c; border-radius: 6px; padding: 14px; margin-bottom: 18px;');\n            erg.warnungen.forEach(function (w) {\n                kasten.appendChild(el('div', 'color: var(--text-secondary, #a0a0b0); padding: 4px 0; font-size: 0.9rem;', w));\n            });\n            out.appendChild(kasten);\n        }\n\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_ueberblick));\n        const ueberblick = el('div', KASTEN);\n        zeile(ueberblick, T.l_version, String(kern.version));\n        zeile(ueberblick, T.l_erstellt, datumText(kern.erstellt));\n        zeile(ueberblick, T.l_geaendert, datumText(kern.geaendert));\n        zeile(ueberblick, T.l_cmp, String(kern.cmpId), true);\n        zeile(ueberblick, T.l_cmpversion, String(kern.cmpVersion));\n        zeile(ueberblick, T.l_bildschirm, String(kern.bildschirm));\n        zeile(ueberblick, T.l_sprache, kern.sprache);\n        zeile(ueberblick, T.l_gvl, String(kern.gvlVersion), true);\n        zeile(ueberblick, T.l_richtlinie, String(kern.richtlinie) + (kern.richtlinie >= 4 ? '  (TCF 2.2)' : ''));\n        zeile(ueberblick, T.l_land, kern.land);\n        zeile(ueberblick, T.l_dienst, kern.dienstspezifisch ? T.ja : T.nein);\n        zeile(ueberblick, T.l_stapel, kern.nichtStandard ? T.ja : T.nein);\n        zeile(ueberblick, T.l_zweckeins, kern.zweckEins ? T.ja : T.nein);\n        out.appendChild(ueberblick);<\/p>\n<p>        out.appendChild(el('div', UEBERSCHRIFT, T.h_zwecke));\n        out.appendChild(zweckeKasten(kern));<\/p>\n<p>        out.appendChild(el('div', UEBERSCHRIFT, T.h_merkmale));\n        const merkmale = el('div', KASTEN);\n        for (let i = 1; i <= MERKMALE.length; i++) {\n            zeile(merkmale, i + '. ' + MERKMALE[i - 1], kern.merkmale.indexOf(i) !== -1 ? T.ja : T.nein, kern.merkmale.indexOf(i) !== -1);\n        }\n        out.appendChild(merkmale);\n\n        const namen = erg.gvl &#038;&#038; erg.gvl.namen ? erg.gvl.namen : null;\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_anbieter + ' - ' + T.a_zustimmung));\n        out.appendChild(anbieterKasten(T.a_zustimmung, kern.anbieter, namen));\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_anbieter + ' - ' + T.a_li));\n        out.appendChild(anbieterKasten(T.a_li, kern.anbieterLI, namen));\n\n        if (!namen) {\n            out.appendChild(el('div', 'color: var(--text-secondary, #a0a0b0); font-size: 0.85rem; margin: -8px 0 18px;', T.hinweis_namen));\n        } else if (erg.gvl.fehler) {\n            out.appendChild(el('div', 'color: #ffb86c; font-size: 0.85rem; margin: -8px 0 18px;', T.hinweis_gvl_kaputt));\n        }\n\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_beschraenkungen));\n        const beschr = el('div', KASTEN);\n        if (!kern.beschraenkungen.length) {\n            beschr.appendChild(el('div', 'color: var(--text-secondary, #a0a0b0);', T.b_keine));\n        } else {\n            kern.beschraenkungen.forEach(function (b) {\n                const name = b.zweck >= 1 && b.zweck <= ZWECKE.length ? ZWECKE[b.zweck - 1] : '?';\n                const bereiche = b.anbieter.map(function (p) { return p[0] === p[1] ? String(p[0]) : p[0] + '-' + p[1]; }).join(', ');\n                zeile(beschr, T.b_zweck + ' ' + b.zweck + ': ' + name, (ARTEN[b.art] || '?') + '  [' + bereiche + ']');\n            });\n        }\n        out.appendChild(beschr);\n\n        const verleger = erg.segmente.filter(function (s) { return s.art === 3 &#038;&#038; s.verleger; });\n        if (verleger.length) {\n            out.appendChild(el('div', UEBERSCHRIFT, T.h_verleger));\n            const kasten = el('div', KASTEN);\n            const v = verleger[0].verleger;\n            zeile(kasten, T.v_zwecke, v.zwecke.length ? v.zwecke.join(', ') : T.v_keine);\n            zeile(kasten, T.v_zweckeLI, v.zweckeLI.length ? v.zweckeLI.join(', ') : T.v_keine);\n            zeile(kasten, T.v_eigene, v.anzahlEigene ? (v.eigene.join(', ') || T.v_keine) : T.v_keine);\n            out.appendChild(kasten);\n        }\n\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_segmente));\n        const segmente = el('div', KASTEN);\n        erg.segmente.forEach(function (s) {\n            zeile(segmente, s.name, T.seg_bits.replace('%s', String(s.bits)));\n        });\n        out.appendChild(segmente);\n    }\n\n    btn.addEventListener('click', function () {\n        const erg = dekodieren(feld.value, gvlFeld.value, Date.now());\n        if (!erg.ok) {\n            meldung(erg.fehler);\n            return;\n        }\n        zeigen(erg);\n    });\n\n    beispielBtn.addEventListener('click', function () {\n        \/\/ Ein selbst erzeugter, gueltiger String nach TCF 2.2 mit Kernsegment\n        \/\/ und Verlagssegment - siehe beispiel_bauen.js neben dieser Quelle.\n        feld.value = 'CQoSKcgQoSKcgAfAJBDETfEgAPLgAEPgAAYgJYQBoAAgAYADQAJgAWwF5wMgAywEsIAYAAgAaQCAAIwAEdABgDIAPA.eFAAACgAAA';\n        gvlFeld.value = '';\n        btn.click();\n    });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Decode an IAB TCF v2.2 consent string entirely in the browser: CMP, purposes, legitimate interest, vendors, publisher restrictions and the checks that TCF 2.2 made stricter.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[91040,91279],"class_list":["post-10229","page","type-page","status-publish","hentry","tag-consent-management","tag-prywatnosc-danych-pl"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>TCF v2.2 Consent String Decoder - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/tcf-consent-string-decoder\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"TCF v2.2 Consent String Decoder - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Decode an IAB TCF v2.2 consent string entirely in the browser: CMP, purposes, legitimate interest, vendors, publisher restrictions and the checks that TCF 2.2 made stricter.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/tcf-consent-string-decoder\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/tcf-consent-string-decoder\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/tcf-consent-string-decoder\\\/\",\"name\":\"TCF v2.2 Consent String Decoder - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-08-14T22:05:24+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/tcf-consent-string-decoder\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/tcf-consent-string-decoder\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/tcf-consent-string-decoder\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"TCF v2.2 Consent String Decoder\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"TCF v2.2 Consent String Decoder - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/tcf-consent-string-decoder\/","og_locale":"en_US","og_type":"article","og_title":"TCF v2.2 Consent String Decoder - Lukas Wojcik - Blog","og_description":"Decode an IAB TCF v2.2 consent string entirely in the browser: CMP, purposes, legitimate interest, vendors, publisher restrictions and the checks that TCF 2.2 made stricter.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/tcf-consent-string-decoder\/","og_site_name":"Lukas Wojcik - Blog","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/tcf-consent-string-decoder\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/tcf-consent-string-decoder\/","name":"TCF v2.2 Consent String Decoder - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-08-14T22:05:24+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/tcf-consent-string-decoder\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/tcf-consent-string-decoder\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/tcf-consent-string-decoder\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/"},{"@type":"ListItem","position":3,"name":"TCF v2.2 Consent String Decoder"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/10229","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=10229"}],"version-history":[{"count":0,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/10229\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/38"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=10229"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=10229"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}