DOMPurify's hook system provides an event-driven architecture for extending sanitization functionality with custom logic. Hooks allow you to intercept and modify the sanitization process at specific points, enabling custom validation, transformation, and logging.
Add, remove, and manage hook functions that execute at specific points during sanitization.
/**
* Add a hook function to execute at a specific sanitization point
* @param entryPoint - The sanitization event to hook into
* @param hookFunction - Function to execute when the event occurs
*/
function addHook(entryPoint: HookName, hookFunction: HookFunction): void;
/**
* Remove a specific hook function or the last added hook at an entry point
* @param entryPoint - The sanitization event to remove hook from
* @param hookFunction - Specific hook to remove (optional, removes last if not specified)
* @returns The removed hook function
*/
function removeHook(entryPoint: HookName, hookFunction?: HookFunction): HookFunction | undefined;
/**
* Remove all hooks at a specific entry point
* @param entryPoint - The sanitization event to clear hooks from
*/
function removeHooks(entryPoint: HookName): void;
/**
* Remove all hooks from all entry points
*/
function removeAllHooks(): void;
type HookName =
| 'beforeSanitizeElements'
| 'afterSanitizeElements'
| 'beforeSanitizeAttributes'
| 'afterSanitizeAttributes'
| 'uponSanitizeElement'
| 'uponSanitizeAttribute'
| 'beforeSanitizeShadowDOM'
| 'afterSanitizeShadowDOM'
| 'uponSanitizeShadowNode';Usage Examples:
import DOMPurify from "dompurify";
// Add a logging hook
DOMPurify.addHook('beforeSanitizeElements', function(node, data, config) {
console.log('Processing element:', node.nodeName);
});
// Add multiple hooks to same entry point
const validateHook = (node, data, config) => {
// Custom validation logic
};
const logHook = (node, data, config) => {
// Logging logic
};
DOMPurify.addHook('uponSanitizeElement', validateHook);
DOMPurify.addHook('uponSanitizeElement', logHook);
// Remove specific hook
DOMPurify.removeHook('uponSanitizeElement', validateHook);
// Remove all hooks at entry point
DOMPurify.removeHooks('beforeSanitizeElements');
// Clear all hooks
DOMPurify.removeAllHooks();Hooks that execute during element processing phases of sanitization.
/**
* Hook executed before each element is processed for sanitization
*/
type BeforeSanitizeElementsHook = (
this: DOMPurify,
currentNode: Node,
hookEvent: null,
config: Config
) => void;
/**
* Hook executed after each element has been processed for sanitization
*/
type AfterSanitizeElementsHook = (
this: DOMPurify,
currentNode: Node,
hookEvent: null,
config: Config
) => void;
/**
* Hook executed when an element is being evaluated for removal/keeping
*/
type UponSanitizeElementHook = (
this: DOMPurify,
currentNode: Node,
hookEvent: {
tagName: string;
allowedTags: Record<string, boolean>;
},
config: Config
) => void;Usage Examples:
// Log all elements being processed
DOMPurify.addHook('beforeSanitizeElements', function(node) {
if (node.nodeType === 1) { // Element node
console.log(`Processing: <${node.nodeName.toLowerCase()}>`);
}
});
// Custom element validation
DOMPurify.addHook('uponSanitizeElement', function(node, data, config) {
const tagName = data.tagName;
// Allow custom elements with specific prefix
if (tagName.startsWith('app-') && !data.allowedTags[tagName]) {
data.allowedTags[tagName] = true;
}
// Block specific elements even if normally allowed
if (tagName === 'iframe' && node.getAttribute('src')?.includes('untrusted.com')) {
data.allowedTags[tagName] = false;
}
});
// Post-processing cleanup
DOMPurify.addHook('afterSanitizeElements', function(node) {
// Add custom attributes to elements after sanitization
if (node.nodeType === 1 && node.nodeName === 'A') {
node.setAttribute('rel', 'noopener');
}
});Hooks that execute during attribute processing phases of sanitization.
/**
* Hook executed before attributes are processed on an element
*/
type BeforeSanitizeAttributesHook = (
this: DOMPurify,
currentNode: Element,
hookEvent: null,
config: Config
) => void;
/**
* Hook executed after attributes have been processed on an element
*/
type AfterSanitizeAttributesHook = (
this: DOMPurify,
currentNode: Element,
hookEvent: null,
config: Config
) => void;
/**
* Hook executed when an attribute is being evaluated for removal/keeping
*/
type UponSanitizeAttributeHook = (
this: DOMPurify,
currentNode: Element,
hookEvent: {
attrName: string;
attrValue: string;
keepAttr: boolean;
allowedAttributes: Record<string, boolean>;
forceKeepAttr?: boolean;
},
config: Config
) => void;Usage Examples:
// Custom attribute validation
DOMPurify.addHook('uponSanitizeAttribute', function(node, data, config) {
const { attrName, attrValue } = data;
// Allow custom data attributes
if (attrName.startsWith('data-app-')) {
data.keepAttr = true;
data.forceKeepAttr = true;
}
// Validate URL attributes
if (attrName === 'href' && !attrValue.match(/^https?:\/\//)) {
data.keepAttr = false;
}
// Transform attribute values
if (attrName === 'target' && attrValue === '_blank') {
data.attrValue = '_blank';
// Also need to set rel="noopener" somewhere else
}
});
// Attribute logging and analysis
DOMPurify.addHook('beforeSanitizeAttributes', function(node) {
console.log(`Processing attributes for: <${node.nodeName.toLowerCase()}>`);
for (let i = 0; i < node.attributes.length; i++) {
const attr = node.attributes[i];
console.log(` ${attr.name}="${attr.value}"`);
}
});
// Post-attribute processing
DOMPurify.addHook('afterSanitizeAttributes', function(node) {
// Add security attributes to links
if (node.nodeName === 'A' && node.hasAttribute('href')) {
node.setAttribute('rel', 'noopener noreferrer');
}
});Hooks that execute during Shadow DOM processing for Web Components.
/**
* Hook executed before Shadow DOM content is processed
*/
type BeforeSanitizeShadowDOMHook = (
this: DOMPurify,
currentNode: DocumentFragment,
hookEvent: null,
config: Config
) => void;
/**
* Hook executed after Shadow DOM content has been processed
*/
type AfterSanitizeShadowDOMHook = (
this: DOMPurify,
currentNode: DocumentFragment,
hookEvent: null,
config: Config
) => void;
/**
* Hook executed for each node within Shadow DOM
*/
type UponSanitizeShadowNodeHook = (
this: DOMPurify,
currentNode: Node,
hookEvent: null,
config: Config
) => void;Usage Examples:
// Shadow DOM processing
DOMPurify.addHook('beforeSanitizeShadowDOM', function(fragment) {
console.log('Processing Shadow DOM fragment');
});
// Custom Shadow DOM node handling
DOMPurify.addHook('uponSanitizeShadowNode', function(node) {
// Special handling for nodes inside Shadow DOM
if (node.nodeType === 1) {
console.log(`Shadow DOM element: ${node.nodeName}`);
}
});
// Post-Shadow DOM processing
DOMPurify.addHook('afterSanitizeShadowDOM', function(fragment) {
console.log('Shadow DOM processing complete');
console.log(`Fragment contains ${fragment.childNodes.length} nodes`);
});Understanding the execution context and state available in hook functions.
/**
* Hook function execution context
* - `this` refers to the DOMPurify instance
* - Access to current sanitization config via `config` parameter
* - Node being processed via `currentNode` parameter
* - Hook-specific data via `hookEvent` parameter
* - Can modify hookEvent properties to influence sanitization
*/Usage Examples:
// Access DOMPurify instance and config in hooks
DOMPurify.addHook('uponSanitizeElement', function(node, data, config) {
// `this` is the DOMPurify instance
console.log('DOMPurify version:', this.version);
// Access current configuration
console.log('Return DOM mode:', config.RETURN_DOM);
console.log('Allowed tags:', config.ALLOWED_TAGS);
// Modify sanitization behavior through data object
if (config.CUSTOM_VALIDATION_MODE) {
// Custom logic based on configuration
}
});
// State tracking across hook calls
let elementCount = 0;
DOMPurify.addHook('beforeSanitizeElements', function(node) {
if (node.nodeType === 1) {
elementCount++;
}
});
DOMPurify.addHook('afterSanitizeElements', function(node) {
console.log(`Processed ${elementCount} elements total`);
});Common patterns and advanced techniques for using hooks effectively.
/**
* Hook chaining: Multiple hooks on same entry point execute in order
* Hook data modification: uponSanitize* hooks can modify behavior
* Conditional hooks: Use config or node properties for conditional logic
* Error handling: Hooks should handle errors to avoid breaking sanitization
* Performance: Minimize work in hooks as they execute for every node/attribute
*/Usage Examples:
// Hook chaining for multi-step processing
DOMPurify.addHook('uponSanitizeElement', function(node, data, config) {
// Step 1: Basic validation
console.log('Step 1: Basic validation');
});
DOMPurify.addHook('uponSanitizeElement', function(node, data, config) {
// Step 2: Custom business logic
console.log('Step 2: Custom validation');
});
// Conditional hook logic
DOMPurify.addHook('uponSanitizeAttribute', function(node, data, config) {
const { attrName, attrValue } = data;
// Different handling based on element type
switch (node.nodeName) {
case 'A':
if (attrName === 'href') {
// Link-specific validation
}
break;
case 'IMG':
if (attrName === 'src') {
// Image-specific validation
}
break;
}
});
// Error handling in hooks
DOMPurify.addHook('beforeSanitizeElements', function(node) {
try {
// Potentially dangerous operation
someCustomValidation(node);
} catch (error) {
console.error('Hook error:', error);
// Don't re-throw to avoid breaking sanitization
}
});
// Performance-conscious hook
const allowedCustomElements = new Set(['app-header', 'app-footer']);
DOMPurify.addHook('uponSanitizeElement', function(node, data, config) {
// Fast lookup instead of expensive operations
if (allowedCustomElements.has(data.tagName)) {
data.allowedTags[data.tagName] = true;
}
});