--- title: HTMLRewriter description: Use Bun's HTMLRewriter to transform HTML documents with CSS selectors --- HTMLRewriter lets you use CSS selectors to transform HTML documents. It works with `Request`, `Response`, as well as `string`. Bun's implementation is based on Cloudflare's [lol-html](https://github.com/cloudflare/lol-html). --- ## Usage A common usecase is rewriting URLs in HTML content. Here's an example that rewrites image sources and link URLs to use a CDN domain: ```ts // Replace all images with a rickroll const rewriter = new HTMLRewriter().on("img", { element(img) { // Famous rickroll video thumbnail img.setAttribute("src", "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg"); // Wrap the image in a link to the video img.before('', { html: true, }); img.after("", { html: true }); // Add some fun alt text img.setAttribute("alt", "Definitely not a rickroll"); }, }); // An example HTML document const html = `
`;
const result = rewriter.transform(html);
console.log(result);
```
This replaces all images with a thumbnail of Rick Astley and wraps each `
```
Now every image on the page will be replaced with a thumbnail of Rick Astley, and clicking any image will lead to [a very famous video](https://www.youtube.com/watch?v=dQw4w9WgXcQ).
### Input types
HTMLRewriter can transform HTML from various sources. The input is automatically handled based on its type:
```ts
// From Response
rewriter.transform(new Response("New content
", { html: true }); }, // Handle text nodes text(text) { text.replace("new text"); }, // Handle comments comments(comment) { comment.remove(); }, }); ``` The handlers can be asynchronous and return a Promise. Note that async operations will block the transformation until they complete: ```ts rewriter.on("div", { async element(element) { await Bun.sleep(1000); element.setInnerContent("replace", { html: true }); }, }); ``` ### CSS Selector Support The `on()` method supports a wide range of CSS selectors: ```ts // Tag selectors rewriter.on("p", handler); // Class selectors rewriter.on("p.red", handler); // ID selectors rewriter.on("h1#header", handler); // Attribute selectors rewriter.on("p[data-test]", handler); // Has attribute rewriter.on('p[data-test="one"]', handler); // Exact match rewriter.on('p[data-test="one" i]', handler); // Case-insensitive rewriter.on('p[data-test="one" s]', handler); // Case-sensitive rewriter.on('p[data-test~="two"]', handler); // Word match rewriter.on('p[data-test^="a"]', handler); // Starts with rewriter.on('p[data-test$="1"]', handler); // Ends with rewriter.on('p[data-test*="b"]', handler); // Contains rewriter.on('p[data-test|="a"]', handler); // Dash-separated // Combinators rewriter.on("div span", handler); // Descendant rewriter.on("div > span", handler); // Direct child // Pseudo-classes rewriter.on("p:nth-child(2)", handler); rewriter.on("p:first-child", handler); rewriter.on("p:nth-of-type(2)", handler); rewriter.on("p:first-of-type", handler); rewriter.on("p:not(:first-child)", handler); // Universal selector rewriter.on("*", handler); ``` ### Element Operations Elements provide various methods for manipulation. All modification methods return the element instance for chaining: ```ts rewriter.on("div", { element(el) { // Attributes el.setAttribute("class", "new-class").setAttribute("data-id", "123"); const classAttr = el.getAttribute("class"); // "new-class" const hasId = el.hasAttribute("id"); // boolean el.removeAttribute("class"); // Content manipulation el.setInnerContent("New content"); // Escapes HTML by default el.setInnerContent("HTML content
", { html: true }); // Parses HTML el.setInnerContent(""); // Clear content // Position manipulation el.before("Content before").after("Content after").prepend("First child").append("Last child"); // HTML content insertion el.before("before", { html: true }) .after("after", { html: true }) .prepend("first", { html: true }) .append("last", { html: true }); // Removal el.remove(); // Remove element and contents el.removeAndKeepContent(); // Remove only the element tags // Properties console.log(el.tagName); // Lowercase tag name console.log(el.namespaceURI); // Element's namespace URI console.log(el.selfClosing); // Whether element is self-closing (e.g. ) console.log(el.canHaveContent); // Whether element can contain content (false for void elements like