changedetection.io

Website change detection and notification

Automation & Workflows ★ 34.5k stars Easy setup Apache-2.0

changedetection.io monitors web pages for changes and sends notifications when content updates, with optional visual diffs. It targets users who need to track price drops, restocks, or content edits. It is deployed via Docker.

Key features

  • Detects web page changes
  • Visual and text diffs
  • Many notification channels
  • Browser rendering support

Quick deploy

A starting point for self-hosting changedetection.io - check the official docs for the full set of options.

  • Image ghcr.io/dgtlmoon/changedetection.io
  • Web port 5000
  • Persist /datastore
Docker Compose
services:
  changedetection:
    image: ghcr.io/dgtlmoon/changedetection.io
    ports:
      - "5000:5000"
    volumes:
      - ./datastore:/datastore
    restart: unless-stopped
docker run
docker run -d --name changedetection \
  -p 5000:5000 \
  -v ./datastore:/datastore \
  --restart unless-stopped \
  ghcr.io/dgtlmoon/changedetection.io

Pros & cons

Strengths

  • Great for price and stock tracking
  • Lots of notifiers
  • Easy to deploy

Trade-offs

  • Focused on one task
  • JS-heavy sites need browser mode

changedetection.io replaces

Last reviewed Aug 26, 2026 · 751 words

Half of the "changedetection.io says nothing changed" complaints come from one cause: the site renders its price in JavaScript, and the default fetcher never runs JavaScript. The fix is a second container running a headless Chrome, which costs about 1 GB of RAM on top of the 256 MB the app itself wants. Decide that up front, because the shops, ticket sites, and government portals people most want to watch are exactly the ones that need it.

The compose file with the browser attached

services:
  changedetection:
    image: ghcr.io/dgtlmoon/changedetection.io
    ports:
      - "5000:5000"
    volumes:
      - ./datastore:/datastore
    environment:
      - PLAYWRIGHT_DRIVER_URL=ws://browser:3000
      - BASE_URL=https://changes.example.com
    depends_on:
      - browser
    restart: unless-stopped

  browser:
    image: dgtlmoon/sockpuppetbrowser:latest
    cap_add:
      - SYS_ADMIN
    restart: unless-stopped

Everything the app knows lives in /datastore, one directory, which makes backup a tar command. Port 5000 is the UI, and BASE_URL is what the links inside notifications point back to, so set it to the address you will actually open. With the sidecar present, each watch gets a fetcher choice: the fast plain requests fetcher, or the Chrome one for pages that need a real browser. Use the plain one wherever it works; a browser fetch is 10 to 20 times slower and the extra load is what gets you rate-limited. There is no login by default, so set a password in settings before the port leaves the LAN, or keep it behind the proxy's auth.

Filters are the difference between signal and noise

A raw watch on a product page fires on every rotating banner, "3 people viewed this" counter, and timestamp. The visual selector tool lets you click the element you care about and it writes the CSS or XPath filter for you; .price-box or //span[@id='stock-status'] is typical. Add ignore rules for text that legitimately changes (dates, view counts), and use the trigger and block text options so a watch only fires when, say, "Add to cart" appears or "Sold out" disappears. The restock and price tracking mode does this for you on shops it understands: it pulls the structured product data most e-commerce templates embed and alerts on price below a threshold or on availability flipping. That mode alone covers the reason most people install it. Test any filter with the preview before saving: a selector that matches nothing produces an empty page and one spurious "changed" alert.

Notifications go through Apprise, so ntfy is a URL

The notification field takes Apprise URLs, which means about 90 services with no plugin code. A self-hosted ntfy topic is:

ntfys://alerts:[email protected]/restocks

Discord, Telegram, Matrix, email, Slack, and a plain webhook are equally one line. The body template can include the diff, the URL, and the matched text, so the phone notification is the answer rather than a prompt to go look. For anything more elaborate (add to a spreadsheet, open a ticket) fire the webhook at n8n and do the logic there; this is the piece of the IFTTT replacement puzzle that IFTTT itself was always bad at.

Be a polite scraper or lose access

The default recheck interval is measured in hours, and that is correct. Checking a shop every minute gets your home IP blocked and, with the browser fetcher, pins a CPU core. Set intervals per watch (5 minutes for a genuine drop, daily for a documentation page), use the jitter option so checks do not land on the exact same second, and set a browser-like user agent. For sites that actively block, the app supports proxies per watch and integrates with commercial proxy providers; I use a cheap residential proxy only for the 2 watches that need it and leave everything else on the home connection. The maintainer's site also sells a hosted version, which is the sensible fallback for people who do not want to run a headless browser.

What I'd do

Deploy both containers, default every watch to the plain fetcher and switch only the ones that come back empty, set a CSS filter on every product watch before enabling it, and route everything to a single ntfy topic. Keep the total under a few dozen watches with sane intervals. Run that way it justifies its 33,345 GitHub stars: it is the rare automation that quietly saves money, and it has been doing so since 2021 with no sign of losing focus.

Compare changedetection.io

3 head-to-head comparisons.

Similar automation & workflows apps