Changedetection.io Website Monitoring
Changedetection.io is a self-hosted tool that monitors websites and web pages for changes, sending alerts when content updates are detected. With CSS/XPath filtering, JavaScript rendering support, and multiple notification channels, it's ideal for monitoring competitor prices, government pages, job listings, and any site that doesn't offer an RSS feed.
Prerequisites
- Docker and Docker Compose installed
- At least 512 MB RAM (more if using browser rendering)
- A domain name for web access (optional)
Docker Installation
# Create project directory
mkdir -p /opt/changedetection && cd /opt/changedetection
# Create docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3'
services:
changedetection:
image: ghcr.io/dgtlmoon/changedetection.io:latest
container_name: changedetection
hostname: changedetection
volumes:
- changedetection_data:/datastore
environment:
PORT: 5000
PUID: 1000
PGID: 1000
BASE_URL: https://cd.yourdomain.com
# Optional: timezone
TZ: UTC
ports:
- "5000:5000"
restart: unless-stopped
# Optional: Playwright/Chromium for JavaScript-heavy pages
playwright-chrome:
image: browserless/chrome:latest
container_name: playwright-chrome
restart: unless-stopped
environment:
SCREEN_WIDTH: 1920
SCREEN_HEIGHT: 1024
DEFAULT_LAUNCH_ARGS: '["--window-size=1920,1080"]'
ports:
- "3000:3000"
volumes:
changedetection_data:
EOF
docker compose up -d
# Check it's running
docker compose ps
docker compose logs changedetection --tail 20
Access the UI at http://your-server:5000.
Set a Password
By default, Changedetection has no authentication. Set a password immediately:
# Option 1: Set via environment variable
# Add to docker-compose.yml environment section:
# USE_X_SETTINGS: "True"
# Option 2: In the web UI:
# Settings → General → Enable "Password" protection
Or add to your compose file:
environment:
HTTP_X_API_KEY: "your-api-key-here"
Watch Configuration
Add Your First Watch
- Enter a URL in the top input field
- Click Watch
- Configure check interval in Settings → General → Check interval
Key watch settings per URL:
- Check interval: How often to check (default: 5 minutes)
- Time between notifications: Prevent repeated alerts
- Fetch using: Direct HTTP or browser (Playwright)
- Ignore text: Regex patterns to ignore
Configuring Watches via API
# Add a new watch
curl -X POST "http://localhost:5000/api/v1/watch" \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/products",
"tag": "products,monitoring",
"title": "Product Price Monitor",
"fetch_backend": "html_requests",
"time_between_check": {"minutes": 30}
}'
# List all watches
curl "http://localhost:5000/api/v1/watch" \
-H "x-api-key: your-api-key"
# Trigger an immediate check
curl -X GET "http://localhost:5000/api/v1/watch/WATCH-UUID/history/latest" \
-H "x-api-key: your-api-key"
# Delete a watch
curl -X DELETE "http://localhost:5000/api/v1/watch/WATCH-UUID" \
-H "x-api-key: your-api-key"
CSS and XPath Filters
Filters let you monitor only specific parts of a page, ignoring timestamps, ads, and dynamic content that change constantly:
CSS Selectors
In the watch settings under Filters & Triggers:
/* Monitor only the price element */
.product-price
/* Monitor main content, ignore header and footer */
main.content
/* Multiple elements */
h1, .price, .stock-status
/* Attribute-based selection */
[data-testid="product-title"]
XPath Filters
//div[@class='price']
//table[@id='comparison-table']//tr
//span[contains(@class,'stock')]
Text Trigger Filters
# Trigger alert ONLY when specific text appears:
# Filters & Triggers → "Trigger/wait for text" → "In Stock"
# Trigger alert ONLY when text DISAPPEARS:
# "Trigger if text is NOT present" → "Out of Stock"
# Ignore lines matching these regex patterns:
# "Ignore text" → "Last updated: .*"
JSON Path Filters (for JSON endpoints)
# Monitor a JSON API endpoint, filter to a specific field
# URL: https://api.example.com/product/123
# Filter: $.price (JSONPath expression)
# OR
# Filter: .stock.available (Jinja2 expression)
Notification Channels
Go to Settings → Notifications to configure channels:
Email (SMTP)
# In Settings → Notifications → Add notification URL:
# mailto://user:[email protected][email protected]&[email protected]&port=587
# Or with multiple recipients:
# mailto://user:[email protected]:[email protected]&[email protected]
Ntfy
# Notification URL format:
ntfy://ntfy.yourdomain.com/changedetection
# With authentication:
ntfy://user:[email protected]/changedetection
Slack, Discord, Telegram
# Slack (via incoming webhook):
json://hooks.slack.com/services/T.../B.../...
# Discord:
discord://webhook-token@webhook-channel-id
# Telegram:
tgram://bot-token/chat-id
# Gotify:
gotifys://gotify.yourdomain.com/app-token
Webhook (Generic)
# Custom webhook for integration with any service:
# Settings → Notification URL:
json://your-server.com/webhook-endpoint?method=POST
# Custom body template (Jinja2):
{
"url": "{{watch_url}}",
"title": "Change detected: {{watch_title}}",
"diff_url": "{{base_url}}/diff/{{watch_uuid}}"
}
Browser Rendering with Playwright
Some pages require JavaScript execution. Connect Playwright for these:
# In docker-compose.yml - playwright service already added above
# Configure Changedetection to use it:
environment:
PLAYWRIGHT_DRIVER_URL: ws://playwright-chrome:3000
For individual watches that need browser rendering:
- Edit watch → Fetch using → Chrome/JS Playwright
- Optional: set Page load wait (extra delay) for slow pages
# Verify Playwright is working
docker compose logs playwright-chrome --tail 20
# Test Playwright connection
curl "http://localhost:3000/json"
Playwright Screenshots
Enable screenshots for visual comparison:
# Edit watch → Filters & Triggers → Visual selector
# Draw a box around the element to monitor
# Changedetection will use pixel difference for change detection
API Usage
# Get watch details and current content hash
curl "http://localhost:5000/api/v1/watch/WATCH-UUID" \
-H "x-api-key: your-api-key"
# Get diff/snapshot history
curl "http://localhost:5000/api/v1/watch/WATCH-UUID/history" \
-H "x-api-key: your-api-key"
# Get the latest snapshot content
curl "http://localhost:5000/api/v1/watch/WATCH-UUID/history/latest" \
-H "x-api-key: your-api-key"
# Bulk import watches from a list
cat > watches.txt << 'EOF'
https://example.com/product1
https://example.com/product2
https://competitor.com/pricing
EOF
while IFS= read -r url; do
curl -s -X POST "http://localhost:5000/api/v1/watch" \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d "{\"url\":\"$url\"}"
echo "Added: $url"
done < watches.txt
Export and Import
# Export all watches to OPML
curl "http://localhost:5000/api/v1/export?format=opml" \
-H "x-api-key: your-api-key" > watches-export.opml
# The UI also supports import from OPML files
# Settings → Import
Troubleshooting
Changedetection shows no changes but content clearly changed:
# The change may be in dynamic content (timestamps, ads)
# Add CSS filter to target only stable content
# Check what text the tool is capturing:
# Watch → View Latest Content (eye icon)
Pages using JavaScript not loading correctly:
# Switch watch to use Chrome/Playwright
# Verify Playwright container is running
docker compose ps playwright-chrome
# Check Playwright connection in Changedetection:
# Settings → Fetching → Playwright driver URL
# Should be: ws://playwright-chrome:3000
Too many notifications (noise):
# Configure "Recheck time" per watch to check less frequently
# Enable "Time between notifications" to limit repeat alerts
# Add text trigger to only alert on specific content changes
High memory usage:
# Limit Playwright memory usage
# playwright-chrome container environment:
# MAX_CONCURRENT_SESSIONS: "2"
# Reduce number of active watches with short check intervals
# Check for memory leaks
docker stats changedetection
Conclusion
Changedetection.io provides a practical solution for monitoring the web content your business cares about — without relying on website-provided feeds or paid monitoring services. Its CSS/XPath filtering prevents false positives from irrelevant page changes, while Playwright support handles modern JavaScript-heavy sites. Running self-hosted, it scales to hundreds of watches on modest server hardware with full control over notification routing.


