Takaisin blogiin
Strategy

Vendor CMP Comparison: OneTrust vs. Didomi vs. GetCookies

Jennifer Park, Data Strategy DirectorOctober 25, 202518 min lukuaika
CMPComparisonOneTrustDidomi

TLDR: CMP selection requires evaluating compliance depth, integration capabilities, and total cost of ownership.

Read full summary Comprehensive CMP comparison framework covering compliance features, consent rate optimization, integration ecosystem, pricing models, and support quality. This guide provides technical evaluation criteria, scoring matrices, and implementation considerations to help you make an informed decision based on your specific requirements. *Summary by Claude AI*
## How do leading CMPs compare? Leading CMPs like **OneTrust**, **Didomi**, and **GetCookies** offer core consent management features but differ significantly in their target audience, pricing models, ease of use, and advanced functionalities like server-side integration or AI-powered cookie scanning. This comprehensive guide provides a technical framework for evaluating CMPs, including hands-on code examples for testing integration capabilities. ## Introduction Selecting a Consent Management Platform is one of the most consequential technology decisions for organizations navigating the global privacy landscape. The wrong choice can result in compliance gaps, integration headaches, poor user experience, and unexpected costs as your needs scale. Unlike simpler SaaS tools, CMPs sit at the intersection of legal requirements, user experience, marketing effectiveness, and technical architecture. A platform that excels for a small e-commerce site may completely fail for a multinational publisher with complex advertising relationships. This guide provides a systematic evaluation framework that goes beyond feature checklists. We'll examine the technical underpinnings of leading platforms, test their APIs and integration capabilities, and provide concrete criteria for matching CMP capabilities to your specific organizational needs. ## The CMP Evaluation Framework Before comparing specific vendors, let's establish a comprehensive evaluation framework: ```typescript // cmp-evaluation-framework.ts // Systematic CMP evaluation system interface CMPVendor { name: string; website: string; founded: number; headquarters: string; targetMarket: 'enterprise' | 'mid_market' | 'smb' | 'all_segments'; specializations: string[]; } interface ComplianceCapability { regulation: string; supportLevel: 'full' | 'partial' | 'roadmap' | 'none'; certifications: string[]; lastAuditDate?: string; notes?: string; } interface IntegrationCapability { name: string; type: 'native' | 'api' | 'webhook' | 'manual'; setupComplexity: 'low' | 'medium' | 'high'; documentation: 'excellent' | 'good' | 'fair' | 'poor'; maintenanceRequired: 'minimal' | 'moderate' | 'significant'; } interface PricingTier { name: string; monthlyPrice: number | 'custom'; annualDiscount: number; pageViews: number | 'unlimited'; domains: number | 'unlimited'; features: string[]; supportLevel: string; } interface CMPEvaluation { vendor: CMPVendor; compliance: ComplianceCapability[]; integrations: IntegrationCapability[]; pricing: PricingTier[]; technicalScores: TechnicalScores; userExperienceScores: UXScores; supportScores: SupportScores; overallScore: number; recommendations: string[]; } interface TechnicalScores { scriptSize: number; // KB loadTime: number; // ms apiResponseTime: number; // ms uptime: number; // percentage securityFeatures: number; // 1-10 customizability: number; // 1-10 serverSideSupport: boolean; multiTenantSupport: boolean; } interface UXScores { bannerDesignOptions: number; // 1-10 mobileExperience: number; // 1-10 accessibilityScore: number; // 1-10 preferenceCenterUX: number; // 1-10 consentRateOptimization: number; // 1-10 } interface SupportScores { documentationQuality: number; // 1-10 responseTime: number; // hours availableChannels: string[]; dedicatedSupport: boolean; onboardingAssistance: boolean; trainingResources: number; // 1-10 } class CMPEvaluator { private evaluations: Map = new Map(); private weights: EvaluationWeights; constructor(weights?: Partial) { this.weights = { compliance: 0.30, technical: 0.25, userExperience: 0.20, integration: 0.15, support: 0.10, ...weights }; } // Add vendor evaluation addVendorEvaluation(evaluation: CMPEvaluation): void { this.evaluations.set(evaluation.vendor.name, evaluation); } // Calculate weighted score for a vendor calculateScore(vendorName: string): DetailedScore { const evaluation = this.evaluations.get(vendorName); if (!evaluation) { throw new Error(`Vendor not found: ${vendorName}`); } // Compliance score const complianceScore = this.calculateComplianceScore(evaluation.compliance); // Technical score const technicalScore = this.calculateTechnicalScore(evaluation.technicalScores); // UX score const uxScore = this.calculateUXScore(evaluation.userExperienceScores); // Integration score const integrationScore = this.calculateIntegrationScore(evaluation.integrations); // Support score const supportScore = this.calculateSupportScore(evaluation.supportScores); // Weighted total const weightedScore = ( complianceScore * this.weights.compliance + technicalScore * this.weights.technical + uxScore * this.weights.userExperience + integrationScore * this.weights.integration + supportScore * this.weights.support ); return { vendor: vendorName, overall: Math.round(weightedScore * 10) / 10, breakdown: { compliance: { score: complianceScore, weight: this.weights.compliance }, technical: { score: technicalScore, weight: this.weights.technical }, userExperience: { score: uxScore, weight: this.weights.userExperience }, integration: { score: integrationScore, weight: this.weights.integration }, support: { score: supportScore, weight: this.weights.support } }, strengths: this.identifyStrengths(evaluation), weaknesses: this.identifyWeaknesses(evaluation), bestFor: this.determineBestUseCases(evaluation) }; } private calculateComplianceScore(compliance: ComplianceCapability[]): number { const regulationWeights: Record = { 'GDPR': 3, 'CCPA/CPRA': 2.5, 'IAB TCF 2.2': 2.5, 'Google Consent Mode v2': 2, 'LGPD': 1.5, 'POPIA': 1, 'PIPEDA': 1, 'ePrivacy': 1.5 }; let weightedScore = 0; let totalWeight = 0; compliance.forEach(cap => { const weight = regulationWeights[cap.regulation] || 1; totalWeight += weight; const supportScore = { 'full': 10, 'partial': 6, 'roadmap': 3, 'none': 0 }[cap.supportLevel]; // Bonus for certifications const certBonus = cap.certifications.length * 0.5; weightedScore += (supportScore + Math.min(certBonus, 2)) * weight; }); return totalWeight > 0 ? (weightedScore / totalWeight) : 0; } private calculateTechnicalScore(scores: TechnicalScores): number { // Script size score (lower is better) const scriptScore = scores.scriptSize <= 20 ? 10 : scores.scriptSize <= 40 ? 8 : scores.scriptSize <= 60 ? 6 : scores.scriptSize <= 100 ? 4 : 2; // Load time score (lower is better) const loadScore = scores.loadTime <= 50 ? 10 : scores.loadTime <= 100 ? 8 : scores.loadTime <= 200 ? 6 : scores.loadTime <= 500 ? 4 : 2; // API response time (lower is better) const apiScore = scores.apiResponseTime <= 50 ? 10 : scores.apiResponseTime <= 100 ? 8 : scores.apiResponseTime <= 200 ? 6 : 4; // Uptime score const uptimeScore = scores.uptime >= 99.99 ? 10 : scores.uptime >= 99.9 ? 8 : scores.uptime >= 99.5 ? 6 : 4; // Bonus for advanced features const featureBonus = (scores.serverSideSupport ? 1 : 0) + (scores.multiTenantSupport ? 0.5 : 0); return ( scriptScore * 0.2 + loadScore * 0.2 + apiScore * 0.15 + uptimeScore * 0.15 + scores.securityFeatures * 0.15 + scores.customizability * 0.15 + featureBonus ); } private calculateUXScore(scores: UXScores): number { return ( scores.bannerDesignOptions * 0.2 + scores.mobileExperience * 0.25 + scores.accessibilityScore * 0.2 + scores.preferenceCenterUX * 0.15 + scores.consentRateOptimization * 0.2 ); } private calculateIntegrationScore(integrations: IntegrationCapability[]): number { const criticalIntegrations = [ 'Google Tag Manager', 'Google Analytics 4', 'Google Ads', 'Meta/Facebook Pixel', 'Shopify', 'WordPress' ]; let score = 0; const maxScore = 10; // Check critical integrations criticalIntegrations.forEach(name => { const integration = integrations.find(i => i.name.toLowerCase().includes(name.toLowerCase()) ); if (integration) { const typeScore = integration.type === 'native' ? 1.5 : integration.type === 'api' ? 1.2 : 1; const complexityScore = integration.setupComplexity === 'low' ? 1.2 : integration.setupComplexity === 'medium' ? 1 : 0.8; score += typeScore * complexityScore; } }); // Bonus for total integrations const totalBonus = Math.min(integrations.length / 20, 1) * 2; return Math.min((score / criticalIntegrations.length) * 8 + totalBonus, maxScore); } private calculateSupportScore(scores: SupportScores): number { // Response time score (lower is better) const responseScore = scores.responseTime <= 1 ? 10 : scores.responseTime <= 4 ? 8 : scores.responseTime <= 8 ? 6 : scores.responseTime <= 24 ? 4 : 2; // Channel score const channelScore = Math.min(scores.availableChannels.length * 2, 10); return ( scores.documentationQuality * 0.25 + responseScore * 0.20 + channelScore * 0.15 + (scores.dedicatedSupport ? 2 : 0) + (scores.onboardingAssistance ? 1 : 0) + scores.trainingResources * 0.15 ); } private identifyStrengths(evaluation: CMPEvaluation): string[] { const strengths: string[] = []; // Compliance strengths const fullComplianceCount = evaluation.compliance.filter(c => c.supportLevel === 'full').length; if (fullComplianceCount >= 5) { strengths.push('Comprehensive global compliance coverage'); } // Technical strengths if (evaluation.technicalScores.scriptSize <= 30) { strengths.push('Lightweight script with minimal performance impact'); } if (evaluation.technicalScores.serverSideSupport) { strengths.push('Native server-side consent management'); } if (evaluation.technicalScores.loadTime <= 50) { strengths.push('Excellent page load performance'); } // UX strengths if (evaluation.userExperienceScores.mobileExperience >= 9) { strengths.push('Outstanding mobile experience'); } if (evaluation.userExperienceScores.accessibilityScore >= 9) { strengths.push('Best-in-class accessibility compliance'); } // Support strengths if (evaluation.supportScores.responseTime <= 2) { strengths.push('Fast support response times'); } if (evaluation.supportScores.documentationQuality >= 9) { strengths.push('Excellent documentation and resources'); } return strengths; } private identifyWeaknesses(evaluation: CMPEvaluation): string[] { const weaknesses: string[] = []; // Compliance weaknesses const partialOrNone = evaluation.compliance.filter( c => c.supportLevel === 'partial' || c.supportLevel === 'none' ).length; if (partialOrNone >= 3) { weaknesses.push('Gaps in global compliance coverage'); } // Technical weaknesses if (evaluation.technicalScores.scriptSize > 80) { weaknesses.push('Large script size may impact Core Web Vitals'); } if (!evaluation.technicalScores.serverSideSupport) { weaknesses.push('No native server-side consent management'); } // UX weaknesses if (evaluation.userExperienceScores.mobileExperience < 7) { weaknesses.push('Mobile experience needs improvement'); } if (evaluation.userExperienceScores.bannerDesignOptions < 6) { weaknesses.push('Limited customization options'); } // Support weaknesses if (evaluation.supportScores.responseTime > 12) { weaknesses.push('Slow support response times'); } return weaknesses; } private determineBestUseCases(evaluation: CMPEvaluation): string[] { const useCases: string[] = []; const vendor = evaluation.vendor; if (vendor.targetMarket === 'enterprise' || vendor.targetMarket === 'all_segments') { if (evaluation.technicalScores.multiTenantSupport) { useCases.push('Multi-brand enterprise organizations'); } } if (evaluation.integrations.some(i => i.name.includes('Shopify'))) { useCases.push('E-commerce businesses'); } if (evaluation.compliance.some(c => c.regulation === 'IAB TCF 2.2' && c.supportLevel === 'full' )) { useCases.push('Publishers with programmatic advertising'); } if (evaluation.userExperienceScores.consentRateOptimization >= 8) { useCases.push('Marketing-focused organizations optimizing consent rates'); } if (vendor.targetMarket === 'smb') { useCases.push('Small businesses needing quick deployment'); } return useCases; } // Compare multiple vendors compareVendors(vendorNames: string[]): VendorComparison { const scores = vendorNames.map(name => this.calculateScore(name)); // Sort by overall score scores.sort((a, b) => b.overall - a.overall); // Find category leaders const categoryLeaders: Record = {}; const categories = ['compliance', 'technical', 'userExperience', 'integration', 'support']; categories.forEach(category => { let leader = { name: '', score: 0 }; scores.forEach(score => { const catScore = score.breakdown[category as keyof typeof score.breakdown].score; if (catScore > leader.score) { leader = { name: score.vendor, score: catScore }; } }); categoryLeaders[category] = leader.name; }); return { rankings: scores, categoryLeaders, recommendation: this.generateRecommendation(scores) }; } private generateRecommendation(scores: DetailedScore[]): string { const top = scores[0]; const second = scores[1]; if (!second) { return `${top.vendor} is the recommended choice based on your evaluation criteria.`; } const gap = top.overall - second.overall; if (gap > 1.5) { return `${top.vendor} is the clear leader with a ${gap.toFixed(1)} point advantage. ` + `Key strengths: ${top.strengths.slice(0, 2).join(', ')}.`; } else if (gap > 0.5) { return `${top.vendor} edges out ${second.vendor} by ${gap.toFixed(1)} points. ` + `Consider ${second.vendor} if ${second.strengths[0]?.toLowerCase() || 'specific features'} are priorities.`; } else { return `${top.vendor} and ${second.vendor} are closely matched. ` + `Choose based on: ${top.vendor} excels at ${top.strengths[0]?.toLowerCase() || 'overall'}, ` + `while ${second.vendor} leads in ${second.strengths[0]?.toLowerCase() || 'specific areas'}.`; } } } interface EvaluationWeights { compliance: number; technical: number; userExperience: number; integration: number; support: number; } interface DetailedScore { vendor: string; overall: number; breakdown: { compliance: { score: number; weight: number }; technical: { score: number; weight: number }; userExperience: { score: number; weight: number }; integration: { score: number; weight: number }; support: { score: number; weight: number }; }; strengths: string[]; weaknesses: string[]; bestFor: string[]; } interface VendorComparison { rankings: DetailedScore[]; categoryLeaders: Record; recommendation: string; } export { CMPEvaluator, CMPEvaluation, CMPVendor }; ``` ## Key Comparison Criteria Deep Dive ### 1. Compliance Coverage Not all compliance is equal. A CMP claiming "GDPR compliance" might mean anything from basic cookie blocking to full data subject request handling. ```typescript // compliance-depth-analyzer.ts // Analyze actual depth of CMP compliance features interface RegulationRequirement { id: string; regulation: string; requirement: string; mandatory: boolean; technicalImplementation: string; } interface CMPComplianceDepth { vendor: string; regulation: string; requirements: ComplianceRequirementStatus[]; overallDepth: 'surface' | 'moderate' | 'deep' | 'comprehensive'; gaps: string[]; } interface ComplianceRequirementStatus { requirementId: string; supported: boolean; implementation: 'automatic' | 'configurable' | 'manual' | 'not_supported'; notes?: string; } class ComplianceDepthAnalyzer { private readonly gdprRequirements: RegulationRequirement[] = [ { id: 'gdpr_prior_consent', regulation: 'GDPR', requirement: 'Obtain consent before processing', mandatory: true, technicalImplementation: 'Script blocking until consent' }, { id: 'gdpr_granular_consent', regulation: 'GDPR', requirement: 'Allow granular purpose-based consent', mandatory: true, technicalImplementation: 'Multi-purpose consent UI' }, { id: 'gdpr_freely_given', regulation: 'GDPR', requirement: 'Consent must be freely given (no bundling)', mandatory: true, technicalImplementation: 'Individual purpose toggles' }, { id: 'gdpr_withdraw_easy', regulation: 'GDPR', requirement: 'Easy withdrawal of consent', mandatory: true, technicalImplementation: 'Preference center access' }, { id: 'gdpr_proof_consent', regulation: 'GDPR', requirement: 'Demonstrate proof of consent', mandatory: true, technicalImplementation: 'Consent receipt storage/export' }, { id: 'gdpr_cross_border', regulation: 'GDPR', requirement: 'Handle cross-border data transfers', mandatory: true, technicalImplementation: 'Transfer mechanism disclosure' }, { id: 'gdpr_dpo_contact', regulation: 'GDPR', requirement: 'DPO contact information', mandatory: false, technicalImplementation: 'Privacy notice integration' }, { id: 'gdpr_automated_decisions', regulation: 'GDPR', requirement: 'Disclosure of automated decision-making', mandatory: true, technicalImplementation: 'Purpose description fields' }, { id: 'gdpr_legitimate_interest', regulation: 'GDPR', requirement: 'Legitimate interest balancing test support', mandatory: false, technicalImplementation: 'LI assessment documentation' }, { id: 'gdpr_vendor_management', regulation: 'GDPR', requirement: 'Third-party vendor consent management', mandatory: true, technicalImplementation: 'Vendor list with individual consent' } ]; private readonly tcfRequirements: RegulationRequirement[] = [ { id: 'tcf_cmp_id', regulation: 'IAB TCF 2.2', requirement: 'Registered CMP with IAB', mandatory: true, technicalImplementation: 'IAB-issued CMP ID' }, { id: 'tcf_tc_string', regulation: 'IAB TCF 2.2', requirement: 'Generate valid TC String', mandatory: true, technicalImplementation: 'TCF encoder implementation' }, { id: 'tcf_gvl_sync', regulation: 'IAB TCF 2.2', requirement: 'Sync with Global Vendor List', mandatory: true, technicalImplementation: 'GVL API integration' }, { id: 'tcf_purposes', regulation: 'IAB TCF 2.2', requirement: 'Support all TCF purposes', mandatory: true, technicalImplementation: '11 purpose consent collection' }, { id: 'tcf_special_features', regulation: 'IAB TCF 2.2', requirement: 'Special feature consent', mandatory: true, technicalImplementation: 'Geolocation/device consent' }, { id: 'tcf_legitimate_interest', regulation: 'IAB TCF 2.2', requirement: 'Vendor LI declarations', mandatory: true, technicalImplementation: 'LI opt-out mechanism' }, { id: 'tcf_publisher_restrictions', regulation: 'IAB TCF 2.2', requirement: 'Publisher restriction support', mandatory: false, technicalImplementation: 'Vendor/purpose restrictions UI' }, { id: 'tcf_consent_storage', regulation: 'IAB TCF 2.2', requirement: 'Compliant consent storage', mandatory: true, technicalImplementation: 'Cookie/localStorage per spec' } ]; analyzeGDPRDepth( vendor: string, statuses: ComplianceRequirementStatus[] ): CMPComplianceDepth { const mandatoryReqs = this.gdprRequirements.filter(r => r.mandatory); const optionalReqs = this.gdprRequirements.filter(r => !r.mandatory); const mandatorySupported = mandatoryReqs.filter(req => statuses.find(s => s.requirementId === req.id && s.supported) ).length; const optionalSupported = optionalReqs.filter(req => statuses.find(s => s.requirementId === req.id && s.supported) ).length; const gaps = this.gdprRequirements .filter(req => !statuses.find(s => s.requirementId === req.id && s.supported)) .map(req => req.requirement); // Determine depth level let depth: 'surface' | 'moderate' | 'deep' | 'comprehensive'; if (mandatorySupported < mandatoryReqs.length * 0.6) { depth = 'surface'; } else if (mandatorySupported < mandatoryReqs.length * 0.9) { depth = 'moderate'; } else if (optionalSupported < optionalReqs.length * 0.5) { depth = 'deep'; } else { depth = 'comprehensive'; } return { vendor, regulation: 'GDPR', requirements: statuses, overallDepth: depth, gaps }; } analyzeTCFDepth( vendor: string, statuses: ComplianceRequirementStatus[] ): CMPComplianceDepth { const mandatoryReqs = this.tcfRequirements.filter(r => r.mandatory); const optionalReqs = this.tcfRequirements.filter(r => !r.mandatory); const mandatorySupported = mandatoryReqs.filter(req => statuses.find(s => s.requirementId === req.id && s.supported) ).length; const optionalSupported = optionalReqs.filter(req => statuses.find(s => s.requirementId === req.id && s.supported) ).length; const gaps = this.tcfRequirements .filter(req => !statuses.find(s => s.requirementId === req.id && s.supported)) .map(req => req.requirement); let depth: 'surface' | 'moderate' | 'deep' | 'comprehensive'; if (mandatorySupported < mandatoryReqs.length * 0.7) { depth = 'surface'; } else if (mandatorySupported < mandatoryReqs.length) { depth = 'moderate'; } else if (optionalSupported < optionalReqs.length * 0.5) { depth = 'deep'; } else { depth = 'comprehensive'; } return { vendor, regulation: 'IAB TCF 2.2', requirements: statuses, overallDepth: depth, gaps }; } // Generate compliance comparison table generateComparisonTable(analyses: CMPComplianceDepth[]): string { let table = '| Vendor | Regulation | Depth | Gap Count |\n'; table += '|--------|------------|-------|----------|\n'; analyses.forEach(analysis => { const depthIcon = { 'surface': 'Basic', 'moderate': 'Good', 'deep': 'Strong', 'comprehensive': 'Complete' }[analysis.overallDepth]; table += `| ${analysis.vendor} | ${analysis.regulation} | ${depthIcon} | ${analysis.gaps.length} |\n`; }); return table; } } export { ComplianceDepthAnalyzer, CMPComplianceDepth }; ``` ### 2. Technical Performance Analysis CMP script performance directly impacts Core Web Vitals and user experience. Here's how to benchmark: ```typescript // cmp-performance-tester.ts // Benchmark CMP technical performance interface PerformanceMetrics { scriptSize: number; // bytes compressedSize: number; // bytes (gzip) parseTime: number; // ms executionTime: number; // ms timeToInteractive: number; // ms memoryUsage: number; // bytes networkRequests: number; totalTransferSize: number; // bytes } interface CoreWebVitalsImpact { lcpDelta: number; // Largest Contentful Paint impact (ms) fidDelta: number; // First Input Delay impact (ms) clsDelta: number; // Cumulative Layout Shift impact ttfbDelta: number; // Time to First Byte impact (ms) } interface CMPPerformanceReport { vendor: string; testDate: string; testUrl: string; metrics: PerformanceMetrics; coreWebVitalsImpact: CoreWebVitalsImpact; recommendations: string[]; score: number; // 0-100 } class CMPPerformanceTester { private readonly performanceThresholds = { scriptSize: { excellent: 20000, // 20KB good: 40000, // 40KB acceptable: 80000, // 80KB poor: 150000 // 150KB }, loadTime: { excellent: 50, // ms good: 100, acceptable: 200, poor: 500 }, networkRequests: { excellent: 2, good: 4, acceptable: 8, poor: 15 } }; // Analyze performance impact analyzePerformance( vendor: string, metrics: PerformanceMetrics, baselineMetrics?: CoreWebVitalsImpact ): CMPPerformanceReport { const coreWebVitalsImpact = this.estimateCoreWebVitalsImpact(metrics); const score = this.calculatePerformanceScore(metrics); const recommendations = this.generateRecommendations(metrics); return { vendor, testDate: new Date().toISOString(), testUrl: 'benchmark-test-page', metrics, coreWebVitalsImpact, recommendations, score }; } private estimateCoreWebVitalsImpact(metrics: PerformanceMetrics): CoreWebVitalsImpact { // LCP impact: larger scripts delay render const lcpDelta = Math.round( (metrics.scriptSize / 1000) * 0.5 + // ~0.5ms per KB metrics.parseTime + (metrics.networkRequests * 20) // ~20ms per request ); // FID impact: JS execution blocks main thread const fidDelta = Math.round( metrics.executionTime * 0.3 + // Partial execution blocks input metrics.parseTime * 0.5 ); // CLS impact: banner rendering may cause layout shift // This depends heavily on implementation const clsDelta = metrics.networkRequests > 5 ? 0.05 : 0.02; // TTFB impact: minimal unless server-side rendering const ttfbDelta = 0; return { lcpDelta, fidDelta, clsDelta, ttfbDelta }; } private calculatePerformanceScore(metrics: PerformanceMetrics): number { let score = 100; // Script size penalty if (metrics.scriptSize > this.performanceThresholds.scriptSize.excellent) { const penalty = Math.min( 30, ((metrics.scriptSize - this.performanceThresholds.scriptSize.excellent) / 1000) * 0.5 ); score -= penalty; } // Load time penalty const totalLoadTime = metrics.parseTime + metrics.executionTime; if (totalLoadTime > this.performanceThresholds.loadTime.excellent) { const penalty = Math.min(25, (totalLoadTime - 50) / 10); score -= penalty; } // Network requests penalty if (metrics.networkRequests > this.performanceThresholds.networkRequests.excellent) { const penalty = (metrics.networkRequests - 2) * 3; score -= Math.min(20, penalty); } // Memory usage penalty (if excessive) if (metrics.memoryUsage > 5 * 1024 * 1024) { // 5MB const penalty = ((metrics.memoryUsage - 5 * 1024 * 1024) / (1024 * 1024)) * 2; score -= Math.min(15, penalty); } return Math.max(0, Math.round(score)); } private generateRecommendations(metrics: PerformanceMetrics): string[] { const recommendations: string[] = []; if (metrics.scriptSize > this.performanceThresholds.scriptSize.acceptable) { recommendations.push( 'Consider async loading or splitting the CMP script to reduce initial payload' ); } if (metrics.compressedSize > metrics.scriptSize * 0.4) { recommendations.push( 'Script compression ratio is suboptimal - may indicate inefficient code' ); } if (metrics.networkRequests > this.performanceThresholds.networkRequests.good) { recommendations.push( 'High number of network requests - consider bundling or using a CDN' ); } if (metrics.executionTime > 100) { recommendations.push( 'Long execution time may block main thread - verify defer/async loading' ); } if (metrics.memoryUsage > 3 * 1024 * 1024) { recommendations.push( 'High memory usage detected - may impact mobile device performance' ); } return recommendations; } // Compare multiple CMPs comparePerformance(reports: CMPPerformanceReport[]): PerformanceComparison { const sorted = [...reports].sort((a, b) => b.score - a.score); const best = sorted[0]; const worst = sorted[sorted.length - 1]; const averageScore = reports.reduce((sum, r) => sum + r.score, 0) / reports.length; return { rankings: sorted.map((r, i) => ({ rank: i + 1, vendor: r.vendor, score: r.score, scriptSize: `${(r.metrics.scriptSize / 1024).toFixed(1)}KB`, loadTime: `${(r.metrics.parseTime + r.metrics.executionTime).toFixed(0)}ms` })), bestPerformer: best.vendor, worstPerformer: worst.vendor, averageScore: Math.round(averageScore), scoreDifferential: best.score - worst.score, summary: this.generateComparisonSummary(sorted) }; } private generateComparisonSummary(sorted: CMPPerformanceReport[]): string { const best = sorted[0]; const gap = sorted[0].score - sorted[sorted.length - 1].score; if (gap < 10) { return 'All tested CMPs have similar performance characteristics.'; } else if (gap < 25) { return `${best.vendor} leads in performance, though differences are moderate.`; } else { return `Significant performance gap detected. ${best.vendor} substantially outperforms competitors.`; } } } interface PerformanceComparison { rankings: { rank: number; vendor: string; score: number; scriptSize: string; loadTime: string; }[]; bestPerformer: string; worstPerformer: string; averageScore: number; scoreDifferential: number; summary: string; } export { CMPPerformanceTester, CMPPerformanceReport, PerformanceMetrics }; ``` ### 3. Integration Ecosystem Testing The true test of a CMP is how well it integrates with your existing technology stack: ```typescript // integration-compatibility-tester.ts // Test CMP integration capabilities interface IntegrationTest { name: string; category: 'analytics' | 'advertising' | 'ecommerce' | 'tag_manager' | 'other'; testType: 'api' | 'event' | 'callback' | 'dataLayer'; steps: TestStep[]; expectedOutcome: string; } interface TestStep { action: string; waitFor?: string; timeout?: number; assertion?: string; } interface IntegrationTestResult { testName: string; passed: boolean; duration: number; errors: string[]; warnings: string[]; details: Record; } class IntegrationCompatibilityTester { private readonly standardTests: IntegrationTest[] = [ { name: 'Google Consent Mode v2 Integration', category: 'analytics', testType: 'dataLayer', steps: [ { action: 'Initialize CMP', waitFor: 'cmp_loaded' }, { action: 'Check default consent state', assertion: 'gtag consent default exists' }, { action: 'Grant all consent', waitFor: 'consent_updated' }, { action: 'Verify consent update event', assertion: 'gtag consent update fired' } ], expectedOutcome: 'Consent Mode signals properly sent to Google tags' }, { name: 'IAB TCF 2.2 TC String Generation', category: 'advertising', testType: 'api', steps: [ { action: 'Initialize CMP', waitFor: '__tcfapi available' }, { action: 'Call getTCData', assertion: 'returns valid tcData object' }, { action: 'Verify TC string format', assertion: 'tcString matches TCF spec' }, { action: 'Check vendor consent status', assertion: 'vendorConsents populated' } ], expectedOutcome: 'Valid TC String generated per IAB TCF 2.2 specification' }, { name: 'Google Tag Manager Integration', category: 'tag_manager', testType: 'event', steps: [ { action: 'Initialize GTM', waitFor: 'gtm.js loaded' }, { action: 'Initialize CMP', waitFor: 'cmp_loaded' }, { action: 'Check dataLayer events', assertion: 'consent events in dataLayer' }, { action: 'Grant consent', waitFor: 'consent_updated' }, { action: 'Verify tag firing', assertion: 'blocked tags now firing' } ], expectedOutcome: 'GTM properly receives consent signals and controls tag firing' }, { name: 'Facebook/Meta Pixel Consent', category: 'advertising', testType: 'callback', steps: [ { action: 'Initialize CMP', waitFor: 'cmp_loaded' }, { action: 'Check fbq consent state', assertion: 'fbq consent revoked by default' }, { action: 'Grant advertising consent', waitFor: 'consent_updated' }, { action: 'Verify fbq consent grant', assertion: 'fbq consent granted' } ], expectedOutcome: 'Meta Pixel respects consent state changes' } ]; // Test Google Consent Mode integration async testGoogleConsentMode(cmpApi: any): Promise { const result: IntegrationTestResult = { testName: 'Google Consent Mode v2 Integration', passed: false, duration: 0, errors: [], warnings: [], details: {} }; const startTime = Date.now(); try { // Check if gtag is available if (typeof window === 'undefined' || !(window as any).gtag) { result.errors.push('gtag not available on page'); return result; } const gtag = (window as any).gtag; // Capture consent commands const consentCommands: any[] = []; const originalGtag = gtag; (window as any).gtag = function(...args: any[]) { if (args[0] === 'consent') { consentCommands.push({ type: args[1], params: args[2] }); } originalGtag.apply(this, args); }; // Initialize CMP and wait await cmpApi.init(); // Check for default consent command const defaultCommand = consentCommands.find(c => c.type === 'default'); if (!defaultCommand) { result.errors.push('No consent default command detected'); } else { result.details.defaultConsent = defaultCommand.params; // Verify required parameters const requiredParams = ['ad_storage', 'analytics_storage', 'ad_user_data', 'ad_personalization']; requiredParams.forEach(param => { if (!(param in defaultCommand.params)) { result.warnings.push(`Missing recommended parameter: ${param}`); } }); } // Grant consent and check for update await cmpApi.acceptAll(); // Small delay for event propagation await new Promise(resolve => setTimeout(resolve, 100)); const updateCommand = consentCommands.find(c => c.type === 'update'); if (!updateCommand) { result.errors.push('No consent update command after acceptAll()'); } else { result.details.updateConsent = updateCommand.params; } // Restore original gtag (window as any).gtag = originalGtag; result.passed = result.errors.length === 0; } catch (error) { result.errors.push(`Test error: ${error}`); } result.duration = Date.now() - startTime; return result; } // Test IAB TCF API async testTCFAPI(): Promise { const result: IntegrationTestResult = { testName: 'IAB TCF 2.2 TC String Generation', passed: false, duration: 0, errors: [], warnings: [], details: {} }; const startTime = Date.now(); try { if (typeof window === 'undefined' || !(window as any).__tcfapi) { result.errors.push('__tcfapi not available'); return result; } const tcfapi = (window as any).__tcfapi; // Get ping status await new Promise((resolve) => { tcfapi('ping', 2, (pingReturn: any) => { result.details.ping = pingReturn; if (!pingReturn.cmpLoaded) { result.errors.push('CMP not loaded according to ping'); } if (pingReturn.cmpVersion !== 2) { result.warnings.push(`CMP version is ${pingReturn.cmpVersion}, expected 2`); } resolve(); }); }); // Get TC data await new Promise((resolve) => { tcfapi('getTCData', 2, (tcData: any, success: boolean) => { if (!success) { result.errors.push('getTCData returned failure'); resolve(); return; } result.details.tcData = { tcString: tcData.tcString, eventStatus: tcData.eventStatus, cmpStatus: tcData.cmpStatus, purposeConsents: tcData.purpose?.consents, vendorConsents: tcData.vendor?.consents }; // Validate TC string format if (!tcData.tcString || tcData.tcString.length < 50) { result.errors.push('Invalid TC string length'); } // Check for required properties if (!tcData.purpose || !tcData.vendor) { result.errors.push('Missing purpose or vendor data'); } resolve(); }); }); result.passed = result.errors.length === 0; } catch (error) { result.errors.push(`Test error: ${error}`); } result.duration = Date.now() - startTime; return result; } // Generate integration compatibility report generateReport(results: IntegrationTestResult[]): IntegrationReport { const passed = results.filter(r => r.passed).length; const failed = results.filter(r => !r.passed).length; const criticalFailures = results.filter(r => !r.passed && ( r.testName.includes('Consent Mode') || r.testName.includes('TCF') ) ); return { summary: { total: results.length, passed, failed, passRate: Math.round((passed / results.length) * 100) }, results, criticalIssues: criticalFailures.map(r => ({ test: r.testName, errors: r.errors })), recommendations: this.generateIntegrationRecommendations(results) }; } private generateIntegrationRecommendations(results: IntegrationTestResult[]): string[] { const recommendations: string[] = []; const consentModeResult = results.find(r => r.testName.includes('Consent Mode')); if (consentModeResult && !consentModeResult.passed) { recommendations.push( 'Critical: Fix Google Consent Mode integration to maintain Google Ads functionality' ); } const tcfResult = results.find(r => r.testName.includes('TCF')); if (tcfResult && !tcfResult.passed) { recommendations.push( 'Critical: Fix IAB TCF integration for programmatic advertising compliance' ); } const gtmResult = results.find(r => r.testName.includes('GTM')); if (gtmResult && gtmResult.warnings.length > 0) { recommendations.push( 'Review GTM integration for optimal consent signal handling' ); } return recommendations; } } interface IntegrationReport { summary: { total: number; passed: number; failed: number; passRate: number; }; results: IntegrationTestResult[]; criticalIssues: { test: string; errors: string[] }[]; recommendations: string[]; } export { IntegrationCompatibilityTester, IntegrationTestResult }; ``` ## Detailed Vendor Comparison ### Comparison Matrix | Feature | OneTrust | Didomi | GetCookies | |---------|----------|--------|---------------| | **Target Market** | Enterprise | Mid-Market | All Segments | | **Script Size** | ~120KB | ~80KB | ~25KB | | **Load Time** | ~200ms | ~150ms | ~50ms | | **Google Consent Mode v2** | Yes | Yes | Native | | **IAB TCF 2.2** | Yes | Yes | Yes | | **Server-Side CMP** | Add-on | Add-on | Native | | **AI Cookie Classification** | Medium | Medium | High | | **Automated Scanning** | Daily | Daily | Real-time | | **A/B Testing** | Enterprise | Yes | Yes | | **Multi-tenant** | Yes | Limited | Yes | | **Free Tier** | No | Limited | Yes | | **Starting Price** | $500+/mo | $150+/mo | $29/mo | ### Vendor Profiles ```typescript // vendor-profiles.ts // Detailed vendor profile data const vendorProfiles = { oneTrust: { name: 'OneTrust', founded: 2016, headquarters: 'Atlanta, USA', strengths: [ 'Comprehensive enterprise privacy suite', 'Strong compliance consulting services', 'Extensive regulation coverage', 'Robust audit and reporting features' ], weaknesses: [ 'Higher script size impacts page speed', 'Complex setup requires dedicated resources', 'Premium pricing excludes smaller businesses', 'Steep learning curve for administrators' ], bestFor: [ 'Large enterprises with complex compliance needs', 'Organizations requiring integrated privacy management', 'Companies with dedicated privacy teams', 'Heavily regulated industries (finance, healthcare)' ], pricingModel: 'Enterprise custom pricing, typically $500-5000+/month', integrations: 150, supportOptions: ['Email', 'Phone', 'Dedicated CSM', 'Professional Services'] }, didomi: { name: 'Didomi', founded: 2017, headquarters: 'Paris, France', strengths: [ 'Strong European market presence', 'Good balance of features and usability', 'Solid TCF implementation', 'Reasonable mid-market pricing' ], weaknesses: [ 'Server-side features are add-ons', 'Limited free tier functionality', 'Less comprehensive than enterprise solutions', 'API documentation could be better' ], bestFor: [ 'European publishers and media companies', 'Mid-sized e-commerce businesses', 'Companies with moderate technical resources', 'Organizations prioritizing TCF compliance' ], pricingModel: 'Tiered pricing from $150/month, scales with traffic', integrations: 100, supportOptions: ['Email', 'Chat', 'Documentation', 'Community'] }, getCookie: { name: 'GetCookie', founded: 2024, headquarters: 'Norway', strengths: [ 'Lightweight script (25KB) with minimal performance impact', 'Native Google Consent Mode v2 integration', 'Built-in server-side consent management', 'AI-powered real-time cookie classification', 'Competitive pricing for all business sizes', 'Modern tech stack and API-first design' ], weaknesses: [ 'Newer entrant to market', 'Smaller partner ecosystem', 'Enterprise features still maturing' ], bestFor: [ 'Performance-focused websites', 'Companies prioritizing Core Web Vitals', 'Businesses wanting modern architecture', 'Organizations seeking value pricing', 'Teams preferring developer-friendly tools' ], pricingModel: 'Free tier available, paid from $29/month', integrations: 50, supportOptions: ['Email', 'Chat', 'Documentation', 'API Support'] } }; export { vendorProfiles }; ``` ## Total Cost of Ownership Calculator Beyond monthly fees, CMP costs include implementation, maintenance, and opportunity costs: ```typescript // tco-calculator.ts // Calculate true total cost of ownership for CMP selection interface TCOInputs { monthlyTraffic: number; numberOfDomains: number; implementationHours: number; hourlyRate: number; maintenanceHoursPerMonth: number; vendorMonthlyFee: number; contractLengthMonths: number; expectedConsentRate: number; averageRevenuePerUser: number; } interface TCOBreakdown { implementationCost: number; vendorFees: number; maintenanceCost: number; opportunityCost: number; totalCost: number; costPerUser: number; roi: number; } class TCOCalculator { calculate(inputs: TCOInputs): TCOBreakdown { // Implementation cost (one-time) const implementationCost = inputs.implementationHours * inputs.hourlyRate; // Vendor fees over contract period const vendorFees = inputs.vendorMonthlyFee * inputs.contractLengthMonths; // Maintenance cost over contract period const maintenanceCost = inputs.maintenanceHoursPerMonth * inputs.hourlyRate * inputs.contractLengthMonths; // Opportunity cost: revenue lost from users who reject consent const totalUsersOverContract = inputs.monthlyTraffic * inputs.contractLengthMonths; const rejectionRate = 1 - inputs.expectedConsentRate; const usersWhoReject = totalUsersOverContract * rejectionRate; // Assume 30% revenue reduction for users without consent (can't track/personalize) const opportunityCost = usersWhoReject * inputs.averageRevenuePerUser * 0.30; const totalCost = implementationCost + vendorFees + maintenanceCost + opportunityCost; // Cost per consenting user const consentingUsers = totalUsersOverContract * inputs.expectedConsentRate; const costPerUser = totalCost / consentingUsers; // ROI calculation (value preserved vs cost) const revenuePreserved = consentingUsers * inputs.averageRevenuePerUser; const roi = ((revenuePreserved - totalCost) / totalCost) * 100; return { implementationCost, vendorFees, maintenanceCost, opportunityCost, totalCost, costPerUser, roi }; } // Compare TCO across vendors compareTCO(scenarios: { vendor: string; inputs: TCOInputs }[]): TCOComparison { const results = scenarios.map(scenario => ({ vendor: scenario.vendor, breakdown: this.calculate(scenario.inputs), inputs: scenario.inputs })); // Sort by total cost results.sort((a, b) => a.breakdown.totalCost - b.breakdown.totalCost); const cheapest = results[0]; const mostExpensive = results[results.length - 1]; const savings = mostExpensive.breakdown.totalCost - cheapest.breakdown.totalCost; return { results, lowestTCO: cheapest.vendor, highestTCO: mostExpensive.vendor, potentialSavings: savings, recommendation: this.generateTCORecommendation(results) }; } private generateTCORecommendation( results: { vendor: string; breakdown: TCOBreakdown; inputs: TCOInputs }[] ): string { const cheapest = results[0]; const bestROI = [...results].sort((a, b) => b.breakdown.roi - a.breakdown.roi)[0]; if (cheapest.vendor === bestROI.vendor) { return `${cheapest.vendor} offers both the lowest TCO and best ROI.`; } return `${cheapest.vendor} has the lowest total cost, but ${bestROI.vendor} ` + `offers better ROI due to higher consent rates. Consider your priorities.`; } // Generate detailed report generateReport(breakdown: TCOBreakdown, inputs: TCOInputs): string { const formatCurrency = (n: number) => `$${n.toLocaleString('en-US', { maximumFractionDigits: 0 })}`; return ` # Total Cost of Ownership Report ## Summary - **Total Cost (${inputs.contractLengthMonths} months)**: ${formatCurrency(breakdown.totalCost)} - **Cost Per Consenting User**: ${formatCurrency(breakdown.costPerUser)} - **ROI**: ${breakdown.roi.toFixed(1)}% ## Cost Breakdown | Category | Cost | % of Total | |----------|------|------------| | Implementation | ${formatCurrency(breakdown.implementationCost)} | ${((breakdown.implementationCost / breakdown.totalCost) * 100).toFixed(1)}% | | Vendor Fees | ${formatCurrency(breakdown.vendorFees)} | ${((breakdown.vendorFees / breakdown.totalCost) * 100).toFixed(1)}% | | Maintenance | ${formatCurrency(breakdown.maintenanceCost)} | ${((breakdown.maintenanceCost / breakdown.totalCost) * 100).toFixed(1)}% | | Opportunity Cost | ${formatCurrency(breakdown.opportunityCost)} | ${((breakdown.opportunityCost / breakdown.totalCost) * 100).toFixed(1)}% | ## Assumptions - Monthly traffic: ${inputs.monthlyTraffic.toLocaleString()} - Expected consent rate: ${(inputs.expectedConsentRate * 100).toFixed(0)}% - Average revenue per user: ${formatCurrency(inputs.averageRevenuePerUser)} `.trim(); } } interface TCOComparison { results: { vendor: string; breakdown: TCOBreakdown; inputs: TCOInputs }[]; lowestTCO: string; highestTCO: string; potentialSavings: number; recommendation: string; } export { TCOCalculator, TCOInputs, TCOBreakdown }; ``` ## Frequently Asked Questions ### What's the most important factor when choosing a CMP? Compliance coverage should be your first filter—a CMP that doesn't fully support your required regulations is disqualified regardless of other features. After compliance, prioritize based on your situation: performance for content sites, integrations for marketing-heavy organizations, and ease of use if you lack technical resources. ### Should I choose a specialized or all-in-one CMP? Specialized CMPs (consent-only) typically offer better performance and lower costs but require integration with other privacy tools. All-in-one platforms provide convenience but may be overkill for simple needs. Match the scope to your actual requirements. ### How important is IAB TCF certification? Critical for publishers with programmatic advertising relationships. TCF compliance ensures your consent signals are accepted by the ad tech ecosystem. E-commerce sites without programmatic ads may not need full TCF support. ### Can I switch CMPs later without losing consent records? Technically yes, but it's complex. Consent records should be exportable (check vendor contracts), but you'll need to re-prompt users if you can't migrate consent state. Plan for at least 1-2 week transition overlap. ### What about open-source CMPs? Open-source options like Klaro or Osano's free tier work well for simple sites but typically lack enterprise features, dedicated support, and may require more technical maintenance. They're best for technically capable teams with straightforward needs. ## Making Your Decision Choosing a CMP is a significant commitment that impacts compliance, user experience, and marketing effectiveness. The framework and tools provided in this guide should help you make a systematic, data-driven decision. Start by defining your must-have requirements (regulations, integrations, budget constraints), then use the evaluation framework to score your shortlisted vendors. Request trials or proofs-of-concept before committing to long contracts. Remember that the "best" CMP is the one that best fits your specific needs—a solution perfect for a global enterprise may be completely wrong for a regional e-commerce site, and vice versa. Focus on match quality, not abstract rankings.
J

Jennifer Park, Data Strategy Director

Kirjoittaja GetCookiesissa, erikoistunut tietosuojavaatimustenmukaisuuteen, hyväksyntähallintaan ja digitaalisen markkinoinnin optimointiin.

Valmis yksinkertaistamaan evästehyväksyntää?

GetCookies tekee GDPR:n, CCPA:n ja maailmanlaajuisen tietosuojavaatimustenmukaisuuden vaivattomaksi. Aloita tänään.