diff --git a/docs/.gitignore b/docs/.gitignore
index dc3d23dade298ac6ac7b28c04aa6b419826b6092..e01a1f16134866d7d667508a4489062c6f975a59 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -3,4 +3,4 @@ _build/
 devel/swh-*
 !/swh-loader.rst
 sources/
-user/software-origins/table.inc
+user/software-origins/dynamic/*.inc
diff --git a/docs/user/Makefile b/docs/user/Makefile
index 714e1221666caa7b2a0be6adc03633a4f63de93a..b471bbd3331d9f544bdb228fc5d489fb0c964ca3 100644
--- a/docs/user/Makefile
+++ b/docs/user/Makefile
@@ -10,10 +10,13 @@ BUILDDIR      = _build
 
 all: html
 
-software-origins/table.inc: ../software-origins-support.yml ../../swh/docs/generate_software_origins_list.py
-	python3 ../../swh/docs/generate_software_origins_list.py ../software-origins-support.yml software-origins/table.inc
+software-origins/dynamic/table.inc: ../software-origins-support.yml ../../swh/docs/generate_software_origins_list.py ../../swh/docs/generate_software_origin_status.py
+	python3 -m swh.docs.generate_software_origins_list ../software-origins-support.yml software-origins/dynamic/table.inc
+	# technically the following should be in its own target, but it would be
+	# overly complicated.
+	python3 -m swh.docs.generate_software_origin_status ../software-origins-support.yml software-origins/dynamic/
 
-dynamic-rst: software-origins/table.inc
+dynamic-rst: software-origins/dynamic/table.inc
 
 # Catch-all target: route all unknown targets to Sphinx using the new
 # "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
@@ -23,6 +26,6 @@ sphinx/%: Makefile dynamic-rst
 html: sphinx/html
 
 clean: sphinx/clean
-	rm -f software-origins/table.inc
+	rm -f software-origins/dynamic/*.inc
 
 .PHONY: html clean dynamic-rst
diff --git a/docs/user/software-origins/arch.rst b/docs/user/software-origins/arch.rst
index 5b1a25cf5f72763359f3e857fed0eb29e5cc390d..07d9a223555874ff1a2459a00017d80e887e2bf1 100644
--- a/docs/user/software-origins/arch.rst
+++ b/docs/user/software-origins/arch.rst
@@ -5,3 +5,5 @@ Archlinux
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/arch_status.inc
diff --git a/docs/user/software-origins/archive.rst b/docs/user/software-origins/archive.rst
index 839f7866d65d2636d23b81deb554fbb5afc8363c..1b75670c79283276f4d25f8eb34badbdb5295670 100644
--- a/docs/user/software-origins/archive.rst
+++ b/docs/user/software-origins/archive.rst
@@ -5,3 +5,5 @@ Archive loader
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/archive_status.inc
diff --git a/docs/user/software-origins/aur.rst b/docs/user/software-origins/aur.rst
index 2bbcff2aac43751e740d0913d64c64d9057554cd..ecf226c6874065d080b9239f22d90598138e545b 100644
--- a/docs/user/software-origins/aur.rst
+++ b/docs/user/software-origins/aur.rst
@@ -5,3 +5,5 @@ AUR
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/aur_status.inc
diff --git a/docs/user/software-origins/bitbucket.rst b/docs/user/software-origins/bitbucket.rst
index ebfd2db1e3095cf447a5755e79b200229db771b5..fc6dbef3bffe78fb0e4dd5a7e51a611a1f07c9a0 100644
--- a/docs/user/software-origins/bitbucket.rst
+++ b/docs/user/software-origins/bitbucket.rst
@@ -5,3 +5,5 @@ Bitbucket
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/bitbucket_status.inc
diff --git a/docs/user/software-origins/bower.rst b/docs/user/software-origins/bower.rst
index da2c4aeec260af565e9cfd71c78fb6b64016818b..19bcd8ac8974d3ef9f10f7d6ad1248556946aa7e 100644
--- a/docs/user/software-origins/bower.rst
+++ b/docs/user/software-origins/bower.rst
@@ -5,3 +5,5 @@ Bower
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/bower_status.inc
diff --git a/docs/user/software-origins/bzr.rst b/docs/user/software-origins/bzr.rst
index 63c5eccceaf8dab4cbe26ed8a4b775c8630da4da..5a66361f65f8338f5f9c283314b748e08d08b2bc 100644
--- a/docs/user/software-origins/bzr.rst
+++ b/docs/user/software-origins/bzr.rst
@@ -5,3 +5,5 @@ Bazaar
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/bzr_status.inc
diff --git a/docs/user/software-origins/cgit.rst b/docs/user/software-origins/cgit.rst
index 0fca56882baa3401593d20b4e0bd46d413c8e11b..b243d8f9ec80c70e59cada8456097cd970830b39 100644
--- a/docs/user/software-origins/cgit.rst
+++ b/docs/user/software-origins/cgit.rst
@@ -5,3 +5,5 @@ Cgit
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/cgit_status.inc
diff --git a/docs/user/software-origins/conda.rst b/docs/user/software-origins/conda.rst
index 2036c2c32b841c7bae5603d77aed4d8826d2db2b..5ac13ad6a54de75a9b7bf230842ce233a9412713 100644
--- a/docs/user/software-origins/conda.rst
+++ b/docs/user/software-origins/conda.rst
@@ -5,3 +5,5 @@ Conda
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/conda_status.inc
diff --git a/docs/user/software-origins/cpan.rst b/docs/user/software-origins/cpan.rst
index 3b87d083a446e7415055b8452036eb04a067bf7a..6b3a172c9df16122dcbb9e31a7ce52d7ba28d301 100644
--- a/docs/user/software-origins/cpan.rst
+++ b/docs/user/software-origins/cpan.rst
@@ -5,3 +5,5 @@ CPAN
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/cpan_status.inc
diff --git a/docs/user/software-origins/cran.rst b/docs/user/software-origins/cran.rst
index 007c11b1b0271593abde2877f7e717dd589302ec..6bb728a739d88465aafe24635e191b8f3563cc8e 100644
--- a/docs/user/software-origins/cran.rst
+++ b/docs/user/software-origins/cran.rst
@@ -5,3 +5,5 @@ CRAN
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/cran_status.inc
diff --git a/docs/user/software-origins/crates.rst b/docs/user/software-origins/crates.rst
index da208fbc9bed74acc16d1ddc806fcedada414f77..e8388e1f69425789a3199a1903a3309e8a200784 100644
--- a/docs/user/software-origins/crates.rst
+++ b/docs/user/software-origins/crates.rst
@@ -5,3 +5,5 @@ Crates
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/crates_status.inc
diff --git a/docs/user/software-origins/cvs.rst b/docs/user/software-origins/cvs.rst
index f4b1ceda8080e286b556dde821eead11fd16563f..16f72c99c2ca3336ce5e5997141af9b784b5b79f 100644
--- a/docs/user/software-origins/cvs.rst
+++ b/docs/user/software-origins/cvs.rst
@@ -5,3 +5,5 @@ CVS
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/cvs_status.inc
diff --git a/docs/user/software-origins/debian.rst b/docs/user/software-origins/debian.rst
index 5936a86f63ac1d2fe54fcad9d9ec0bdf56676c12..014876d953a48204c3ace7cac7c1238c0341ca42 100644
--- a/docs/user/software-origins/debian.rst
+++ b/docs/user/software-origins/debian.rst
@@ -5,3 +5,5 @@ Debian
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/debian_status.inc
diff --git a/docs/user/software-origins/deposit.rst b/docs/user/software-origins/deposit.rst
index b108f66c991ad5a6a9fb26e6fe5bf775e90d6433..870595bcafb7f031c0e117192636e91d6fe35d71 100644
--- a/docs/user/software-origins/deposit.rst
+++ b/docs/user/software-origins/deposit.rst
@@ -5,3 +5,5 @@ Deposit
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/deposit_status.inc
diff --git a/docs/user/software-origins/dynamic/.gitkeep b/docs/user/software-origins/dynamic/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/docs/user/software-origins/git.rst b/docs/user/software-origins/git.rst
index 8941b79e97886dbd7920af22d5b614d1c1fb6363..815770f3daeed6e9b695841bcedaf19f589f1fcb 100644
--- a/docs/user/software-origins/git.rst
+++ b/docs/user/software-origins/git.rst
@@ -5,3 +5,5 @@ Git
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/git_status.inc
diff --git a/docs/user/software-origins/gitea.rst b/docs/user/software-origins/gitea.rst
index 9965cb89df6966d3b9a265e46b97317c6d331c45..8f28f5794f81de368c548c6eb0f1ae122c527972 100644
--- a/docs/user/software-origins/gitea.rst
+++ b/docs/user/software-origins/gitea.rst
@@ -5,3 +5,5 @@ Gitea
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/gitea_status.inc
diff --git a/docs/user/software-origins/github.rst b/docs/user/software-origins/github.rst
index 4a6212ef007d8e8b2a41f55b96bfff97d30890a1..e5b57f38239230f3169c611135dccc42cbf60531 100644
--- a/docs/user/software-origins/github.rst
+++ b/docs/user/software-origins/github.rst
@@ -5,3 +5,5 @@ GitHub
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/github_status.inc
diff --git a/docs/user/software-origins/gitlab.rst b/docs/user/software-origins/gitlab.rst
index 1663eb744318fe2cac384edb9e57ba54ed52bc32..98a988ed1d20c00fb18584fc35c403ea7cad99db 100644
--- a/docs/user/software-origins/gitlab.rst
+++ b/docs/user/software-origins/gitlab.rst
@@ -5,3 +5,5 @@ GitLab
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/gitlab_status.inc
diff --git a/docs/user/software-origins/gnu.rst b/docs/user/software-origins/gnu.rst
index 2496e3b3424c275f5057df88be4a6e6123708ab9..5aadba670ab8fae94bf0de30c9debf5458433302 100644
--- a/docs/user/software-origins/gnu.rst
+++ b/docs/user/software-origins/gnu.rst
@@ -5,3 +5,5 @@ GNU projects
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/gnu_status.inc
diff --git a/docs/user/software-origins/gogs.rst b/docs/user/software-origins/gogs.rst
index fb67e30a6327acc848ede35f84130ebf94bedc18..cadfb9891a010ec0dec64b324cf8847b7aa9d88a 100644
--- a/docs/user/software-origins/gogs.rst
+++ b/docs/user/software-origins/gogs.rst
@@ -5,3 +5,5 @@ Gogs
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/gogs_status.inc
diff --git a/docs/user/software-origins/golang.rst b/docs/user/software-origins/golang.rst
index 93d927d743af6e9764f65946f4db05e865736cb7..65154b638784cb9c6bab2cb9da859b573f2902a8 100644
--- a/docs/user/software-origins/golang.rst
+++ b/docs/user/software-origins/golang.rst
@@ -5,3 +5,5 @@ Golang
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/golang_status.inc
diff --git a/docs/user/software-origins/hackage.rst b/docs/user/software-origins/hackage.rst
index adc66fe2367971a9fa7a7c5349b25ba09c83b57e..c59f46e52f9f73c5052f31f90e623ac0170a4ef2 100644
--- a/docs/user/software-origins/hackage.rst
+++ b/docs/user/software-origins/hackage.rst
@@ -5,3 +5,5 @@ Hackage
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/hackage_status.inc
diff --git a/docs/user/software-origins/index.rst b/docs/user/software-origins/index.rst
index c71aa04830706899a76e53d7d2a830e5b0706a1f..63cec163a7c73877902556cf1bcac94454c18857 100644
--- a/docs/user/software-origins/index.rst
+++ b/docs/user/software-origins/index.rst
@@ -15,4 +15,4 @@ or Version Control System (VCS).
 This page references all available forges/PMs/VCSs supported by |swh| loaders and/or listers,
 and links to their high-level documentation.
 
-.. include:: table.inc
+.. include:: dynamic/table.inc
diff --git a/docs/user/software-origins/launchpad.rst b/docs/user/software-origins/launchpad.rst
index 19c29b632bc638ef53edada4f77b259a0cc1fb58..ee285506f5f9b17ed8f2e3a799503b4cdacb7424 100644
--- a/docs/user/software-origins/launchpad.rst
+++ b/docs/user/software-origins/launchpad.rst
@@ -5,3 +5,5 @@ Launchpad
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/launchpad_status.inc
diff --git a/docs/user/software-origins/maven.rst b/docs/user/software-origins/maven.rst
index 4afcefb6d9b90d1dc6913cd20c0c6800a00c99c7..bd163be4c4e573f87caf09d16456ebad81e11a8e 100644
--- a/docs/user/software-origins/maven.rst
+++ b/docs/user/software-origins/maven.rst
@@ -5,3 +5,5 @@ Maven
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/maven_status.inc
diff --git a/docs/user/software-origins/mercurial.rst b/docs/user/software-origins/mercurial.rst
index 8bc4ff46d12b14419c722c1f01c7a303e94effab..e7563c07368ce03a299b5d90c2d87f42e484fb31 100644
--- a/docs/user/software-origins/mercurial.rst
+++ b/docs/user/software-origins/mercurial.rst
@@ -5,3 +5,5 @@ Mercurial
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/mercurial_status.inc
diff --git a/docs/user/software-origins/nixguix.rst b/docs/user/software-origins/nixguix.rst
index 2ede5de88027c38d5e031c284bb106e608c63101..dd042fa6865054743bb43422eeffb9ff9d2385db 100644
--- a/docs/user/software-origins/nixguix.rst
+++ b/docs/user/software-origins/nixguix.rst
@@ -5,3 +5,5 @@ Nix and Guix
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/nixguix_status.inc
diff --git a/docs/user/software-origins/npm.rst b/docs/user/software-origins/npm.rst
index 253269b856b44db52d11f399fc1728b0c56762dd..49f4a8f1f6a77e43e72e24ba95e214cbfef49316 100644
--- a/docs/user/software-origins/npm.rst
+++ b/docs/user/software-origins/npm.rst
@@ -5,3 +5,5 @@ NPM
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/npm_status.inc
diff --git a/docs/user/software-origins/opam.rst b/docs/user/software-origins/opam.rst
index 4c542e037dab22d33d0b6c504b184a2a74a78dfc..88a5834b30a908e7d449eadece8a32a0f50cb346 100644
--- a/docs/user/software-origins/opam.rst
+++ b/docs/user/software-origins/opam.rst
@@ -5,3 +5,5 @@ Opam
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/opam_status.inc
diff --git a/docs/user/software-origins/packagist.rst b/docs/user/software-origins/packagist.rst
index 43a218a6d8a0022397d4e64084f4bb4690ab6f5f..f51ece2587a92cc14a8dcb0b3b6939c0902f63e1 100644
--- a/docs/user/software-origins/packagist.rst
+++ b/docs/user/software-origins/packagist.rst
@@ -5,3 +5,5 @@ Packagist
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/packagist_status.inc
diff --git a/docs/user/software-origins/phabricator.rst b/docs/user/software-origins/phabricator.rst
index b7a646a0b0761029103f5810a0126e3a68386a71..f86258a62c20b0564650d7f0e87a00d96a450679 100644
--- a/docs/user/software-origins/phabricator.rst
+++ b/docs/user/software-origins/phabricator.rst
@@ -5,3 +5,5 @@ Phabricator
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/phabricator_status.inc
diff --git a/docs/user/software-origins/pubdev.rst b/docs/user/software-origins/pubdev.rst
index 9133b77288f43f6bf1af38d4c3f72f28d07690c4..1fc7bf757fca24bb837e1b00f3a924529696f8e4 100644
--- a/docs/user/software-origins/pubdev.rst
+++ b/docs/user/software-origins/pubdev.rst
@@ -5,3 +5,5 @@ Pub.Dev
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/pubdev_status.inc
diff --git a/docs/user/software-origins/puppet.rst b/docs/user/software-origins/puppet.rst
index 5469cac034c2e962aa3da124b4c6f02cc040ac14..e77efacfbdbfc262e44dfc58b1827c9492785d21 100644
--- a/docs/user/software-origins/puppet.rst
+++ b/docs/user/software-origins/puppet.rst
@@ -5,3 +5,5 @@ Puppet
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/puppet_status.inc
diff --git a/docs/user/software-origins/pypi.rst b/docs/user/software-origins/pypi.rst
index 54b651a66690748ffe6cbc5f7f0ad8359f2d5e8b..8fe948b0132dccee9776f79f88232a4c7ef6a1e4 100644
--- a/docs/user/software-origins/pypi.rst
+++ b/docs/user/software-origins/pypi.rst
@@ -5,3 +5,5 @@ PyPI
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/pypi_status.inc
diff --git a/docs/user/software-origins/rubygems.rst b/docs/user/software-origins/rubygems.rst
index 33dc484f121da686e1284e53c3f6d574a3a5be0e..37ff1dda049f21ffec06d667ca6ef9ffc8729a5f 100644
--- a/docs/user/software-origins/rubygems.rst
+++ b/docs/user/software-origins/rubygems.rst
@@ -5,3 +5,5 @@ RubyGems
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/rubygems_status.inc
diff --git a/docs/user/software-origins/sourceforge.rst b/docs/user/software-origins/sourceforge.rst
index dd1bc9338b4ccf282b0cf046ffd3b26a43c298d8..533e06f5a959ab4003f98cb3054b19e6e62af8e7 100644
--- a/docs/user/software-origins/sourceforge.rst
+++ b/docs/user/software-origins/sourceforge.rst
@@ -5,3 +5,5 @@ SourceForge
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/sourceforge_status.inc
diff --git a/docs/user/software-origins/svn.rst b/docs/user/software-origins/svn.rst
index d0208d7260d0d4e6401c2415172340edbcf94c1e..c36ebeac0a42036af615b4673f6e71d4938120c5 100644
--- a/docs/user/software-origins/svn.rst
+++ b/docs/user/software-origins/svn.rst
@@ -5,3 +5,5 @@ Subversion
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/svn_status.inc
diff --git a/docs/user/software-origins/tuleap.rst b/docs/user/software-origins/tuleap.rst
index 2dbf43dd6cbd5b6b995249a197c1035b0319842b..d718a5a9be5f5a176ec3b0152e7f000d34197214 100644
--- a/docs/user/software-origins/tuleap.rst
+++ b/docs/user/software-origins/tuleap.rst
@@ -5,3 +5,5 @@ Tuleap
 
 .. todo::
    This page is a work in progress.
+
+.. include:: dynamic/tuleap_status.inc
diff --git a/swh/docs/generate_software_origin_status.py b/swh/docs/generate_software_origin_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..111b6cee87b12864ab06ec99f099accc13fa0658
--- /dev/null
+++ b/swh/docs/generate_software_origin_status.py
@@ -0,0 +1,143 @@
+# Copyright (C) 2023 The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+"""Reads :file:`docs/software-origins-support.yml` and generates ReST documents
+which are included each in a forge's page to summarize its status and link
+to other documentation page"""
+
+from pathlib import Path
+import sys
+
+from .software_origins import parse
+
+
+def write_status(data, forge_id, file) -> None:
+    forge = data.forges[forge_id]
+
+    has_loader = forge["loader"]["status"] != "N/A"
+    has_lister = forge["lister"]["status"] != "N/A"
+
+    links_cell = ""
+
+    if forge.status == "dev":
+        status_cell = f"Archival for {forge.name} is currently in development:\n\n"
+    elif forge.status in ("staging", "prod"):
+        if forge.status == "staging":
+            status_cell = (
+                f"{forge.name} is currently archived only on "
+                f"the staging infrastructure:\n\n"
+            )
+        else:
+            status_cell = (
+                f"{forge.name} is currently archived by Software Heritage:\n\n"
+            )
+
+        if has_loader:
+            links_cell += f"* `Browse\N{NBSP}origins <{forge.origins}>`__\n"
+        if has_lister:
+            links_cell += f"* `See\N{NBSP}coverage <{forge.coverage}>`__\n"
+    else:
+        assert False, f"Unexpected status {forge.status!r} for {forge_id}"
+
+    if has_loader:
+        links_cell += (
+            f"* `Loader Source Code <{forge.loader.source}>`__\n"
+            f"* :mod:`Loader Developer documentation <{forge.loader.package_name}>`"
+            f"\n"
+        )
+    if has_lister:
+        links_cell += (
+            f"* `Lister Source Code <{forge.lister.source}>`__\n"
+            f"* :mod:`Lister Developer documentation <{forge.lister.package_name}>`"
+            f"\n"
+        )
+
+    loader_issue = forge["loader"].get("issue")
+    lister_issue = forge["lister"].get("issue")
+    if loader_issue and lister_issue:
+        if loader_issue == lister_issue:
+            links_cell += f"* `Tracking issue <{loader_issue}>`__\n\n"
+        else:
+            links_cell += (
+                f"* `Tracking loader issue <{loader_issue}>`__\n\n"
+                f"* `Tracking lister issue <{lister_issue}>`__\n\n"
+            )
+    elif not loader_issue and not lister_issue:
+        pass
+    elif lister_issue:
+        if has_loader:
+            status_cell += f"* `Lister Tracking issue <{lister_issue}>`__\n\n"
+        else:
+            status_cell += f"* `Tracking issue <{lister_issue}>`__\n\n"
+    elif loader_issue:
+        if has_loader:
+            status_cell += f"* `Loader Tracking issue <{loader_issue}>`__\n\n"
+        else:
+            status_cell += f"* `Tracking issue <{loader_issue}>`__\n\n"
+    else:
+        assert False, f"The impossible happened for {forge_id}"
+
+    notes = forge.get("notes")
+    if notes:
+        status_cell = f"{status_cell}\n\n{notes}"
+
+    grant_id = forge.get("grant")
+    grant = data["grants"][grant_id] if grant_id else None
+    developer_id = forge.get("developer")
+    if grant:
+        assert developer_id, f"{forge_id} has grant but no developer"
+        developer = data["developers"][developer_id]
+
+        # TODO: grammar is hard; we may want to write 'grant from the XXX Foundation'
+        # but also 'grant from XXX' sometimes.
+        # For now, only foundations provide grants.
+        assert grant.funder.endswith(" Foundation")
+
+        grant_cell = (
+            f"Developed by `{developer['name']} <{developer['url']}>`__ "
+            f"thanks to a `grant from the {grant.funder} <{grant.announcement}>`__ "
+        )
+    else:
+        assert not developer_id, f"{forge_id} has developer but no grant"
+        grant_cell = ""
+
+    file.write(status_cell)
+    file.write(links_cell)
+    file.write("\n")
+    file.write(grant_cell)
+
+    file.write("\n")
+
+
+def write_logo(data, forge_id, file) -> None:
+    forge = data.forges[forge_id]
+    file.write(
+        f".. image:: ../logos/{forge_id}.png\n"
+        f"  :alt: {forge['name']} logo\n"
+        f"  :align: right"
+        f"\n\n"
+    )
+
+
+def main(input_path: Path, output_dir: Path) -> None:
+    data = parse(input_path)
+
+    for forge_id in data.forges:
+        with (output_dir / f"{forge_id}_status.inc").open("wt") as output_file:
+            write_logo(data, forge_id, output_file)
+            write_status(data, forge_id, output_file)
+
+
+if __name__ == "__main__":
+    try:
+        (_, input_path, output_path) = sys.argv
+    except ValueError:
+        print(
+            f"Syntax: {sys.argv[0]} docs/devel/forge-support.yml "
+            f"docs/user/software-origins/",
+            sys.stderr,
+        )
+        exit(1)
+    main(Path(input_path), Path(output_path))
diff --git a/swh/docs/generate_software_origins_list.py b/swh/docs/generate_software_origins_list.py
index fcfd1bc62ebe716a6fcdeca0c3ddfad50c538f49..5c0737e0c60c20158c74dba0dd1f4b6abd24831d 100644
--- a/swh/docs/generate_software_origins_list.py
+++ b/swh/docs/generate_software_origins_list.py
@@ -11,7 +11,8 @@ import sys
 import textwrap
 
 import tabulate
-import yaml
+
+from .software_origins import parse
 
 PRELUDE = """
 .. This file was generated by swh/docs/generate_software_origins_list.py
@@ -40,108 +41,41 @@ def write_table(data, file) -> None:
             f"<user-software-origins-{forge_id}>`"
         )
 
+        links.append((f"{forge_id}-loader-source", forge.loader.source))
+        links.append((f"{forge_id}-lister-source", forge.lister.source))
+
         has_loader = forge["loader"]["status"] != "N/A"
         has_lister = forge["lister"]["status"] != "N/A"
 
-        assert has_loader or has_lister, f"{forge_id} has neither loader nor lister"
-
-        if has_loader and has_lister:
-            # We may want to do this eventually, but it never happened so far.
-            assert (
-                forge["loader"]["status"] == forge["lister"]["status"]
-            ), f"Loader and lister statuses for {forge_id} are not the same."
-
-        status = forge["loader"]["status"] if has_loader else forge["lister"]["status"]
-
-        default_loader_source = (
-            f"https://gitlab.softwareheritage.org/swh/devel/swh-loader-core/-/"
-            f"tree/master/swh/loader/package/{forge_id}"
-        )
-        default_loader_package_name = f"swh.loader.package.{forge_id}"
-        default_lister_source = (
-            f"https://gitlab.softwareheritage.org/swh/devel/swh-lister/-/"
-            f"tree/master/swh/lister/{forge_id}"
-        )
-        default_lister_package_name = f"swh.lister.{forge_id}"
-
-        loader_package_name = forge["loader"].get(
-            "package_name", default_loader_package_name
-        )
-        lister_package_name = forge["lister"].get(
-            "package_name", default_lister_package_name
-        )
-        links.append(
-            (
-                f"{forge_id}-loader-source",
-                forge["loader"].get("source", default_loader_source),
-            )
-        )
-        links.append(
-            (
-                f"{forge_id}-lister-source",
-                forge["lister"].get("source", default_lister_source),
-            )
-        )
-
-        loader_id_in_swh_web = forge["loader"].get("id_in_swh_web", forge_id)
-        lister_id_in_swh_web = forge["lister"].get("id_in_swh_web", forge_id)
-
-        if status == "dev":
+        if forge.status == "dev":
             status_cell = "In\N{NBSP}development\n\n"
-        elif status in ("staging", "prod"):
-            if status == "staging":
+        elif forge.status in ("staging", "prod"):
+            links.append((f"{forge_id}-origins", forge.origins))
+            links.append((f"{forge_id}-coverage", forge.coverage))
+            if forge.status == "staging":
                 status_cell = "In\N{NBSP}staging\n\n"
-                links.append(
-                    (
-                        f"{forge_id}-origins",
-                        "https://webapp.staging.swh.network/browse/search/"
-                        f"?with_visit=true&with_content=true"
-                        f"&visit_type={loader_id_in_swh_web}",
-                    )
-                )
-                links.append(
-                    (
-                        f"{forge_id}-coverage",
-                        "https://webapp.staging.swh.network/coverage/"
-                        f"?focus={lister_id_in_swh_web}#{lister_id_in_swh_web}",
-                    )
-                )
             else:
-                # status == "prod"
                 status_cell = "In\N{NBSP}production\n\n"
-                links.append(
-                    (
-                        f"{forge_id}-origins",
-                        f"https://archive.softwareheritage.org/browse/search/"
-                        f"?with_visit=true&with_content=true"
-                        f"&visit_type={loader_id_in_swh_web}",
-                    )
-                )
-                links.append(
-                    (
-                        f"{forge_id}-coverage",
-                        f"https://archive.softwareheritage.org/coverage/"
-                        f"?focus={lister_id_in_swh_web}#{lister_id_in_swh_web}",
-                    )
-                )
 
             if has_loader:
                 status_cell += f"`Browse\N{NBSP}origins <{forge_id}-origins_>`__\n\n"
             if has_lister:
                 status_cell += f"`See\N{NBSP}coverage <{forge_id}-coverage_>`__\n\n"
         else:
-            assert False, f"Unexpected status {status!r} for {forge_id}"
+            assert False, f"Unexpected status {forge.status!r} for {forge_id}"
 
         links_cell = ""
         if has_loader:
             links_cell += (
                 f"* `Loader Source Code <{forge_id}-loader-source_>`__\n"
-                f"* :mod:`Loader Developer documentation <{loader_package_name}>`\n"
+                f"* :mod:`Loader Developer documentation <{forge.loader.package_name}>`"
+                f"\n"
             )
         if has_lister:
             links_cell += (
                 f"* `Lister Source Code <{forge_id}-lister-source_>`__\n"
-                f"* :mod:`Lister Developer documentation <{lister_package_name}>`\n"
+                f"* :mod:`Lister Developer documentation <{forge.lister.package_name}>`"
+                f"\n"
             )
 
         loader_issue = forge["loader"].get("issue")
@@ -231,7 +165,7 @@ def write_grants(data, file) -> None:
 
 
 def main(input_path: Path, output_path: Path) -> None:
-    data = yaml.safe_load(input_path.read_text())
+    data = parse(input_path)
 
     with output_path.open("wt") as output_file:
         output_file.write(PRELUDE.format(source_yml=input_path))
diff --git a/swh/docs/software_origins.py b/swh/docs/software_origins.py
new file mode 100644
index 0000000000000000000000000000000000000000..baa5ed361902c952ac83d2a445ca177c1ac35c11
--- /dev/null
+++ b/swh/docs/software_origins.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2023 The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+"""Parser for :file:`docs/devel/software-origins-support.yml`
+"""
+
+import collections
+from pathlib import Path
+
+import yaml
+
+
+class DotDict(collections.UserDict):
+    """Like a dictionary, but can be accessed with ``.key`` in addition to
+    ``[key]``, to simplify f-string."""
+
+    def __init__(self, d):
+        d = {
+            key: DotDict(value) if isinstance(value, dict) else value
+            for (key, value) in d.items()
+        }
+        super().__setattr__("data", d)
+
+    def __getattr__(self, key):
+        try:
+            return self.data[key]
+        except KeyError as e:
+            raise AttributeError(*e.args) from None
+
+    def __setattr__(self, key, value):
+        self.data[key] = value
+
+
+def parse(input_path: Path):
+    data = DotDict(yaml.safe_load(input_path.read_text()))
+
+    for (forge_id, forge) in sorted(data["forges"].items()):
+        forge.loader.setdefault(
+            "source",
+            f"https://gitlab.softwareheritage.org/swh/devel/swh-loader-core/-/"
+            f"tree/master/swh/loader/package/{forge_id}",
+        )
+        forge.lister.setdefault(
+            "source",
+            f"https://gitlab.softwareheritage.org/swh/devel/swh-lister/-/"
+            f"tree/master/swh/lister/{forge_id}",
+        )
+
+        forge.loader.setdefault("package_name", f"swh.loader.package.{forge_id}")
+        forge.lister.setdefault("package_name", f"swh.lister.{forge_id}")
+
+        forge.loader.setdefault("id_in_swh_web", forge_id)
+        forge.lister.setdefault("id_in_swh_web", forge_id)
+
+        has_loader = forge["loader"]["status"] != "N/A"
+        has_lister = forge["lister"]["status"] != "N/A"
+
+        assert has_loader or has_lister, f"{forge_id} has neither loader nor lister"
+
+        if has_loader and has_lister:
+            # We may want to do this eventually, but it never happened so far.
+            assert (
+                forge["loader"]["status"] == forge["lister"]["status"]
+            ), f"Loader and lister statuses for {forge_id} are not the same."
+
+        forge.status = (
+            forge["loader"]["status"] if has_loader else forge["lister"]["status"]
+        )
+
+        if forge.status == "dev":
+            forge.origins = None
+            forge.coverage = None
+        elif forge.status in ("staging", "prod"):
+            if forge.status == "staging":
+                archive_base_url = "https://webapp.staging.swh.network"
+            else:
+                archive_base_url = "https://archive.softwareheritage.org"
+            forge.origins = (
+                f"{archive_base_url}/browse/search/?with_visit=true&with_content=true"
+                f"&visit_type={forge.loader.id_in_swh_web}"
+            )
+            forge.coverage = (
+                f"{archive_base_url}/coverage"
+                f"focus={forge.lister.id_in_swh_web}#{forge.lister.id_in_swh_web}"
+            )
+
+    return data