Skip to main content
JSON filtering allows you to extract specific values from JSON responses, whether from APIs or JSON embedded in HTML pages. changedetection.io supports both JSONPath and jq for flexible JSON querying.

How JSON Filtering Works

When you apply a JSON filter, changedetection.io:
  1. Fetches the content from the URL
  2. Parses the JSON (either pure JSON or extracts it from HTML)
  3. Applies your filter to extract specific values
  4. Formats the output for monitoring
  5. Detects changes in the extracted data
Key Benefits:
  • Monitor API responses for changes
  • Extract nested data structures
  • Filter and transform JSON data
  • Parse JSON embedded in HTML (like LD+JSON)
  • Use logical operations to filter results

Filtering Methods

changedetection.io supports three JSON filtering syntaxes:
Prefix: json:Library: jsonpath-ngBest for: Simple to moderate JSON queriesExample:
json:$.products[*].price
json:$..author
json:$.store.book[0].title

JSONPath Syntax

Basic Selection

json:$.store.book[0].title
Access nested properties with dot notation.
json:$.products[*].name
Select all names from products array.
json:$..price
Recursive descent - finds all price fields at any level.

Array Access

json:$.items[0]
First item in array.
json:$.items[-1]
Last item in array.
json:$.items[0:3]
Slice - first three items.

Filtering

json:$.products[?(@.price < 100)]
Filter products where price is less than 100.
json:$.books[?(@.author == 'Tolkien')]
Filter books by author.

jq Syntax

Basic Selection

jq:.store.book[0].title
Access nested properties.
jq:.products[].price
Extract all prices from products array.

Filtering with select()

jq:.products[] | select(.price < 100)
Filter products under $100.
jq:.items[] | select(.stock > 0) | .name
Get names of items in stock.

Transformations

jq:.products | map(.price)
Extract array of all prices.
jq:.users | map({name, email})
Create new objects with selected fields.
jq:.prices | add
Sum all prices.

Practical Examples

Monitor API Price

{
  "product": {
    "id": 12345,
    "name": "Gaming Laptop",
    "price": 1299.99,
    "currency": "USD",
    "stock": 5
  }
}

Extract Multiple Fields

json:$.product.name
json:$.product.price
json:$.product.stock
Each filter on a new line extracts different fields.

Monitor Array of Items

{
  "products": [
    {"name": "Laptop", "price": 999},
    {"name": "Mouse", "price": 29},
    {"name": "Keyboard", "price": 79}
  ]
}

Embedded JSON in HTML

changedetection.io can automatically extract and parse JSON embedded in HTML pages, particularly useful for:
  • LD+JSON (Linked Data JSON) - Structured data for SEO
  • Application State - JavaScript state stored in script tags
  • API Data - JSON embedded for client-side rendering

LD+JSON Product Data

<html>
<head>
  <script type="application/ld+json">
  {
    "@context": "http://schema.org",
    "@type": "Product",
    "name": "Gaming Laptop",
    "offers": {
      "@type": "Offer",
      "price": "1299.99",
      "priceCurrency": "USD",
      "availability": "http://schema.org/InStock"
    }
  }
  </script>
</head>
<body>...</body>
</html>
changedetection.io automatically detects and parses <script type="application/ld+json"> tags. Just use your JSON filter as if you were querying the JSON directly.

Multiple JSON Blocks

If the HTML contains multiple JSON blocks:
<script type="application/ld+json">{...}</script>
<script type="application/ld+json">{...}</script>
changedetection.io will search through all blocks and return the first matching result.

Advanced Filtering

jq:.products[] | select(.price < 100 and .stock > 0) | .name
Finds products under $100 that are in stock.
jq:.items[] | select(.category == "electronics" or .category == "computers")
Filters items by multiple categories.
json:$.store.books[*].author.name
Navigates through nested objects.
jq:.data.users[].profile.email
Accesses deeply nested fields.
jq:.products | map(.price) | add / length
Calculates average price.
jq:.items | sort_by(.price) | .[0]
Finds cheapest item.
jq:.products | group_by(.category) | map({category: .[0].category, count: length})
Groups and counts by category.
jq:if .stock > 0 then "In Stock" else "Out of Stock" end
Returns different values based on conditions.
jq:.products[] | if .price < 50 then .name else empty end
Only outputs names of products under $50.

Output Formatting

Single Value

When your filter returns a single value:
json:$.price
Output: 29.99 (unquoted number)

Multiple Values

When your filter returns multiple values:
json:$.products[*].price
Output:
[
    99.99,
    29.99,
    149.99
]

String Values

json:$.product.name
Output: "Gaming Laptop" (includes quotes) Use jqraw: prefix to get unquoted strings:
jqraw:.product.name
Output: Gaming Laptop

Testing JSON Filters

Using Online Tools

JSONPath: jq:
  • jq play
  • Interactive jq playground

Using Browser Console

  1. Open DevTools Console (F12)
  2. Fetch and test your JSON:
    fetch('https://api.example.com/data')
      .then(r => r.json())
      .then(data => console.log(data))
    
  3. Examine structure and test paths

Common Patterns

Pattern: Extract Nested Price

json:$.offers.price
json:$..price
Use case: Product pricing from structured data.

Pattern: Monitor Stock Status

json:$.availability
Use case: Track product availability.

Pattern: Track Multiple Products

json:$.products[*].name
json:$.products[*].price
Use case: Monitor product catalog.

Pattern: Filter Price Range (jq)

jq:.products[] | select(.price > 50 and .price < 200)
Use case: Find mid-range products.

Pattern: Count Items (jq)

jq:.items | length
Use case: Track number of available items.

Common Pitfalls

Pitfall #1: Forgetting the Prefix
$.products[*].price  # Wrong - will be treated as CSS selector
Correct:
json:$.products[*].price
Pitfall #2: Path Not FoundIf your filter returns nothing:
  1. Verify JSON structure (use browser DevTools)
  2. Check for typos in property names
  3. Ensure arrays use correct syntax: [*] or []
  4. Try recursive descent: $..property_name
Pitfall #3: Wrong Filter Type
json:$.products[]  # JSONPath doesn't use [] for iteration
Use jq instead:
jq:.products[]
Or JSONPath:
json:$.products[*]
Pitfall #4: jq Not Availablejq requires compilation and may not be available on Windows.Fallback: Use JSONPath for cross-platform compatibility:
json:$.products[?(@.price < 100)].name

When to Use JSON Filtering

Good for:
  • Monitoring REST APIs
  • Tracking prices from JSON endpoints
  • Extracting structured data from HTML (LD+JSON)
  • Processing JSON with logical filtering
  • Monitoring nested data structures
Not ideal for:

JSONPath vs jq

FeatureJSONPathjq
Syntax complexitySimplerMore powerful
Platform support✅ All platforms❌ May not work on Windows
FilteringBasic [?(@.x)]Advanced select()
TransformationsLimitedExtensive (map, group, etc.)
Learning curveEasierSteeper
Output formattingAutomaticFull control
Math operations❌ No✅ Yes (add, multiply, etc.)

Real-World Examples

jq:.stargazers_count
Track repository stars from GitHub API.
jq:.open_issues_count
Monitor open issues count.
json:$.bitcoin.usd
Extract Bitcoin price in USD from crypto API.
jq:.data.quotes.USD.price
Alternative structure from different API.
jq:.products[] | select(.stock < 10) | "\(.name): \(.stock) left"
Alerts when stock is low (under 10 units).
json:$.current.temp_c
json:$.current.condition.text
Extract temperature and conditions from weather API.
jq:.jobs[] | select(.location == "Remote") | .title
Filter remote job titles from job board API.

Combining with Other Filters

You can combine JSON filtering with other features:

Ignore Text

After extracting JSON data, filter out unwanted values: Filter: json:$.products[*].name Ignore text: Discontinued

Trigger Keywords

Trigger only when specific values appear: Filter: json:$.status Trigger text: Available, In Stock

Extract Text (Regex)

Further process extracted JSON values: Filter: json:$.price Extract text: /\d+\.\d{2}/ (extract just the number)

Debugging Tips

No Output

  1. Verify URL returns valid JSON (check in browser)
  2. Test filter in online JSONPath/jq playground
  3. Check for typos in property names
  4. Try recursive descent: $..property
  5. Enable browser-based fetching if JSON is loaded dynamically

Unexpected Output Format

  1. Single value returns without array brackets
  2. Multiple values return as JSON array
  3. Use jqraw: for unquoted string output
  4. Check if your path is too broad ($..price finds ALL prices)

Parsing Errors

  1. Ensure content-type is JSON or contains valid JSON
  2. Check for BOM (Byte Order Mark) issues
  3. Verify JSON is not wrapped in extra markup
  4. For HTML-embedded JSON, ensure <script type="application/ld+json"> is correct

Build docs developers (and LLMs) love