diff --git a/config/_default/config.yml b/config/_default/config.yml
index 275f6b800354..3341de2c7c46 100644
--- a/config/_default/config.yml
+++ b/config/_default/config.yml
@@ -49,6 +49,12 @@ module:
outputFormats:
rss:
baseName: rss
+ rssupcoming:
+ mediaType: application/rss+xml
+ baseName: rss-upcoming
+ rssondemand:
+ mediaType: application/rss+xml
+ baseName: rss-ondemand
llms:
mediaType: "text/plain"
baseName: "llms"
diff --git a/content/events/_index.md b/content/events/_index.md
index 2e243367d435..5ed75dd7b77d 100644
--- a/content/events/_index.md
+++ b/content/events/_index.md
@@ -214,5 +214,5 @@ sections:
- label: On-demand
anchor: on-demand
-outputs: ["html", "rss"]
+outputs: ["html", "rss", "rssupcoming", "rssondemand"]
---
diff --git a/layouts/events/list.rss.xml b/layouts/events/list.rss.xml
new file mode 100644
index 000000000000..cc252e77d71f
--- /dev/null
+++ b/layouts/events/list.rss.xml
@@ -0,0 +1,7 @@
+{{- $feeds := partial "events/feed-events.html" . -}}
+{{- partial "events/rss-channel.html" (dict
+ "ctx" .
+ "events" $feeds.all
+ "title" "Pulumi Events"
+ "description" "All Pulumi events and on-demand recordings.")
+-}}
diff --git a/layouts/events/list.rssondemand.xml b/layouts/events/list.rssondemand.xml
new file mode 100644
index 000000000000..b76ee8043666
--- /dev/null
+++ b/layouts/events/list.rssondemand.xml
@@ -0,0 +1,7 @@
+{{- $feeds := partial "events/feed-events.html" . -}}
+{{- partial "events/rss-channel.html" (dict
+ "ctx" .
+ "events" $feeds.ondemand
+ "title" "Pulumi Events – On-demand"
+ "description" "On-demand Pulumi event and workshop recordings.")
+-}}
diff --git a/layouts/events/list.rssupcoming.xml b/layouts/events/list.rssupcoming.xml
new file mode 100644
index 000000000000..d5bec0ab193e
--- /dev/null
+++ b/layouts/events/list.rssupcoming.xml
@@ -0,0 +1,7 @@
+{{- $feeds := partial "events/feed-events.html" . -}}
+{{- partial "events/rss-channel.html" (dict
+ "ctx" .
+ "events" $feeds.upcoming
+ "title" "Pulumi Events – Upcoming"
+ "description" "Upcoming Pulumi events and workshops.")
+-}}
diff --git a/layouts/events/rss.xml b/layouts/events/rss.xml
deleted file mode 100644
index 0903c47509d1..000000000000
--- a/layouts/events/rss.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-{{ printf "" | safeHTML }}
-{{ $events := .Data.Pages }}
-
-
-
- Pulumi Events
- {{ .Site.Params.canonicalURL }}{{ .RelPermalink }}
- Feed containing upcoming Pulumi events.
- {{ .Site.LanguageCode }}
- {{ now | time.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}
- {{ range sort $events }}
- -
- {{ $eventLink := printf "%s%s" .Site.Params.canonicalURL .RelPermalink }}
-
- {{ if .Params.external }}
- {{ $eventLink = .Params.url_slug }}
- {{ end }}
- {{ $eventLink }}
- {{ .Params.title }}
- {{ .Params.meta_desc }}
- {{ .Params.sortable_date }}
- {{ $eventLink }}
-
- {{ end }}
-
-
diff --git a/layouts/partials/events/feed-events.html b/layouts/partials/events/feed-events.html
new file mode 100644
index 000000000000..b5ea038a98ca
--- /dev/null
+++ b/layouts/partials/events/feed-events.html
@@ -0,0 +1,36 @@
+{{/*
+ Events Feed Source Partial
+
+ Computes the event slices used by the events RSS feeds, mirroring the
+ upcoming/on-demand logic in partials/events/list-section.html so the feeds
+ match exactly what the events page lists. Returns a dict with three slices:
+
+ all - every listed event, newest first
+ upcoming - upcoming events, soonest first
+ ondemand - on-demand recordings, newest first
+
+ Listed = unlisted == false. An event is "upcoming" while its sortable_date
+ (plus a 24h grace window) is in the future; once past, it is "on-demand"
+ only if it has a youtube_url recording.
+*/}}
+
+{{ $nowUnix := now.UnixMilli }}
+{{ $upcoming := slice }}
+{{ $ondemand := slice }}
+
+{{ range (where site.Pages "Type" "events") }}
+ {{ if eq .Params.unlisted false }}
+ {{ $eventDateUnix := (add (.Params.sortable_date | time.AsTime).UnixMilli (duration "hour" 24).Milliseconds) }}
+ {{ if lt $nowUnix $eventDateUnix }}
+ {{ $upcoming = $upcoming | append . }}
+ {{ else if .Params.youtube_url }}
+ {{ $ondemand = $ondemand | append . }}
+ {{ end }}
+ {{ end }}
+{{ end }}
+
+{{ $upcoming = sort $upcoming "Params.sortable_date" "asc" }}
+{{ $ondemand = sort $ondemand "Params.sortable_date" "desc" }}
+{{ $all := sort (append $upcoming $ondemand) "Params.sortable_date" "desc" }}
+
+{{ return (dict "all" $all "upcoming" $upcoming "ondemand" $ondemand) }}
diff --git a/layouts/partials/events/rss-channel.html b/layouts/partials/events/rss-channel.html
new file mode 100644
index 000000000000..0dcfe813175a
--- /dev/null
+++ b/layouts/partials/events/rss-channel.html
@@ -0,0 +1,40 @@
+{{/*
+ Events RSS Channel Partial
+
+ Renders a complete RSS 2.0 document for a set of events. Shared by the
+ combined and per-section events feeds.
+
+ Expected context (dict):
+ ctx - the events list Page (for site params and the feed link)
+ events - a slice of event Pages to render as items
+ title - the channel
+ description - the channel
+*/ -}}
+{{- $ctx := .ctx -}}
+{{ printf "" | safeHTML }}
+
+
+ {{ .title }}
+ {{ $ctx.Site.Params.canonicalURL }}{{ $ctx.RelPermalink }}
+ {{ .description }}
+ {{ $ctx.Site.LanguageCode }}
+ {{ now | time.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}
+ {{ range .events }}
+ -
+ {{ $eventLink := printf "%s%s" $ctx.Site.Params.canonicalURL .RelPermalink }}
+
+ {{ if .Params.external }}
+ {{ $eventLink = .Params.url_slug }}
+ {{ end }}
+ {{ $eventLink }}
+ {{ .Params.title }}
+ {{ .Params.meta_desc }}
+ {{ (.Params.sortable_date | time.AsTime).Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}
+ {{ $eventLink }}
+
+ {{ end }}
+
+
diff --git a/layouts/partials/head.html b/layouts/partials/head.html
index 0238f9db5dad..4ff25c8597ba 100644
--- a/layouts/partials/head.html
+++ b/layouts/partials/head.html
@@ -186,6 +186,13 @@
+
+ {{ if eq .Section "events" }}
+
+
+
+ {{ end }}
+