A pure TypeScript ad-blocking engine for Node.js and the browser. Block ads, trackers, and annoyances using the same filter list format trusted by millions of users worldwide.
Zero dependencies. Works everywhere JavaScript runs.
npm install openshield-jsimport { OpenShield } from 'openshield-js' const shield = new OpenShield() // Load popular filter lists await shield.loadFilterLists(['easylist', 'easyprivacy']) // Check if a network request should be blocked const result = shield.check( 'https://ads.doubleclick.net/ad.js', // request URL 'https://mysite.com/page', // page URL 'script' // resource type ) console.log(result.blocked) // true // Get CSS selectors to hide ad elements on a page const cosmetics = shield.getCosmeticsForUrl('https://example.com') console.log(cosmetics.hidingSelectors) // ['.ad-banner', '#sidebar-ad', ...]- Pure TypeScript — no native binaries, no WebAssembly, no build steps required
- Zero Dependencies — nothing to audit, nothing to break
- Cross-Platform — works in Node.js, browsers, Deno, Bun, and edge runtimes
- Dual Output — ships both ESM and CommonJS, use it anywhere
- Fast Matching — token-based reverse index for near-instant URL lookups, even with 70,000+ filters loaded
- Full Filter Syntax — supports Adblock Plus and uBlock Origin filter formats
- Network Filtering — block requests by URL pattern, domain, resource type, and party context
- Cosmetic Filtering — hide ad elements with CSS selector rules
- 8 Built-in Lists — popular community-maintained filter lists ready to use
- TypeScript First — full type definitions included out of the box
Create a new OpenShield instance.
const shield = new OpenShield() // With custom fetch (for environments without global fetch) const shield = new OpenShield({ fetch: myCustomFetchFunction })Load one or more built-in filter lists. Returns a Promise.
await shield.loadFilterLists(['easylist', 'easyprivacy'])Available built-in lists:
| List ID | Description |
|---|---|
easylist | Primary ad-blocking list |
easyprivacy | Tracker and privacy protection |
peter-lowe | Peter Lowe's ad and tracking server list |
fanboy-annoyance | Annoyances (popups, cookie notices, etc.) |
fanboy-social | Social media widget blocking |
ublock-filters | uBlock Origin community filters |
ublock-badware | Badware and malicious site protection |
ublock-privacy | Additional privacy filters |
Load a custom filter list from any URL.
await shield.loadFilterListFromUrl('https://example.com/my-filters.txt')Add raw filter rules directly as a string.
shield.addFilters(` ||ads.example.com^ ||tracker.example.net^$third-party ##.ad-banner `)Check if a network request should be blocked.
const result = shield.check( 'https://ads.example.com/banner.js', 'https://mysite.com/page', 'script' )Parameters:
url— the URL of the request to checksourceUrl— the URL of the page making the requestresourceType— one of:document,subdocument,stylesheet,script,image,font,object,xmlhttprequest,xhr,ping,media,websocket,other,popup
Returns:
{ blocked: boolean // true if the request should be blocked exception: boolean // true if an exception rule allowed it redirect: string | null // redirect URL if applicable filter: string | null // the filter rule that matched }Get cosmetic filter results for a page — CSS selectors to hide ad elements.
const cosmetics = shield.getCosmeticsForUrl('https://example.com/page')Returns:
{ hidingSelectors: string[] // CSS selectors to hide (e.g., '.ad-banner') injectStyles: string[] // CSS styles to inject scriptletInjections: string[] // Scriptlets to execute }Get engine statistics.
const stats = shield.getStats() // { // networkFilterCount: 45000, // exceptionFilterCount: 5000, // cosmeticFilterCount: 30000, // filterListCount: 2 // }Use the useOpenShield hook to hide ad elements and check URLs in your React app.
// hooks/useOpenShield.ts import { useEffect, useRef, useState } from 'react' import { OpenShield } from 'openshield-js' export function useOpenShield(filterRules: string) { const shieldRef = useRef<OpenShield | null>(null) const [ready, setReady] = useState(false) useEffect(() => { const shield = new OpenShield() shield.addFilters(filterRules) shieldRef.current = shield setReady(true) }, [filterRules]) const check = (url: string, sourceUrl: string, type: string) => { if (!shieldRef.current) return { blocked: false } return shieldRef.current.check(url, sourceUrl, type) } const getCosmetics = (url: string) => { if (!shieldRef.current) return { hidingSelectors: [], injectStyles: [], scriptletInjections: [] } return shieldRef.current.getCosmeticsForUrl(url) } return { ready, check, getCosmetics } }// components/SafeContent.tsx import { useOpenShield } from '../hooks/useOpenShield' const FILTER_RULES = ` ||ads.example.com^ ||tracker.net^$third-party ##.ad-banner ##.sponsored-content ` export function SafeContent() { const { ready, check, getCosmetics } = useOpenShield(FILTER_RULES) // Check a URL before loading it const handleLinkClick = (url: string) => { const result = check(url, window.location.href, 'document') if (result.blocked) { console.log('Blocked:', url) return } window.open(url) } // Inject cosmetic filters as CSS useEffect(() => { if (!ready) return const { hidingSelectors } = getCosmetics(window.location.href) if (hidingSelectors.length === 0) return const style = document.createElement('style') style.textContent = hidingSelectors .map(s => `${s} { display: none !important; }`) .join('\n') document.head.appendChild(style) return () => { style.remove() } }, [ready]) return <div>Your protected content here</div> }Block ad/tracker requests at the edge before they reach your users.
// middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { OpenShield } from 'openshield-js' const shield = new OpenShield() shield.addFilters(` ||ads.doubleclick.net^ ||tracker.example.com^ ||analytics.evil.com^$third-party `) export function middleware(request: NextRequest) { // Check all outgoing fetch/script URLs referenced by page const url = request.nextUrl.toString() const referer = request.headers.get('referer') || '' const result = shield.check(url, referer, 'document') if (result.blocked) { return new NextResponse('Blocked', { status: 403 }) } return NextResponse.next() } export const config = { matcher: ['/api/proxy/:path*'], }Pre-compute cosmetic filters on the server and send clean CSS to the client.
// app/layout.tsx import { OpenShield } from 'openshield-js' const shield = new OpenShield() shield.addFilters(` ##.ad-banner ##.sponsored-content ##.tracking-pixel `) export default function RootLayout({ children }: { children: React.ReactNode }) { const cosmetics = shield.getCosmeticsForUrl('https://mysite.com') const blockingCSS = cosmetics.hidingSelectors .map(s => `${s} { display: none !important; }`) .join('\n') return ( <html> <head> <style dangerouslySetInnerHTML={{ __html: blockingCSS }} /> </head> <body>{children}</body> </html> ) }import express from 'express' import { OpenShield } from 'openshield-js' const shield = new OpenShield() await shield.loadFilterLists(['easylist', 'easyprivacy']) const app = express() app.use((req, res, next) => { const result = shield.check(req.url, req.headers.referer || '', 'document') if (result.blocked) { return res.status(403).send('Blocked') } next() })import Fastify from 'fastify' import { OpenShield } from 'openshield-js' const shield = new OpenShield() await shield.loadFilterLists(['easylist']) const app = Fastify() app.addHook('onRequest', async (request, reply) => { const result = shield.check(request.url, request.headers.referer || '', 'document') if (result.blocked) { reply.code(403).send('Blocked') } })import { Hono } from 'hono' import { OpenShield } from 'openshield-js' const app = new Hono() const shield = new OpenShield() shield.addFilters(` ||ads.example.com^ ||tracker.net^$third-party `) app.use('*', async (c, next) => { const result = shield.check(c.req.url, c.req.header('referer') || '', 'document') if (result.blocked) { return c.text('Blocked', 403) } await next() }) export default app<!-- composables/useOpenShield.ts --> <script setup lang="ts"> import { ref, onMounted } from 'vue' import { OpenShield } from 'openshield-js' const shield = ref<OpenShield | null>(null) const ready = ref(false) onMounted(() => { const instance = new OpenShield() instance.addFilters(` ||ads.example.com^ ##.ad-banner ##.sponsored `) shield.value = instance ready.value = true // Apply cosmetic filters const { hidingSelectors } = instance.getCosmeticsForUrl(window.location.href) const style = document.createElement('style') style.textContent = hidingSelectors .map(s => `${s} { display: none !important; }`) .join('\n') document.head.appendChild(style) }) function checkUrl(url: string): boolean { if (!shield.value) return false return shield.value.check(url, window.location.href, 'document').blocked } </script><script lang="ts"> import { onMount } from 'svelte' import { OpenShield } from 'openshield-js' let blocked: string[] = [] onMount(async () => { const shield = new OpenShield() await shield.loadFilterLists(['easylist']) // Apply cosmetic filters const { hidingSelectors } = shield.getCosmeticsForUrl(window.location.href) const style = document.createElement('style') style.textContent = hidingSelectors .map(s => `${s} { display: none !important; }`) .join('\n') document.head.appendChild(style) // Check URLs const urls = ['https://ads.doubleclick.net/ad.js', 'https://github.com'] blocked = urls.filter(url => shield.check(url, window.location.href, 'script').blocked ) }) </script> <p>Blocked {blocked.length} requests</p>import { OpenShield } from 'openshield-js' const shield = new OpenShield() await shield.loadFilterLists(['easylist', 'peter-lowe']) function shouldBlockDomain(domain: string): boolean { return shield.check(`https://${domain}/`, '', 'document').blocked } shouldBlockDomain('ads.doubleclick.net') // true shouldBlockDomain('github.com') // falseimport { OpenShield } from 'openshield-js' const shield = new OpenShield() await shield.loadFilterLists(['easylist']) // Hide ad elements on the current page const cosmetics = shield.getCosmeticsForUrl(window.location.href) for (const selector of cosmetics.hidingSelectors) { const style = document.createElement('style') style.textContent = `${selector} { display: none !important; }` document.head.appendChild(style) }import { OpenShield } from 'openshield-js' const shield = new OpenShield() shield.addFilters(` ! Block specific ad domains ||ads.example.com^ ||tracker.example.net^ ! Block third-party tracking scripts ||analytics.evil.com^$third-party,script ! Allow a specific safe resource @@||cdn.example.com/safe-widget.js^ ! Hide ad elements on specific sites example.com##.sidebar-ad example.com##.popup-overlay ##.ad-banner `) const result = shield.check( 'https://ads.example.com/banner.js', 'https://mysite.com', 'script' ) console.log(result.blocked) // trueOpenShield supports the standard Adblock Plus / uBlock Origin filter syntax:
||example.com^ → Block all requests to example.com ||example.com^$script → Block only script requests ||example.com^$third-party → Block only when loaded from other sites @@||example.com^ → Exception: allow requests to example.com /banner\d+\.js/ → Block URLs matching a regex ##.ad-banner → Hide elements with class "ad-banner" on all sites example.com##.ad-banner → Hide only on example.com example.com#@#.ad-banner → Exception: don't hide on example.com | Option | Description |
|---|---|
$script | Match script requests |
$image | Match image requests |
$stylesheet | Match CSS requests |
$xmlhttprequest | Match XHR/fetch requests |
$third-party | Match only third-party requests |
$first-party | Match only same-origin requests |
$domain=example.com | Match only on specific source domains |
$important | Override exception rules |
$redirect=resource | Redirect instead of blocking |
| Runtime | Support |
|---|---|
| Node.js 18+ | Full |
| Deno | Full |
| Bun | Full |
| Modern Browsers | Full |
| Cloudflare Workers | Full |
| Vercel Edge | Full |
MIT