How to write filters

This guide covers how to write and maintain your own Adblock Plus filters. Custom filters give you more control over what loads on the sites you visit, including ads, images, requests, and scripts.

Please note that the filter examples in this guide are there to teach, not to copy into your filter list.

About Adblock Plus filter lists

The following filter lists are enabled by default:

  • EasyList (+ bundled language filter list, depending on your browser's language setting)

  • ABP Anti-circumvention filter list

  • Acceptable Ads

In addition to the default filter lists, you'll find more to choose from on ABP’s settings page. You can also write your own filters. A filter is a single rule that tells your browser what to block or hide on a webpage; a filter list is a collection of those rules, maintained over time as websites change.

Types of filters

There are several types of filters, or filter rules, including:

Type

Description

Blocking filters

Applied on the network level to decide whether a request should be blocked.

Element hiding filters

Hide particular elements on a page, including element hiding with extended selectors.

Exception filters

Allowlist or override filters, used to cancel out other filters so a request loads or an element stays visible even when a blocking filter matches it.

Snippet filters

Run a small piece of JavaScript in the page, for ads that blocking and hiding filters can't tackle.

Snippet exception filters

Switch off a snippet filter.

You can create your custom filter(s) via the Adblock Plus settings page.

Blocking filters

A blocking filter stops a network request before your browser loads it. The simplest version is the address of the thing you want gone, for example:

http://example.com/ads/banner123.gif

This filter works as long as the address stays the same, but often it doesn't. The 123 in the filename could be randomized and dynamic, so on your next visit the banner arrives as banner456.gif and your filter misses it.

Wildcards can fix that. A * stands in for any run of characters:

http://example.com/ads/banner*.gif

Filters can also match any part of the request rather than the whole path, so this filter would also work. It blocks everything served from the site's ads directory:

http://example.com/ads/

Note
Shorter patterns catch more, which cuts both ways. http://example.com/ matches every address on the site, so it blocks the banners along with the articles, images, and stylesheets you wanted to see.

Matching the start or end of a request

A filter that matches anywhere in a request address will sometimes match somewhere you didn't intend. js catches http://example.com/annoyingAd.js as you'd expect, and it also catches http://example.com/js/index.html, because "js" is also in the path.

A pipe character (|) pins the filter to one end of the address.

| at the end of a filter means the address has to stop there. js| blocks http://example.com/annoyingAd.js and leaves http://example.com/js/index.html untouched.

| at the start of a filter means the address has to begin there. |http://baddomain.example/ blocks http://baddomain.example/banner.gif, but not http://gooddomain.example/analyze?http://baddomain.example, where the bad address only shows up as a query parameter.

|| at the start of a filter anchors to the beginning of the domain name, which saves you writing out every variation of a URL. A single filter, ||example.com/banner.gif, is considered best practice as it covers all three of these:

http://example.com/banner.gif
https://example.com/banner.gif
http://www.example.com/banner.gif

It still won't touch http://badexample.com/banner.gif or http://gooddomain.example/analyze?http://example.com/banner.gif.

Matching separator characters

Sometimes a filter needs to stop at whatever punctuation comes next, and you can't always predict what that punctuation will be. Say you want to block http://example.com/ and http://example.com:8000/ while leaving http://example.com.ar/ alone. A ^ stands in for one separator character:

||example.com^

A separator is any character that isn't a letter, a digit, _, -, . or %. The end of an address counts as a separator too, which is why a trailing ^ still matches when nothing follows.

Take this URL:

http://example.com:8000/foo.bar?a=12&b=%D1%82%D0%B5%D1%81%D1%82

Its separators are the : and // after the scheme, the : before the port, the / before the path, the ? opening the query string, both = signs, the &, and the end of the address.

The dots in example.com and foo.bar are not separators, which is exactly why filter ||example.com^ does not match on http://example.com.ar/.

Any of these three filters blocks that request:

||example.com^
^foo.bar^
^%D1%82%D0%B5%D1%81%D1%82^

Anchors and separators combine, and the result is the pattern you'll see most often in filter lists. ||example.com^ matches http://example.com/ and its subdomains on any scheme, and it stops at the first separator, so it never spills over to block http://example.com.ar.

Exception rules

When a filter blocks something you wanted to keep, you have a choice: narrow the blocking filter, or leave it as it is and write an exception that allowlists that network request.

@@||example.com/ads/banner.gif

An exception rule overrides any filter matching the same request. Writing one means putting @@ in front of a blocking filter pattern; everything else behaves the same way, wildcards and regular expressions included.

Example

Your custom filter adv is doing its job on network requests fetching ads, but it's also blocking http://example.com/advice.html, which contains legitimate content. Add the exception @@advice as a custom filter and that page displays as expected again, while adv continues to block everything else it matches.

Exceptions can allowlist a whole page rather than a single request. Add the $document option and Adblock Plus stops blocking on the site entirely:

@@||example.com^$document

With the above exception rule in place, all pages of http://example.com remain unblocked.

Blocking filter options and patterns

Filter options

Filter options allow you to specify when a blocking filter applies and which resource it should block. You can restrict a filter to one type of request, to particular domains, to third-party requests, and so on.

Filter options are written after the filter, indicated by $ and separated from each other by commas:

/ads/*$script,match-case

/ads/* is the filter. $script restricts the filter to script requests, and match-case makes the match case-sensitive, so an address containing /Ads/ gets through unblocked.

Options work on blocking filters and on the exception filters that override them. Element hiding filters take a domain prefix instead, covered further down.

Adblock Plus supports these options, marked by $:

Option

Applies to

Notes

script

Scripts loaded via the HTML script tag


image

Images, usually loaded via the HTML img tag


stylesheet

CSS stylesheet files


object

Content handled by browser plug-ins


xmlhttprequest

Requests made with XMLHttpRequest or the fetch() API


subdocument

Embedded pages, usually inline frames


ping

Requests from the ping attribute on a link, and from navigator.sendBeacon()


websocket

Connections opened through a WebSocket object


webrtc

Connections opened through RTCPeerConnection instances to ICE servers


font

External font files


media

Audio and video files


popup

Pages opened in a new tab or window

Pop-ups load normally unless a filter names this option

other

Request types not covered above


webbundle

Web bundle requests

ABP 4.36 and later.

Any type option takes a tilde ~ to invert it. $~image applies the filter to every request type except images.

||example.com/tracker^$~image

Exception-only options

Only work on exception filters, starting with @@. On a blocking filter they have no effect.

Option

Effect

document

Turns off all blocking for the page. Use it to allowlist an entire site or iframe

elemhide

Turns off element hiding filters for the page and leaves blocking filters running

generichide

Turns off generic element hiding filters only, so domain-specific ones keep working

genericblock

Turns off generic blocking filters only, so domain-specific ones keep working

@@||example.com^$document
@@||example.com^$generichide

Behavior options

Option

Effect

Notes

third-party

Matches only requests to a domain other than the page's

~third-party restricts the filter to first-party requests instead

match-case

Applies the filter only to requests with matching letter case

*/BannerAd.gif$match-case blocks http://example.com/BannerAd.gif and leaves http://example.com/bannerad.gif alone

domain=

Restricts the filter to the listed domains, separated by pipe characters

Exclude domains using domain=~website.com

sitekey=

Restricts the filter to pages carrying that sitekey

List several keys separated by | and the filter applies to a page presenting any of them. More here.

csp=

Injects a Content Security Policy header into the response for a page or frame the filter matches

ABP 3.1 and later. $csp=script-src 'none' blocks every script in the document, including inline ones. An exception filter carrying the same option overrides it, and an allowlisted document is left alone.

rewrite=

Redirects the request to a built-in placeholder resource instead of blocking it outright

ABP 3.5 and later. Written as rewrite=abp-resource:name, and useful when an outright block breaks the page

header=

Matches on the value of a response header

ABP 3.11 and later, Firefox only

addheader=

Injects a header into the request or response

ABP 4.32 and later. Written as addheader=type:name:value. The header name has to start with x- or be set-cookie

Redirecting requests to a blank resource

Sometimes, blocking a script outright breaks the page functionality, because the site's own code expects that file to load and falls over when nothing arrives. $rewrite= covers that case. Rather than blocking the request, Adblock Plus answers it with an empty stand-in of the right type, so the page gets a valid response and the script does nothing.

Name the resource with an abp-resource: prefix:

||example.com/tracker.js^$rewrite=abp-resource:blank-js,domain=example.com

Available in ABP 3.5 or higher:

Resource

Description

blank-text

Empty text

blank-css

An empty stylesheet

blank-js

An empty JavaScript file

blank-html

An empty HTML document

blank-mp3

A silent MP3, one tenth of a second long

blank-mp4

A blank MP4 video

1x1-transparent-gif

A 1x1 pixel transparent GIF

2x2-transparent-png

A 2x2 pixel transparent PNG

3x2-transparent-png

A 3x2 pixel transparent PNG

32x32-transparent-png

A 32x32 pixel transparent PNG

Rules for $rewrite= filters

  • The pattern has to be * or start with ||.

  • The filter has to be restricted to domains with the $domain option.

  • $third-party can't be combined with $rewrite=. $~third-party is fine.

  • A resource name Adblock Plus doesn't recognize makes the whole filter inert, and the request goes through.

Header matching

$header adds a condition to a blocking filter: the response has to carry a particular HTTP header before the filter blocks anything. The URL pattern still has to match as usual, so the header check narrows an existing filter rather than replacing what it already does. Available in Adblock Plus 3.11 and later.

Note

Header matching filters carry a security risk, so Adblock Plus accepts them from two places only: a user's own custom filters, and the ABP Anti-Circumvention Filter List, which is vetted and reviewed. They also work in Firefox only.

The option takes the form header=name=content. The header name is matched case-insensitively, and the content is matched literally, as a substring of the header's value:

$header=x-http-header=some text

The above blocks the request when the response carries an x-http-header header whose value contains "some text".

To match on a header's presence and ignore its value, drop the content along with the second equals sign:

$header=x-http-header

Commas in the content string

A comma separates one filter option from the next, so a literal comma inside the content has to be escaped as \x2c:

||example.com^$header=x-http-header=some text\x2c and more

That matches a header value containing "some text, and more".

To search for the characters \x2c literally, add another backslash:

||example.com^$header=x-http-header=some text\\x2c and more

That matches a header value containing "some text\x2c and more".

Regular expressions

Regular expressions aren't supported in the content value. For example, $header=/content/ won't behave as intended.

Adding headers to a request or response

$addheader is an option type that doesn't block a resource. Rather than stopping the request, Adblock Plus lets it through and injects a header of your choosing, either into the request on its way out or into the response coming back. Available in ABP 4.32.0 and later.

Note
For security reasons, $addheader filters are accepted from two places only: a user's own custom filters, and ABP's vetted and reviewed lists such as the ABP Anti-Circumvention Filter List and the Acceptable Ads List.

The option value has three parts separated by colons, written as type:http-header:content.

Part

Meaning

type

Either request or response. Default value is response.

http-header

The name of the header to inject. It has to be a valid HTTP header name starting with x-, or set-cookie

content

The value to give the header. Printable ASCII characters only

||example.com^$addheader=response:x-some-header:some text

The filter above injects x-some-header: some text into responses from http://example.com. Only the first two colons act as separators, so a value containing colons of its own works as written.

What $addheader can and can't do

  • It belongs on blocking filters only. An exception filter can't carry it.

  • Combine it with the document type to reach top-level page requests alone.

  • An exception filter matching the same request won't stop the injection. To switch it off, allowlist the page with an exception filter carrying $document.

  • A handful of headers are refused even with the x- prefix: x-content-type-options, x-download-options, x-frame-options, x-permitted-cross-domain-policies, x-powered-by, and x-xss-protection.

Regular expressions

When ABP's wildcards * can't express the pattern you need, you can write the whole filter as a regular expression by wrapping it in forward slashes. /banner\d+/ matches banner123 and banner321, and it leaves banners alone, because \d+ needs at least one digit.

Regex is written inside the slashes, so ^, * and | carry their regular expression meanings rather than the ABP ones described above. Mozilla's guide to regular expressions covers the syntax.

Note

For performance reasons, we recommend avoiding regular expressions if possible. Only reach for regex when a plain pattern won't do the job.

Comments

Start a line with an exclamation mark and Adblock Plus skips it entirely. Write human-readable comments after the !.

! Blocks the sticky newsletter bar
example.com##.newsletter-bar

Comments are typically placed above a filter and used to describe its function, as well as filter list information such as authorship and licensing that you may find at the top of a filter list.

When Adblock Plus downloads a filter list, it reads certain comments as metadata about the list. They apply to downloaded lists only, so they do nothing among your own custom filters. The parameters they can set are:

Comment

Purpose

! Homepage: http://example.com/

Filter list homepage or source repository

! Title: FooList

Name of filter list

! Expires: 5 days

Sets the update interval for the filter list

! Redirect: http://example.com/list.txt

Indicates that the filter list has moved to a new download or redirect address

! Version: 1234

Defines a numerical version of the filter list

Element hiding filters

Some ads cannot be tackled with blocking filters, as they're written into the page's HTML alongside the rest of the article, so there's no separate network request to block.

Element hiding filters (also known as content filters) cover this gap. Rather than acting on requests, they act on the content of the page itself. They point at an element on the page and hide it.

Rule syntax

Every hiding filter follows this structure:

<domains><separator><body>

Part

Description

domains

The sites the filter applies to, separated by commas. Leave it empty and the filter applies everywhere. Prefix a domain with ~ to exclude it

separator

Marks where the domain list ends, and sets which type of content filter this is

body

Which element the filter acts on

example.com##.ad-banner

example.com is the domain, ## makes this an element hiding filter, and .ad-banner is the CSS class for the element to hide.

Each separator and what it does:

Separator

Type

Body content

##

Element hiding

CSS selector

#?#

Extended element hiding

Extended CSS selector

#@#

Element hiding exception

CSS selector

Basic element hiding filters

Right-click the ad you want gone and click Inspect. Your browser opens its developer tools with the element selected, and you might see markup like this:

HTML
<div class="textad">
  Cheapest tofu, only here and now!
</div>
<div id="sponsorad">
  Really cheap tofu, click here!
</div>
<textad>
  Only here you get the best tofu!
</textad>

Each one can be hidden with a CSS selector in the body of the filter:

Filter

Hides

Matched by

##.textad

The first ad

Its class attribute

###sponsorad

The second ad

Its id attribute

##textad

The third ad

Its element name

A filter with no domain in front of it is called generic, and it applies on every site you visit. All three filters above are generic, which can sometimes cause false positives. The next section, on domain restrictions, covers how to narrow a filter to the sites that need it.

Restricting a filter to certain domains

Generic filters may cause false positives: the same selector that hides an ad on one site could hide legitimate content on another.

example.com##.sponsor

This filter applies on example.com and its subdomains, such as something.example.com, and leaves example.org alone. Where a page embeds iframes, the filter reaches inside any iframe whose URL matches as well.

Separate several domains with commas:

domain1.com,domain2.com,domain3.org##.sponsor

Excluding a domain

A ~ in front of a domain tells the filter to skip it.

Filter

Where it runs

~example.com##.sponsor

Applies everywhere except example.com

example.com,~foo.example.com##.sponsor

Applies on example.com, but not on foo.example.com

Matching any top-level domain

When a site runs on several top-level domains, * stands in for the TLD:

example.*##.ad

That covers example.com, example.co.uk, example.de and the rest, which saves listing each one by hand.

Note
Domain restrictions work on the domain names only, nothing else in the address.

  • Paths are ignored. example.com/news##.ad won't limit the filter to the news page. There's no way to restrict a content filter to part of a site.

  • A bare name isn't a shortcut. example##.ad doesn't cover example.com and example.org. List each one: example.com,example.org##.ad

  • Subdomains come for free. example.com##.ad already applies on news.example.com, so there's nothing extra to write.

  • * is the only wildcard, which replaces the top-level domain: example.*##.ad

Attribute selectors

Some ads don’t carry a class or ID to target. Any other attribute on the element works just as well:

Filter

Hides

##table[width="80%"]

Tables whose width attribute is exactly 80%

##div[title*="adv"]

Divs whose title contains "adv" anywhere

##div[title^="adv"]

Divs whose title starts with "adv"

##div[title$="ert"]

Divs whose title ends with "ert"

Stack conditions and the element has to match all of them:

##div[title^="adv"][title$="ert"]
##table[width="80%"][bgcolor="white"]

The first filter hides divs whose title starts with "adv" and ends with "ert". The second hides tables that are 80% wide with a white background.

Advanced selectors

Any CSS selector your browser supports can go in the body of an element hiding filter, combinators and pseudo-classes included.

##.adheader + *

This filter hides whatever element comes immediately after the element with a class adheader.

Browsers match these more slowly than simple class or ID selectors, so use a simple selector where one will do. MDN's CSS selectors reference lists what is available.

Note
This section assumes you're comfortable with CSS. Adblock Plus rejects a filter whose selector isn't valid CSS, so a typo means the filter won't save at all.

Inline CSS styles

Instead of hiding an element, a content filter can restyle it. Follow the selector with a space and a CSS declaration block, and those styling properties will be applied to the matching elements:

example.com##.banner { margin-bottom: 0 }

This is commonly used for tidying up. For example, hiding an ad may leave an empty box or a stray margin behind, so a small CSS fix can help.

Available from ABP 4.38.1 and AdBlock 6.41.1.

What you can write

Property names have to be real CSS property names, and they can't start with a double dash, so CSS custom properties are not accepted.

Values are restricted to the list below. Anything outside it is rejected for security reasons.

Global keywords

inherit, initial, none, revert, revert-layer, unset

Colors

Hex colors only, such as #009900. Also currentcolor and transparent.

Numbers

Any number, with or without a unit: 0, 10px, 50%, 1.5em.

Accepted units: cm, mm, q, in, pc, pt, px, em, ex, ch, rem, lh, rlh, vw, vh, vmin, vmax, vb, vi, svw, svh, lvw, lvh, dvw, dvh, %, fr

Keywords

absolute, all, auto, block, both, clip, collapse, contain, dashed, default, dotted, double, element, fit-content, fixed, flex, flow, flow-root, grid, groove, hidden, inline, inline-block, inline-flex, inline-grid, inline-table, inset, left, list-item, max-content, min-content, outset, pointer, relative, ridge, right, ruby, scroll, solid, static, sticky, table, table-row, text, thin, true, visible

Remove action

Hiding an element makes it invisible by setting CSS display: none;, which leaves it on the page. Scripts can still find it, and selectors that count siblings still see it. remove: true; takes the element out of the DOM instead.

It goes in the filter body as a CSS property would:

example.com##.banner { remove: true; }

Note

A filter using the remove action will take precedence over ones using other CSS properties.

Extended CSS selectors

Some ads can't be targeted with ordinary CSS, so extended selectors can be helpful in those cases:

Selector

What it matches

Alias

:-abp-has()

Elements by what they contain

:has()

:-abp-contains()

Elements by their text

:has-text()

:-abp-properties()

Elements by their computed CSS property values


:not()

Elements that don't match the inner selector


:xpath()

Elements matched by an XPath expression


Filters using :has() and :not() use the basic syntax ##.

A filter using :-abp-has(), :-abp-contains(), :-abp-properties(), :has-text() or :xpath() extended CSS selector requires the #?# separator:

example.com#?#section:-abp-has(> div > a.advertiser)

They cost more to match than ordinary CSS, so use them sparingly and scope such filters to as few domains and elements as you can.

Prefer :has() and :has-text()

Write filters using :has() and :has-text() rather than the -abp- equivalents. Both versions still work, but since :has() is standard CSS now, it’s supported in every current browser and performs better. Write it in the basic filter syntax ## and the browser matches it directly:

example.com##section:has(> div > a.advertiser)

Using :has-text() allows for better compatibility and is used in open-source filter lists such as EasyList.

example.com#?#aside.card:has-text(Ad)

Note

A filter like example.com##:has(.sponsored) could hide the entire page, because html and body contain .sponsored too. Target an element in front of the pseudo-class and scope the inner selector: example.com##div.container:has(> .sponsored)

Deciding which to use

What you're matching on

Use

Example

DOM structure, elements alone

## with :has()

example.com##div:has(> div > a.advertiser) matches a div whose direct child div has an a.advertiser as its own direct child

Text

#?# with :has-text()

example.com#?#.sidebar > span:has-text(Advertisement) hides span that are direct children of an element with the class sidebar, where the span's text contains "Advertisement"

Combination of elements & text

#?# with :-abp-has() or :-abp-contains() or :-abp-properties()

example.com#?#div.card:-abp-has(> span:-abp-contains(Sponsored)) Hides any div.card whose direct child span contains the word "Sponsored"

Elements that do not match

## with :not() or #?# when combining with other extended selectors

example.com#?#aside:not(:-abp-contains(Legitimate Content)) hides every aside apart from the one labeled “Legitimate Content”

Computed style

#?# with :-abp-properties()

example.com#?#div:-abp-properties(width:300px;height:250px;) hides divs that a stylesheet rule sizes to 300x250

XPath

#?# with :xpath()

example.com#?#:xpath(//div[@class="feed"]/article[.//span[text()="Sponsored"]]) hides articles in the feed containing a span whose text is exactly "Sponsored".

Note

When using the background-color CSS property, use rgb() notation. For example, 

example.com#?#.product-card:-abp-properties(background-color: rgb(61, 156, 79)).

Element hiding exception rules

An exception rule switches off an existing element hiding filter on defined domains. Every exception filter uses #@#:

##.textad 
example.com#@#.textad

The above filters have the same result as ~example.com##.textad on its own. Depending on the use case and the filter source, you may define which domain to exclude by using ~ or writing a separate exception rule using #@#.

The selector has to match exactly

An exception allowlists one specific filter, matched on its selector exactly.

Filter

Exception

Result

##aside.info

example.com#@#aside

Nothing allowlisted

##aside

example.com#@#aside.info

Nothing allowlisted

##aside.info

example.com#@#aside.info

Allowlisted, aside.info will be visible on example.com

Filters with styles or a remove action

When the filter carries a declaration block, the exception needs the selector alone:

##aside.info {remove: true}
example.com#@#aside.info

Snippet filters

Some ads survive both blocking and hiding, usually because scripts on the page build or restore them as it runs. Snippet filters deal with those by running a small piece of JavaScript code on the page itself.

The body names the snippet and passes any arguments it takes, after the #$# separator:

example.com#$#hide-if-contains "Sponsored" div

Snippet filters must be specific to domains. The snippet filters tutorial covers the available snippets and how to write them.

Note
Because snippets run code in the page, Adblock Plus accepts them only from two sources: a user's custom filters, and the vetted and reviewed ABP Anti-Circumvention Filter List.

Snippet exceptions

From ABP 4.45.0 and AdBlock 6.47.0, #@$# switches off a snippet on the domains defined:

example.com#@$#hide-if-contains "Sponsored" div

Snippet exception requires the same command and arguments as the snippet it disables.

Snippet exceptions don’t require a privileged list, and they don't need a domain defined, so a snippet exception with no domain applies globally.

Appendix: Implementing a sitekey on the server

To use sitekey-restricted filters, your pages have to present a public key and a signature ABP can verify. Both values are base64-encoded, and both go out in two places.

An HTTP response header:

X-Adblock-Key: abcdpublickeydcba_abcdsignaturedcba

And the document's root element:

<html data-adblockkey="abcdpublickeydcba_abcdsignaturedcba">

Both are needed, since different browsers read different ones.

The key

Create an RSA private key and a DER representation of the matching public key. 2048 bits is the minimum, 4096 is better.

The signature

For each request, build a string from three values joined with NUL characters (\0): the request path including its query string, the host, and the requesting browser's user agent.

/index.html?q=foo\0www.example.com\0Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0

Sign that string with your private key, using RSA with ⟨digest⟩, and base64-encode the result.

Because the signed data includes the request URI and the user agent, the signature is different for every request, so your server has to compute it per response rather than storing one.