diff --git a/changelog.md b/changelog.md
index 500fe82..66fc500 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,14 @@
# Changelog
+## 2025-11-01 - 1.4.0 - feat(feed)
+Support custom feedUrl for feeds and use it as the self-link in RSS/Atom/JSON; update docs
+
+- Add optional feedUrl option to IFeedOptions (ts/classes.feed.ts).
+- Use feedUrl as the atom:link rel="self" href in RSS and as the in Atom when provided.
+- Expose feedUrl as the JSON Feed "feed_url" value (ts/classes.feed.ts).
+- PodcastFeed now uses podcastOptions.feedUrl for its atom self-link (ts/classes.podcast.ts).
+- Update README: add a Custom Feed URL section and mention feedUrl in the API docs (readme.md).
+
## 2025-10-31 - 1.3.0 - feat(parsing)
Replace rss-parser with fast-xml-parser and add native feed parser; update Smartfeed to use parseFeedXML and adjust plugins/tests
diff --git a/readme.md b/readme.md
index 88a3294..6ab121e 100644
--- a/readme.md
+++ b/readme.md
@@ -7,6 +7,7 @@
## Features ✨
- 🎯 **Full TypeScript Support** - Complete type definitions for all feed formats
+- 🌐 **Cross-Platform** - Works in Node.js, Bun, Deno, and browsers
- 📡 **Multiple Feed Formats** - RSS 2.0, Atom 1.0, JSON Feed 1.0, and Podcast RSS
- 🎙️ **Modern Podcast Support** - iTunes tags, Podcast 2.0 namespace (guid, medium, locked, persons, transcripts, funding)
- 🔒 **Built-in Validation** - Comprehensive validation for URLs, emails, domains, and timestamps
@@ -57,6 +58,30 @@ const atom = feed.exportAtomFeed();
const json = feed.exportJsonFeed();
```
+### Custom Feed URL
+
+You can specify a custom URL for your feed's self-reference instead of the default `https://${domain}/feed.xml`:
+
+```typescript
+const feed = smartfeed.createFeed({
+ domain: 'example.com',
+ title: 'My Blog',
+ description: 'Latest posts',
+ category: 'Technology',
+ company: 'Example Inc',
+ companyEmail: 'hello@example.com',
+ companyDomain: 'https://example.com',
+ feedUrl: 'https://cdn.example.com/feeds/main.xml' // Custom feed URL
+});
+
+// The feedUrl will be used in:
+// - RSS:
+// - Atom:
+// - JSON Feed: "feed_url" field
+```
+
+This is particularly useful when your feed is hosted on a CDN or different domain than your main site.
+
### Creating a Podcast Feed
```typescript
@@ -183,6 +208,7 @@ Creates a standard feed (RSS/Atom/JSON).
- `company` (string) - Company/organization name
- `companyEmail` (string) - Contact email
- `companyDomain` (string) - Company website URL (absolute)
+- `feedUrl` (string, optional) - Custom URL for the feed's self-reference (defaults to `https://${domain}/feed.xml`)
#### `createPodcastFeed(options: IPodcastFeedOptions): PodcastFeed`
diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts
index e0f2e7b..d219c92 100644
--- a/ts/00_commitinfo_data.ts
+++ b/ts/00_commitinfo_data.ts
@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartfeed',
- version: '1.3.0',
+ version: '1.4.0',
description: 'A library for creating and parsing various feed formats.'
}
diff --git a/ts/classes.feed.ts b/ts/classes.feed.ts
index 819e01e..24f6755 100644
--- a/ts/classes.feed.ts
+++ b/ts/classes.feed.ts
@@ -19,6 +19,8 @@ export interface IFeedOptions {
companyEmail: string;
/** The company website URL (must be absolute) */
companyDomain: string;
+ /** Optional: Custom URL for the feed's atom:link rel="self" (defaults to https://${domain}/feed.xml) */
+ feedUrl?: string;
}
/**
@@ -186,7 +188,8 @@ export class Feed {
rss += `${this.escapeXml(this.options.category)}\n`;
// Atom self link
- rss += `\n`;
+ const selfUrl = this.options.feedUrl || `https://${this.options.domain}/feed.xml`;
+ rss += `\n`;
// Items
for (const item of this.items) {
@@ -221,7 +224,8 @@ export class Feed {
atom += `${this.escapeXml(this.options.title)}\n`;
atom += `${this.escapeXml(this.options.description)}\n`;
atom += `\n`;
- atom += `\n`;
+ const selfUrl = this.options.feedUrl || `https://${this.options.domain}/feed.xml`;
+ atom += `\n`;
atom += `${this.formatIso8601Date(new Date())}\n`;
atom += `@push.rocks/smartfeed\n`;
atom += '\n';
@@ -265,7 +269,7 @@ export class Feed {
version: 'https://jsonfeed.org/version/1',
title: this.options.title,
home_page_url: `https://${this.options.domain}`,
- feed_url: `https://${this.options.domain}/feed.json`,
+ feed_url: this.options.feedUrl || `https://${this.options.domain}/feed.json`,
description: this.options.description,
icon: '',
favicon: '',
diff --git a/ts/classes.podcast.ts b/ts/classes.podcast.ts
index 7cbefe7..e6de743 100644
--- a/ts/classes.podcast.ts
+++ b/ts/classes.podcast.ts
@@ -369,7 +369,8 @@ export class PodcastFeed extends Feed {
rss += `${new Date().toUTCString()}\n`;
// Atom self link
- rss += `\n`;
+ const selfUrl = this.podcastOptions.feedUrl || `https://${this.podcastOptions.domain}/feed.xml`;
+ rss += `\n`;
// iTunes channel tags
rss += `${this.escapeXml(this.podcastOptions.itunesAuthor)}\n`;