Migrating from 1.x to 2.x
This guide covers all breaking changes when upgrading from Feedcanon 1.x to 2.x. Each breaking change is detailed with specific upgrade steps and examples.
IMPORTANT
Version 2.x no longer strips tracking parameters on its own. The built-in list and the stripQueryParams option are gone. Pass a cleanUrlFn instead, for example stripTrackingParams from urlpurify.
Installation
Update your package to the latest 2.x version:
npm install feedcanon@latestIf you want to keep stripping tracking parameters, also install urlpurify:
npm install urlpurifyMigration Checklist
Use this checklist to ensure a complete migration:
- Replace
require('feedcanon')withimport(the package is ESM-only) - Upgrade Feedsmith to 3.x if your project uses it directly
- Replace the
stripQueryParamsoption offindCanonicalwithcleanUrlFn - Add
cleanUrlFnif you relied on the default tracking parameter stripping - Remove imports of
defaultStrippedParams - Pass
stripQueryParamsexplicitly when callingnormalizeUrlwithout options - Update code that reads the parsed feed in
onMatchor wrapsdefaultParserto the Feedsmith 3 types - Check stored canonical URLs that contain query strings
Breaking Changes
ESM-Only Package
The CommonJS build has been removed. The package now ships only ES modules, for both the feedcanon and feedcanon/defaults entry points.
Before (1.x)
const { findCanonical } = require('feedcanon')After (2.x)
import { findCanonical } from 'feedcanon'In a CommonJS module, load it with a dynamic import:
const { findCanonical } = await import('feedcanon')Migration Steps
- Replace
require('feedcanon')andrequire('feedcanon/defaults')withimport - In CommonJS code that cannot switch to ES modules, use
await import('feedcanon'). Node.js 20.19, 22.12 and later can also load ES modules withrequire()
Feedsmith Is a Peer Dependency
Feedcanon 1.x installed Feedsmith 2 as its own dependency. In 2.x, Feedsmith is a peer dependency and must be version 3, so Feedcanon and your code share one copy. npm, pnpm and Bun install it for you. You only need to act if your project uses Feedsmith 2 directly, because that version no longer satisfies the peer range.
Migration Steps
- If your own code uses Feedsmith 2, upgrade it to 3.x by following the Feedsmith 2.x to 3.x migration guide
stripQueryParams Option Replaced by cleanUrlFn
In 1.x, findCanonical removed over 150 tracking parameters by default, and the stripQueryParams option replaced that list. In 2.x, both are gone. Feedcanon removes no query parameters unless you pass a cleanUrlFn. It receives a URL and returns the cleaned URL. Feedcanon applies it to the initial response URL, the self URL and every candidate URL before comparing them.
cleanUrlFn is not limited to tracking parameters. It can also unwrap redirect links or apply any other rewrite. The stripTrackingParams and cleanUrl functions from urlpurify fit it directly.
Before (1.x)
import { findCanonical } from 'feedcanon'
import { defaultStrippedParams } from 'feedcanon/defaults'
// Default tracking params stripped
const url = await findCanonical('https://example.com/feed')
// Custom list
const url = await findCanonical('https://example.com/feed', {
stripQueryParams: [...defaultStrippedParams, 'my_tracking_param'],
})
// Keep all params
const url = await findCanonical('https://example.com/feed', {
stripQueryParams: [],
})After (2.x)
import { findCanonical } from 'feedcanon'
import { defaultTrackingParams, stripTrackingParams } from 'urlpurify'
// Default tracking params stripped
const url = await findCanonical('https://example.com/feed', {
cleanUrlFn: stripTrackingParams,
})
// Custom list
const url = await findCanonical('https://example.com/feed', {
cleanUrlFn: (url) => {
return stripTrackingParams(url, [...defaultTrackingParams, 'my_tracking_param'])
},
})
// Keep all params
const url = await findCanonical('https://example.com/feed')Migration Steps
- If you passed no
stripQueryParams, addcleanUrlFn: stripTrackingParamsto keep tracking parameters out of canonical URLs - If you passed a custom list, move it into a
cleanUrlFnthat callsstripTrackingParams(url, yourList) - If you passed
stripQueryParams: [], remove the option - The urlpurify list is not identical to the 1.x list. It also matches families of parameters with patterns such as
/^utm_[a-z0-9_-]+$/. Pass your own list if you need exact 1.x results
defaultStrippedParams Removed
The defaultStrippedParams export has been removed from both feedcanon and feedcanon/defaults. Use defaultTrackingParams from urlpurify instead.
Before (1.x)
import { defaultStrippedParams } from 'feedcanon'After (2.x)
import { defaultTrackingParams } from 'urlpurify'Migration Steps
- Replace
defaultStrippedParamsimports withdefaultTrackingParamsfrom urlpurify defaultTrackingParamsholds both strings and regular expressions. If you need strings only, usetrackingParamsLiterals
normalizeUrl No Longer Strips Tracking Parameters by Default
When called without options, normalizeUrl uses defaultNormalizeOptions. In 1.x, those options included the tracking parameter list. In 2.x, they do not, so tracking parameters stay in the URL. The stripQueryParams option of normalizeUrl itself still works.
Before (1.x)
import { normalizeUrl } from 'feedcanon'
normalizeUrl('https://www.example.com/feed/?utm_source=x&b=2&a=1')
// 'example.com/feed?a=1&b=2'After (2.x)
import { normalizeUrl } from 'feedcanon'
import { stripTrackingParams } from 'urlpurify'
normalizeUrl('https://www.example.com/feed/?utm_source=x&b=2&a=1')
// 'example.com/feed?a=1&b=2&utm_source=x'
normalizeUrl(stripTrackingParams('https://www.example.com/feed/?utm_source=x&b=2&a=1'))
// 'example.com/feed?a=1&b=2'Migration Steps
- Where you call
normalizeUrlwithout options, clean the URL first withstripTrackingParams, or passstripQueryParamswith your own list - If you spread
defaultNormalizeOptionsfromfeedcanon/defaults, note it no longer containsstripQueryParams
Parsed Feed Types Follow Feedsmith 3
The default parser now uses Feedsmith 3, so DefaultParserResult has the Feedsmith 3 shape. This affects the feed passed to onMatch and any custom parser that wraps defaultParser. For example, Atom text fields such as title are now objects with a value property.
Before (1.x)
import { findCanonical } from 'feedcanon'
const url = await findCanonical('https://example.com/feed', {
onMatch: ({ url, feed }) => {
if (feed.format === 'atom') {
console.log(url, feed.feed.title) // string
}
},
})After (2.x)
import { findCanonical } from 'feedcanon'
const url = await findCanonical('https://example.com/feed', {
onMatch: ({ url, feed }) => {
if (feed.format === 'atom') {
console.log(url, feed.feed.title?.value) // string
}
},
})Migration Steps
- Review code that reads
feedinonMatchor usesDefaultParserResult - Apply the changes from the Feedsmith 2.x to 3.x migration guide to that code
Query Strings Keep Their Raw Encoding
normalizeUrl now edits the query as raw key=value pairs. In 1.x, stripping, lowercasing or sorting parameters re-encoded the whole query as form data. A query like ?/feeds/atom10.xml came back as ?%2Ffeeds%2Fatom10.xml= and no longer pointed at the feed. In 2.x, the query keeps its original encoding, and empty pairs are dropped when sorting.
As a result, some canonical URLs returned by 2.x differ from the ones 1.x returned for the same feed.
Before (1.x)
import { normalizeUrl } from 'feedcanon'
normalizeUrl('https://example.com/?/feeds/atom10.xml', { sortQueryParams: true })
// 'https://example.com/?%2Ffeeds%2Fatom10.xml='After (2.x)
import { normalizeUrl } from 'feedcanon'
normalizeUrl('https://example.com/?/feeds/atom10.xml', { sortQueryParams: true })
// 'https://example.com/?/feeds/atom10.xml'Migration Steps
- If you store canonical URLs and look them up in
existsFn, check stored URLs that contain query strings - Re-run
findCanonicalfor affected feeds, or match both forms during the transition
New Features
Custom URL Cleaning
The cleanUrlFn option accepts any function that takes a URL and returns a URL. Use it to unwrap redirect links as well as strip tracking parameters:
import { findCanonical } from 'feedcanon'
import { cleanUrl } from 'urlpurify'
const url = await findCanonical('https://example.com/feed', {
cleanUrlFn: cleanUrl,
})See URL Tiers for more details.
More Feed Protocols
resolveFeedProtocol and findCanonical now also resolve podcasts://, itms://, itms-pcast://, itms-pcasts://, itms-podcast:// and itms-podcasts:// URLs to HTTP(S).