Repository: owid/owid-grapher (2025-07-01T14:44:36Z)

Issue:
Link out to archived versions of a chart from an embed
## Core problem

Currently, the Embed dialog of a chart includes archived charts when we are on a standalone data/grapher page, but not when looking at an embed of a chart inside another page.

## Proposed solution

For gdocs, we want to include the archive information inside the `linkedCharts` and `relatedCharts` objects, so we can use it properly.

This solution doesn't cover other places where we show charts: old WP pages, collections, the new search experience.

## Alternatives

An alternative we've considered is to include the archive information inside the config route. In theory, a chart could also make a cross-origin request directly to `https://archive.ourworldindata.org/versions/charts/:chartId.json` to fetch this info.



Discussion:

Patch:
diff --git a/baker/GrapherBaker.tsx b/baker/GrapherBaker.tsx
index 7c4f33ec94f..43be8b63194 100644
--- a/baker/GrapherBaker.tsx
+++ b/baker/GrapherBaker.tsx
@@ -9,7 +9,6 @@ import {
 } from "@ourworldindata/utils"
 import fs from "fs-extra"
 import {
-    ARCHIVE_BASE_URL,
     BAKED_BASE_URL,
     BAKED_GRAPHER_URL,
 } from "../settings/serverSettings.js"
@@ -32,7 +31,6 @@ import {
     DbRawChartConfig,
     DbEnrichedImage,
     ArchiveMetaInformation,
-    ArchivedPageVersion,
     ArchiveContext,
 } from "@ourworldindata/types"
 import ProgressBar from "progress"
@@ -55,17 +53,8 @@ import { getRelatedChartsForVariable } from "../db/model/Chart.js"
 import { getAllMultiDimDataPageSlugs } from "../db/model/MultiDimDataPage.js"
 import pMap from "p-map"
 import { stringify } from "safe-stable-stringify"
-import { GrapherArchivalManifest } from "./archival/archivalUtils.js"
-import { getLatestGrapherArchivedVersions } from "./archival/archivalChecksum.js"
-
-const getLatestChartArchivedVersionsIfEnabled = async (
-    knex: db.KnexReadonlyTransaction,
-    chartIds?: number[]
-): Promise<Record<number, ArchivedPageVersion>> => {
-    if (!ARCHIVE_BASE_URL) return {}
-
-    return await getLatestGrapherArchivedVersions(knex, chartIds)
-}
+import { GrapherArchivalManifest } from "../serverUtils/archivalUtils.js"
+import { getLatestChartArchivedVersionsIfEnabled } from "../db/model/archival/archivalDb.js"
 
 const renderDatapageIfApplicable = async (
     grapher: GrapherInterface,
@@ -73,10 +62,10 @@ const renderDatapageIfApplicable = async (
     knex: db.KnexReadonlyTransaction,
     {
         imageMetadataDictionary,
-        archivedChartInfo,
+        archivedChartInfoDictionary,
     }: {
         imageMetadataDictionary?: Record<string, DbEnrichedImage>
-        archivedChartInfo?: ArchiveContext
+        archivedChartInfoDictionary?: Record<number, ArchiveContext | undefined>
     } = {}
 ) => {
     const variable = await getVariableOfDatapageIfApplicable(knex, grapher)
@@ -100,7 +89,7 @@ const renderDatapageIfApplicable = async (
             useIndicatorGrapherConfigs: false,
             pageGrapher: grapher,
             imageMetadataDictionary,
-            archivedChartInfo,
+            archivedChartInfoDictionary,
         },
         knex
     )
@@ -116,19 +105,22 @@ export const renderDataPageOrGrapherPage = async (
     knex: db.KnexReadonlyTransaction,
     {
         imageMetadataDictionary,
-        archivedChartInfo,
+        archivedChartInfoDictionary,
     }: {
         imageMetadataDictionary?: Record<string, DbEnrichedImage>
-        archivedChartInfo?: ArchiveContext
+        archivedChartInfoDictionary?: Record<number, ArchiveContext | undefined>
     } = {}
 ): Promise<string> => {
     const datapage = await renderDatapageIfApplicable(grapher, false, knex, {
         imageMetadataDictionary,
-        archivedChartInfo,
+        archivedChartInfoDictionary,
     })
     if (datapage) return datapage
     return renderGrapherPage(grapher, knex, {
-        archivedChartInfo,
+        archivedChartInfo:
+            grapher.id !== undefined
+                ? archivedChartInfoDictionary?.[grapher.id]
+                : undefined,
     })
 }
 
@@ -140,7 +132,7 @@ export async function renderDataPageV2(
         useIndicatorGrapherConfigs,
         pageGrapher,
         imageMetadataDictionary = {},
-        archivedChartInfo,
+        archivedChartInfoDictionary,
     }: {
         variableId: number
         variableMetadata: OwidVariableWithSource
@@ -148,7 +140,7 @@ export async function renderDataPageV2(
         useIndicatorGrapherConfigs: boolean
         pageGrapher?: GrapherInterface
         imageMetadataDictionary?: Record<string, ImageMetadata>
-        archivedChartInfo?: ArchiveContext
+        archivedChartInfoDictionary?: Record<number, ArchiveContext | undefined>
     },
     knex: db.KnexReadonlyTransaction
 ) {
@@ -216,16 +208,25 @@ export async function renderDataPageV2(
     let imageMetadata: Record<string, ImageMetadata> = {}
     let tagToSlugMap: Record<string, string> = {}
 
+    const archivedChartInfo =
+        grapher.id !== undefined
+            ? archivedChartInfoDictionary?.[grapher.id]
+            : undefined
+
     // If we're baking to an archival page, then we want to skip a bunch of sections
     // where the links would break
     if (archivedChartInfo?.type !== "archive-page") {
         // Get the charts this variable is being used in (aka "related charts")
         // and exclude the current chart to avoid duplicates
-        datapageData.allCharts = await getRelatedChartsForVariable(
+        const allCharts = await getRelatedChartsForVariable(
             knex,
             variableId,
             grapher && "id" in grapher ? [grapher.id as number] : []
         )
+        datapageData.allCharts = allCharts.map((chart) => ({
+            ...chart,
+            archivedChartInfo: archivedChartInfoDictionary?.[chart.chartId],
+        }))
 
         datapageData.relatedResearch =
             await getRelatedResearchAndWritingForVariables(knex, [variableId])
@@ -275,17 +276,15 @@ export const renderPreviewDataPageOrGrapherPage = async (
     chartId: number,
     knex: db.KnexReadonlyTransaction
 ) => {
-    const archivedVersion = await getLatestChartArchivedVersionsIfEnabled(
-        knex,
-        [chartId]
-    )
+    const archivedChartInfoDictionary =
+        await getLatestChartArchivedVersionsIfEnabled(knex)
     const datapage = await renderDatapageIfApplicable(grapher, true, knex, {
-        archivedChartInfo: archivedVersion[chartId],
+        archivedChartInfoDictionary,
     })
     if (datapage) return datapage
 
     return renderGrapherPage(grapher, knex, {
-        archivedChartInfo: archivedVersion[chartId],
+        archivedChartInfo: archivedChartInfoDictionary[chartId],
         isPreviewing: true,
     })
 }
@@ -349,7 +348,9 @@ export const bakeSingleGrapherPageForArchival = async (
         outPathHtml,
         await renderDataPageOrGrapherPage(grapher, knex, {
             imageMetadataDictionary,
-            archivedChartInfo: archiveInfo,
+            archivedChartInfoDictionary: {
+                [grapher.id as number]: archiveInfo,
+            },
         })
     )
     const outPathManifest = `${bakedSiteDir}/grapher/${grapher.slug}.manifest.json`
@@ -375,7 +376,7 @@ const bakeGrapherPage = async (
         outPath,
         await renderDataPageOrGrapherPage(grapher, knex, {
             imageMetadataDictionary: args.imageMetadataDictionary,
-            archivedChartInfo: args.archivedChartInfo,
+            archivedChartInfoDictionary: args.archivedChartInfoDictionary,
         })
     )
 }
@@ -386,7 +387,7 @@ export interface BakeSingleGrapherChartArguments {
     bakedSiteDir: string
     slug: string
     imageMetadataDictionary: Record<string, DbEnrichedImage>
-    archivedChartInfo: ArchiveContext | undefined
+    archivedChartInfoDictionary: Record<number, ArchiveContext | undefined>
 }
 
 export const bakeSingleGrapherChart = async (
@@ -431,14 +432,14 @@ export const bakeAllChangedGrapherPagesAndDeleteRemovedGraphers = async (
 
     await fs.mkdirp(bakedSiteDir + "/grapher")
 
-    // Prefetch imageMetadata instead of each grapher page fetching
+    // Prefetch imageMetadata and archivedChartInfoDictionary instead of each grapher page fetching them
     // individually. imageMetadata is used by the google docs powering rich
     // text (including images) in data pages.
     const imageMetadataDictionary = await getAllImages(knex).then((images) =>
         _.keyBy(images, "filename")
     )
-
-    const archivedVersions = await getLatestChartArchivedVersionsIfEnabled(knex)
+    const archivedChartInfoDictionary =
+        await getLatestChartArchivedVersionsIfEnabled(knex)
 
     const jobs: BakeSingleGrapherChartArguments[] = chartsToBake.map((row) => ({
         id: row.id,
@@ -446,7 +447,7 @@ export const bakeAllChangedGrapherPagesAndDeleteRemovedGraphers = async (
         bakedSiteDir: bakedSiteDir,
         slug: row.slug,
         imageMetadataDictionary,
-        archivedChartInfo: archivedVersions[row.id] || undefined,
+        archivedChartInfoDictionary,
     }))
 
     const progressBar = new ProgressBar(
diff --git a/baker/MultiDimBaker.tsx b/baker/MultiDimBaker.tsx
index 7d54af89a4d..bc544b9e07d 100644
--- a/baker/MultiDimBaker.tsx
+++ b/baker/MultiDimBaker.tsx
@@ -39,8 +39,8 @@ import {
     getMultiDimDataPageByCatalogPath,
     getMultiDimDataPageBySlug,
 } from "../db/model/MultiDimDataPage.js"
-import { MultiDimArchivalManifest } from "./archival/archivalUtils.js"
-import { getLatestMultiDimArchivedVersions } from "./archival/archivalChecksum.js"
+import { MultiDimArchivalManifest } from "../serverUtils/archivalUtils.js"
+import { getLatestMultiDimArchivedVersions } from "../db/model/archival/archivalDb.js"
 
 const getLatestMultiDimArchivedVersionsIfEnabled = async (
     knex: db.KnexReadonlyTransaction,
diff --git a/baker/SiteBaker.tsx b/baker/SiteBaker.tsx
index 9302bdf7521..3b6ccff83dc 100644
--- a/baker/SiteBaker.tsx
+++ b/baker/SiteBaker.tsx
@@ -50,6 +50,7 @@ import {
     TombstonePageData,
     gdocUrlRegex,
     NarrativeChartInfo,
+    ArchiveContext,
 } from "@ourworldindata/utils"
 import { execWrapper } from "../db/execWrapper.js"
 import { countryProfileSpecs } from "../site/countryProfileProjects.js"
@@ -90,12 +91,20 @@ import { getNarrativeChartsInfo } from "../db/model/NarrativeChart.js"
 import { getGrapherRedirectsMap } from "./redirectsFromDb.js"
 import * as R from "remeda"
 import { getDods, getParsedDodsDictionary } from "../db/model/Dod.js"
+import {
+    getLatestChartArchivedVersionsIfEnabled,
+    getLatestMultiDimArchivedVersionsIfEnabled,
+} from "../db/model/archival/archivalDb.js"
 
 type PrefetchedAttachments = {
     donors: string[]
     linkedAuthors: LinkedAuthor[]
     linkedDocuments: Record<string, OwidGdocMinimalPostInterface>
     imageMetadata: Record<string, ImageMetadata>
+    archivedVersions: {
+        charts: Record<number, ArchiveContext | undefined>
+        multiDims: Record<number, ArchiveContext | undefined>
+    }
     linkedCharts: {
         graphers: Record<string, LinkedChart>
         explorers: Record<string, LinkedChart>
@@ -311,6 +320,24 @@ export class SiteBaker {
                 `✅ Prefetched ${Object.keys(publishedExplorersBySlug).length} explorers`
             )
 
+            console.log("Prefetching archived versions")
+            const [archivedChartVersions, archivedMultiDimVersions] =
+                await Promise.all([
+                    getLatestChartArchivedVersionsIfEnabled(knex),
+                    getLatestMultiDimArchivedVersionsIfEnabled(knex),
+                ])
+
+            const archivedVersions = {
+                charts: archivedChartVersions,
+                multiDims: archivedMultiDimVersions,
+            }
+            const archiveCount = _.sum(
+                Object.values(archivedVersions).map(
+                    (v) => Object.keys(v).length
+                )
+            )
+            console.log(`✅ Prefetched ${archiveCount} archived versions`)
+
             console.log("Prefetching charts")
             // Get all grapher links from the database so that we only prefetch the ones that are actually in use
             // 2024-06-25 before/after: 6266/2194
@@ -339,7 +366,12 @@ export class SiteBaker {
                             await makeGrapherLinkedChart(
                                 knex,
                                 chart.config,
-                                chart.slug
+                                chart.slug,
+                                {
+                                    archivedChartInfo:
+                                        archivedVersions.charts[chart.id] ||
+                                        undefined,
+                                }
                             )
                         )
                     })
@@ -347,8 +379,13 @@ export class SiteBaker {
             }
 
             const multiDims = await getAllLinkedPublishedMultiDimDataPages(knex)
-            for (const { slug, config } of multiDims) {
-                publishedCharts.push(makeMultiDimLinkedChart(config, slug))
+            for (const { id, slug, config } of multiDims) {
+                publishedCharts.push(
+                    makeMultiDimLinkedChart(config, slug, {
+                        archivedChartInfo:
+                            archivedVersions.multiDims[id] || undefined,
+                    })
+                )
             }
 
             const publishedChartsBySlug = _.keyBy(
@@ -401,6 +438,10 @@ export class SiteBaker {
                 linkedAuthors: publishedAuthors,
                 linkedDocuments: publishedGdocsDictionary,
                 imageMetadata: imageMetadataDictionary,
+                archivedVersions: {
+                    charts: archivedVersions.charts,
+                    multiDims: archivedVersions.multiDims,
+                },
                 linkedCharts: {
                     explorers: publishedExplorersBySlug,
                     graphers: publishedChartsBySlug,
@@ -446,6 +487,11 @@ export class SiteBaker {
                     this._prefetchedAttachmentsCache.imageMetadata,
                     [...imageFilenames, ...featuredImages]
                 ),
+
+                // not using _.pick on these, since they're not directly attached to the _OWID_GDOC_PROPS object anyhow
+                archivedVersions:
+                    this._prefetchedAttachmentsCache.archivedVersions,
+
                 linkedCharts: {
                     graphers: {
                         ...linkedGrapherCharts,
@@ -551,7 +597,10 @@ export class SiteBaker {
 
             // this is a no-op if the gdoc doesn't have an all-chart block
             if ("loadRelatedCharts" in publishedGdoc) {
-                await publishedGdoc.loadRelatedCharts(knex)
+                await publishedGdoc.loadRelatedCharts(
+                    knex,
+                    attachments.archivedVersions.charts
+                )
             }
 
             await publishedGdoc.validate(knex)
diff --git a/baker/archival/ArchivalBaker.ts b/baker/archival/ArchivalBaker.ts
index ff45fc8d5d7..1362ec1ba64 100644
--- a/baker/archival/ArchivalBaker.ts
+++ b/baker/archival/ArchivalBaker.ts
@@ -12,6 +12,8 @@ import {
     GrapherInterface,
     MultiDimDataPageConfigEnriched,
     UrlAndMaybeDate,
+    GrapherChecksumsObjectWithHash,
+    MultiDimChecksumsObjectWithHash,
 } from "@ourworldindata/types"
 import fs from "fs-extra"
 import path from "path"
@@ -21,7 +23,10 @@ import { getVariableData } from "../../db/model/Variable.js"
 import findProjectBaseDir from "../../settings/findBaseDir.js"
 import { bakeSingleGrapherPageForArchival } from "../GrapherBaker.js"
 import { bakeSingleMultiDimDataPageForArchival } from "../MultiDimBaker.js"
-import { hashAndCopyFile, hashAndWriteFile } from "./archivalFileUtils.js"
+import {
+    hashAndCopyFile,
+    hashAndWriteFile,
+} from "../../serverUtils/archivalFileUtils.js"
 import {
     GrapherArchivalManifest,
     MultiDimArchivalManifest,
@@ -29,16 +34,14 @@ import {
     assembleGrapherManifest,
     assembleMultiDimArchivalUrl,
     assembleMultiDimManifest,
-} from "./archivalUtils.js"
+} from "../../serverUtils/archivalUtils.js"
 import pMap from "p-map"
 import {
     getAllChartVersionsForChartId,
     getAllMultiDimVersionsForId,
     getLatestGrapherArchivedVersionsFromDb,
     getLatestMultiDimArchivedVersionsFromDb,
-    GrapherChecksumsObjectWithHash,
-    MultiDimChecksumsObjectWithHash,
-} from "./archivalChecksum.js"
+} from "../../db/model/archival/archivalDb.js"
 import { ARCHIVE_BASE_URL } from "../../settings/serverSettings.js"
 import {
     ArchivalTimestamp,
diff --git a/baker/archival/archiveChangedPages.ts b/baker/archival/archiveChangedPages.ts
index 4bf5597bf6c..feda615282e 100644
--- a/baker/archival/archiveChangedPages.ts
+++ b/baker/archival/archiveChangedPages.ts
@@ -11,11 +11,13 @@ import {
     findChangedMultiDimPages,
     getGrapherChecksumsFromDb,
     getMultiDimChecksumsFromDb,
-    GrapherChecksumsObjectWithHash,
-    MultiDimChecksumsObjectWithHash,
     insertChartVersions,
     insertMultiDimVersions,
-} from "./archivalChecksum.js"
+} from "../../db/model/archival/archivalDb.js"
+import {
+    GrapherChecksumsObjectWithHash,
+    MultiDimChecksumsObjectWithHash,
+} from "@ourworldindata/types"
 import {
     getDateForArchival,
     getAllVariableIds,
diff --git a/db/model/Chart.ts b/db/model/Chart.ts
index cef01605a31..b0fcfbdd48c 100644
--- a/db/model/Chart.ts
+++ b/db/model/Chart.ts
@@ -608,6 +608,7 @@ export const getRelatedChartsForVariable = async (
         knex,
         `-- sql
             SELECT
+                charts.id AS chartId,
                 chart_configs.slug,
                 chart_configs.full->>"$.title" AS title,
                 chart_configs.full->>"$.variantName" AS variantName,
diff --git a/db/model/Gdoc/GdocBase.ts b/db/model/Gdoc/GdocBase.ts
index f2f547b4512..8e6d3848502 100644
--- a/db/model/Gdoc/GdocBase.ts
+++ b/db/model/Gdoc/GdocBase.ts
@@ -21,6 +21,7 @@ import {
     DbInsertPostGdocLink,
     DbPlainTag,
     formatDate,
+    excludeUndefined,
 } from "@ourworldindata/utils"
 import { BAKED_GRAPHER_URL } from "../../../settings/serverSettings.js"
 import { docs as googleDocs } from "@googleapis/docs"
@@ -64,6 +65,7 @@ import {
     VariablesTableName,
     parseVariableDisplayConfig,
     joinTitleFragments,
+    ArchivedPageVersion,
 } from "@ourworldindata/types"
 import {
     getAllNarrativeChartNames,
@@ -71,6 +73,10 @@ import {
 } from "../NarrativeChart.js"
 import { indexBy } from "remeda"
 import { getDods } from "../Dod.js"
+import {
+    getLatestChartArchivedVersionsIfEnabled,
+    getLatestMultiDimArchivedVersionsIfEnabled,
+} from "../archival/archivalDb.js"
 
 export async function getLinkedIndicatorsForCharts(
     knex: db.KnexReadonlyTransaction,
@@ -698,6 +704,20 @@ export class GdocBase implements OwidGdocBaseInterface {
 
     async loadLinkedCharts(knex: db.KnexReadonlyTransaction): Promise<void> {
         const slugToIdMap = await mapSlugsToIds(knex)
+
+        const [archivedChartVersions, archivedMultiDimVersions] =
+            await Promise.all([
+                getLatestChartArchivedVersionsIfEnabled(
+                    knex,
+                    excludeUndefined(
+                        this.linkedChartSlugs.grapher.map(
+                            (slug) => slugToIdMap[slug]
+                        )
+                    )
+                ),
+                getLatestMultiDimArchivedVersionsIfEnabled(knex),
+            ])
+
         // TODO: rewrite this as a single query instead of N queries
         const linkedGrapherCharts = await Promise.all(
             this.linkedChartSlugs.grapher.map(async (originalSlug) => {
@@ -705,10 +725,15 @@ export class GdocBase implements OwidGdocBaseInterface {
                 if (chartId) {
                     const chart = await getChartConfigById(knex, chartId)
                     if (!chart) return
+
                     return makeGrapherLinkedChart(
                         knex,
                         chart.config,
-                        originalSlug
+                        originalSlug,
+                        {
+                            archivedChartInfo:
+                                archivedChartVersions[chartId] || undefined,
+                        }
                     )
                 } else {
                     const multiDim = await getMultiDimDataPageBySlug(
@@ -717,9 +742,15 @@ export class GdocBase implements OwidGdocBaseInterface {
                         { onlyPublished: false }
                     )
                     if (!multiDim) return
+
                     return makeMultiDimLinkedChart(
                         multiDim.config,
-                        originalSlug
+                        originalSlug,
+                        {
+                            archivedChartInfo:
+                                archivedMultiDimVersions[multiDim.id] ||
+                                undefined,
+                        }
                     )
                 }
             })
@@ -1089,7 +1120,8 @@ export async function getMinimalAuthorsByNames(
 export async function makeGrapherLinkedChart(
     knex: db.KnexReadonlyTransaction,
     config: GrapherInterface,
-    originalSlug: string
+    originalSlug: string,
+    { archivedChartInfo }: { archivedChartInfo?: ArchivedPageVersion } = {}
 ): Promise<LinkedChart> {
     const resolvedSlug = config.slug ?? ""
     const resolvedTitle = config.title ?? ""
@@ -1110,6 +1142,7 @@ export async function makeGrapherLinkedChart(
         thumbnail: `${GRAPHER_DYNAMIC_THUMBNAIL_URL}/${resolvedSlug}.png`,
         tags: [],
         indicatorId,
+        archivedChartInfo,
     }
 }
 
@@ -1139,7 +1172,8 @@ export function makeExplorerLinkedChart(
 
 export function makeMultiDimLinkedChart(
     config: MultiDimDataPageConfigEnriched,
-    slug: string
+    slug: string,
+    { archivedChartInfo }: { archivedChartInfo?: ArchivedPageVersion } = {}
 ): LinkedChart {
     let title = config.title.title
     const titleVariant = config.title.titleVariant
@@ -1152,5 +1186,6 @@ export function makeMultiDimLinkedChart(
         title,
         resolvedUrl: `${BAKED_GRAPHER_URL}/${slug}`,
         tags: [],
+        archivedChartInfo,
     }
 }
diff --git a/db/model/Gdoc/GdocPost.ts b/db/model/Gdoc/GdocPost.ts
index 985989c85c1..4f87a11a82c 100644
--- a/db/model/Gdoc/GdocPost.ts
+++ b/db/model/Gdoc/GdocPost.ts
@@ -9,8 +9,9 @@ import {
     RelatedChart,
     OwidGdocMinimalPostInterface,
     OwidGdocBaseInterface,
-    excludeNullish,
-} from "@ourworldindata/utils"
+    ArchiveContext,
+} from "@ourworldindata/types"
+import { excludeNullish } from "@ourworldindata/utils"
 import {
     formatCitation,
     generateStickyNav,
@@ -20,6 +21,7 @@ import { parseFaqs } from "./rawToEnriched.js"
 import { htmlToEnrichedTextBlock } from "./htmlToEnriched.js"
 import { GdocBase } from "./GdocBase.js"
 import { KnexReadonlyTransaction, knexRaw } from "../../db.js"
+import { getLatestChartArchivedVersionsIfEnabled } from "../archival/archivalDb.js"
 
 export class GdocPost extends GdocBase implements OwidGdocPostInterface {
     content!: OwidGdocPostContent
@@ -139,10 +141,14 @@ export class GdocPost extends GdocBase implements OwidGdocPostInterface {
         (knex: KnexReadonlyTransaction): Promise<void> =>
             this.loadRelatedCharts(knex)
 
-    async loadRelatedCharts(knex: KnexReadonlyTransaction): Promise<void> {
+    async loadRelatedCharts(
+        knex: KnexReadonlyTransaction,
+        archivedVersions?: Record<number, ArchiveContext | undefined>
+    ): Promise<void> {
         if (!this.tags?.length || !this.hasAllChartsBlock) return
 
         const relatedCharts = await knexRaw<{
+            chartId: number
             slug: string
             title: string
             variantName: string
@@ -151,6 +157,7 @@ export class GdocPost extends GdocBase implements OwidGdocPostInterface {
             knex,
             `-- sql
                 SELECT DISTINCT
+                    charts.id AS chartId,
                     chart_configs.slug,
                     chart_configs.full->>"$.title" AS title,
                     chart_configs.full->>"$.variantName" AS variantName,
@@ -164,7 +171,14 @@ export class GdocPost extends GdocBase implements OwidGdocPostInterface {
             `,
             [this.tags.map((tag) => tag.id)]
         )
+        archivedVersions ??= await getLatestChartArchivedVersionsIfEnabled(
+            knex,
+            relatedCharts.map((c) => c.chartId)
+        )
 
-        this.relatedCharts = relatedCharts
+        this.relatedCharts = relatedCharts.map((chart) => ({
+            ...chart,
+            archivedChartInfo: archivedVersions[chart.chartId] || undefined,
+        }))
     }
 }
diff --git a/db/model/MultiDimDataPage.ts b/db/model/MultiDimDataPage.ts
index babe5487688..18951d2ea23 100644
--- a/db/model/MultiDimDataPage.ts
+++ b/db/model/MultiDimDataPage.ts
@@ -71,13 +71,16 @@ export const getAllPublishedMultiDimDataPagesBySlug = async (
 
 export async function getAllLinkedPublishedMultiDimDataPages(
     knex: KnexReadonlyTransaction
-): Promise<{ slug: string; config: MultiDimDataPageConfigEnriched }[]> {
+): Promise<
+    { id: number; slug: string; config: MultiDimDataPageConfigEnriched }[]
+> {
     const rows = await knexRaw<
-        Pick<DbPlainMultiDimDataPage, "config"> & { slug: string }
+        Pick<DbPlainMultiDimDataPage, "config"> & { id: number; slug: string }
     >(
         knex,
         `-- sql
         SELECT
+            mddp.id as id,
             mddp.slug as slug,
             mddp.config as config
         FROM multi_dim_data_pages mddp
diff --git a/baker/archival/archivalChecksum.ts b/db/model/archival/archivalDb.ts
similarity index 88%
rename from baker/archival/archivalChecksum.ts
rename to db/model/archival/archivalDb.ts
index a858cc94ab6..5b5b0f00625 100644
--- a/baker/archival/archivalChecksum.ts
+++ b/db/model/archival/archivalDb.ts
@@ -11,52 +11,26 @@ import {
     JsonString,
     MultiDimDataPagesTableName,
     MultiDimDataPageConfigEnriched,
+    GrapherChecksumsObjectWithHash,
+    GrapherChecksums,
+    MultiDimChecksums,
+    MultiDimChecksumsObjectWithHash,
 } from "@ourworldindata/types"
-import * as db from "../../db/db.js"
+import * as db from "../../db.js"
 import { stringify } from "safe-stable-stringify"
-import { hashHex } from "../../serverUtils/hash.js"
-import {
-    GrapherArchivalManifest,
-    MultiDimArchivalManifest,
-    assembleGrapherArchivalUrl,
-    assembleMultiDimArchivalUrl,
-} from "./archivalUtils.js"
+import { hashHex } from "../../../serverUtils/hash.js"
 import {
     ArchivalTimestamp,
     convertToArchivalDateStringIfNecessary,
     getAllVariableIds,
 } from "@ourworldindata/utils"
-
-export interface GrapherChecksums {
-    chartConfigMd5: string
-    indicators: {
-        [id: string]: { metadataChecksum: string; dataChecksum: string }
-    }
-}
-
-export interface GrapherChecksumsObjectWithHash {
-    chartId: number
-    chartSlug: string
-    checksums: GrapherChecksums
-    checksumsHashed: string
-}
-
-export interface MultiDimChecksums {
-    multiDimConfigMd5: string
-    chartConfigs: {
-        [id: string]: string // chartConfigId -> MD5
-    }
-    indicators: {
-        [id: string]: { metadataChecksum: string; dataChecksum: string }
-    }
-}
-
-export interface MultiDimChecksumsObjectWithHash {
-    multiDimId: number
-    multiDimSlug: string
-    checksums: MultiDimChecksums
-    checksumsHashed: string
-}
+import {
+    assembleGrapherArchivalUrl,
+    assembleMultiDimArchivalUrl,
+    GrapherArchivalManifest,
+    MultiDimArchivalManifest,
+} from "../../../serverUtils/archivalUtils.js"
+import { ARCHIVE_BASE_URL } from "../../../settings/serverSettings.js"
 
 // Fetches checksum/hash information about all published charts from the database
 export const getGrapherChecksumsFromDb = async (
@@ -111,7 +85,12 @@ export const getGrapherChecksumsFromDb = async (
 export const getLatestGrapherArchivedVersionsFromDb = async (
     knex: db.KnexReadonlyTransaction,
     chartIds?: number[]
-) => {
+): Promise<
+    Pick<
+        DbPlainArchivedChartVersion,
+        "grapherId" | "grapherSlug" | "archivalTimestamp"
+    >[]
+> => {
     const queryBuilder = knex<DbPlainArchivedChartVersion>(
         ArchivedChartVersionsTableName
     )
@@ -130,7 +109,12 @@ export const getLatestGrapherArchivedVersionsFromDb = async (
 export const getLatestMultiDimArchivedVersionsFromDb = async (
     knex: db.KnexReadonlyTransaction,
     multiDimIds?: number[]
-) => {
+): Promise<
+    Pick<
+        DbPlainArchivedMultiDimVersion,
+        "multiDimId" | "multiDimSlug" | "archivalTimestamp"
+    >[]
+> => {
     const queryBuilder = knex<DbPlainArchivedMultiDimVersion>(
         ArchivedMultiDimVersionsTableName
     )
@@ -172,6 +156,15 @@ export const getLatestGrapherArchivedVersions = async (
     )
 }
 
+export const getLatestChartArchivedVersionsIfEnabled = async (
+    knex: db.KnexReadonlyTransaction,
+    chartIds?: number[]
+): Promise<Record<number, ArchivedPageVersion>> => {
+    if (!ARCHIVE_BASE_URL) return {}
+
+    return await getLatestGrapherArchivedVersions(knex, chartIds)
+}
+
 export const getLatestMultiDimArchivedVersions = async (
     knex: db.KnexReadonlyTransaction,
     multiDimIds?: number[]
@@ -201,7 +194,16 @@ export const getLatestMultiDimArchivedVersions = async (
     )
 }
 
-const hashGrapherChecksumsObj = (checksums: GrapherChecksums) => {
+export const getLatestMultiDimArchivedVersionsIfEnabled = async (
+    knex: db.KnexReadonlyTransaction,
+    multiDimIds?: number[]
+): Promise<Record<number, ArchivedPageVersion>> => {
+    if (!ARCHIVE_BASE_URL) return {}
+
+    return await getLatestMultiDimArchivedVersions(knex, multiDimIds)
+}
+
+const hashGrapherChecksumsObj = (checksums: GrapherChecksums): string => {
     const stringified = stringify(
         _.pick(checksums, "chartConfigMd5", "indicators")
     )
@@ -209,7 +211,7 @@ const hashGrapherChecksumsObj = (checksums: GrapherChecksums) => {
     return hashed
 }
 
-const hashMultiDimChecksumsObj = (checksums: MultiDimChecksums) => {
+const hashMultiDimChecksumsObj = (checksums: MultiDimChecksums): string => {
     const stringified = stringify(
         _.pick(checksums, "multiDimConfigMd5", "chartConfigs", "indicators")
     )
@@ -220,7 +222,7 @@ const hashMultiDimChecksumsObj = (checksums: MultiDimChecksums) => {
 const findGrapherHashesInDb = async (
     knex: db.KnexReadonlyTransaction,
     hashes: string[]
-) => {
+): Promise<Set<string>> => {
     const rows = await knex<DbPlainArchivedChartVersion>(
         ArchivedChartVersionsTableName
     )
@@ -232,7 +234,7 @@ const findGrapherHashesInDb = async (
 const findMultiDimHashesInDb = async (
     knex: db.KnexReadonlyTransaction,
     hashes: string[]
-) => {
+): Promise<Set<string>> => {
     const rows = await knex<DbPlainArchivedMultiDimVersion>(
         ArchivedMultiDimVersionsTableName
     )
@@ -243,7 +245,7 @@ const findMultiDimHashesInDb = async (
 
 export const findChangedGrapherPages = async (
     knex: db.KnexReadonlyTransaction
-) => {
+): Promise<GrapherChecksumsObjectWithHash[]> => {
     const allChartChecksums = await getGrapherChecksumsFromDb(knex)
 
     // We're gonna find the hashes of all the graphers that are already archived and up-to-date
@@ -265,7 +267,7 @@ export const findChangedGrapherPages = async (
 
 export const findChangedMultiDimPages = async (
     knex: db.KnexReadonlyTransaction
-) => {
+): Promise<MultiDimChecksumsObjectWithHash[]> => {
     const allMultiDimChecksums = await getMultiDimChecksumsFromDb(knex)
 
     // We're gonna find the hashes of all the multi-dim pages that are already archived and up-to-date
@@ -290,7 +292,7 @@ export const insertChartVersions = async (
     versions: GrapherChecksumsObjectWithHash[],
     date: ArchivalTimestamp,
     manifests: Record<number, GrapherArchivalManifest>
-) => {
+): Promise<void> => {
     const rows: DbInsertArchivedChartVersion[] = versions.map((v) => ({
         grapherId: v.chartId,
         grapherSlug: v.chartSlug,
@@ -308,7 +310,7 @@ export const insertMultiDimVersions = async (
     versions: MultiDimChecksumsObjectWithHash[],
     date: ArchivalTimestamp,
     manifests: Record<number, MultiDimArchivalManifest>
-) => {
+): Promise<void> => {
     const rows: DbInsertArchivedMultiDimVersion[] = versions.map((v) => ({
         multiDimId: v.multiDimId,
         multiDimSlug: v.multiDimSlug,
@@ -324,7 +326,9 @@ export const insertMultiDimVersions = async (
 export const getAllChartVersionsForChartId = async (
     knex: db.KnexReadonlyTransaction,
     chartId: number
-) => {
+): Promise<
+    Pick<DbPlainArchivedChartVersion, "archivalTimestamp" | "grapherSlug">[]
+> => {
     const rows = await knex<DbPlainArchivedChartVersion>(
         ArchivedChartVersionsTableName
     )
@@ -442,7 +446,9 @@ export const getMultiDimChecksumsFromDb = async (
 export const getAllMultiDimVersionsForId = async (
     knex: db.KnexReadonlyTransaction,
     multiDimId: number
-) => {
+): Promise<
+    Pick<DbPlainArchivedMultiDimVersion, "archivalTimestamp" | "multiDimSlug">[]
+> => {
     const rows = await knex<DbPlainArchivedMultiDimVersion>(
         ArchivedMultiDimVersionsTableName
     )
diff --git a/db/tsconfig.json b/db/tsconfig.json
index bf4e93e58f4..e64c47ed8d1 100644
--- a/db/tsconfig.json
+++ b/db/tsconfig.json
@@ -4,5 +4,9 @@
         "outDir": "../itsJustJavascript/db",
         "rootDir": "."
     },
-    "references": [{ "path": "../settings" }, { "path": "../site" }]
+    "references": [
+        { "path": "../settings" },
+        { "path": "../serverUtils" },
+        { "path": "../site" }
+    ]
 }
diff --git a/packages/@ourworldindata/types/src/domainTypes/Archive.ts b/packages/@ourworldindata/types/src/domainTypes/Archive.ts
index 2fe1c63f0c4..24edea7f2b1 100644
--- a/packages/@ourworldindata/types/src/domainTypes/Archive.ts
+++ b/packages/@ourworldindata/types/src/domainTypes/Archive.ts
@@ -43,3 +43,34 @@ export interface ArchiveVersions {
         url: string
     }>
 }
+
+export interface GrapherChecksums {
+    chartConfigMd5: string
+    indicators: {
+        [id: string]: { metadataChecksum: string; dataChecksum: string }
+    }
+}
+
+export interface GrapherChecksumsObjectWithHash {
+    chartId: number
+    chartSlug: string
+    checksums: GrapherChecksums
+    checksumsHashed: string
+}
+
+export interface MultiDimChecksums {
+    multiDimConfigMd5: string
+    chartConfigs: {
+        [id: string]: string // chartConfigId -> MD5
+    }
+    indicators: {
+        [id: string]: { metadataChecksum: string; dataChecksum: string }
+    }
+}
+
+export interface MultiDimChecksumsObjectWithHash {
+    multiDimId: number
+    multiDimSlug: string
+    checksums: MultiDimChecksums
+    checksumsHashed: string
+}
diff --git a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts
index 75bb7f03038..e564c73bb09 100644
--- a/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts
+++ b/packages/@ourworldindata/types/src/gdocTypes/Gdoc.ts
@@ -20,6 +20,7 @@ import { QueryParams } from "../domainTypes/Various.js"
 import { TagGraphRoot } from "../domainTypes/ContentGraph.js"
 import { DbRawImage } from "../dbTypes/Images.js"
 import { DbPlainNarrativeChart } from "../dbTypes/NarrativeCharts.js"
+import { ArchivedPageVersion } from "../domainTypes/Archive.js"
 
 export enum OwidGdocPublicationContext {
     unlisted = "unlisted",
@@ -58,6 +59,7 @@ export interface LinkedChart {
     tags: string[]
     tab?: GrapherTabOption
     indicatorId?: number // in case of a datapage
+    archivedChartInfo?: ArchivedPageVersion | undefined
 }
 
 // An object containing metadata needed for embedded narrative charts
diff --git a/packages/@ourworldindata/types/src/grapherTypes/GrapherTypes.ts b/packages/@ourworldindata/types/src/grapherTypes/GrapherTypes.ts
index a6a9b3006e8..33ae7b16467 100644
--- a/packages/@ourworldindata/types/src/grapherTypes/GrapherTypes.ts
+++ b/packages/@ourworldindata/types/src/grapherTypes/GrapherTypes.ts
@@ -16,6 +16,7 @@ import {
     GRAPHER_TAB_TYPES,
 } from "./GrapherConstants.js"
 import { OwidVariableDataMetadataDimensions } from "../OwidVariable.js"
+import { ArchiveContext } from "../domainTypes/Archive.js"
 
 export interface Box {
     x: number
@@ -87,7 +88,9 @@ export interface BasicChartInformation {
     variantName?: string | null
 }
 export interface RelatedChart extends BasicChartInformation {
+    chartId: number
     keyChartLevel?: KeyChartLevel
+    archivedChartInfo?: ArchiveContext | undefined
 }
 export enum DimensionProperty {
     y = "y",
diff --git a/packages/@ourworldindata/types/src/index.ts b/packages/@ourworldindata/types/src/index.ts
index 2dac745abea..a58a6a63a24 100644
--- a/packages/@ourworldindata/types/src/index.ts
+++ b/packages/@ourworldindata/types/src/index.ts
@@ -806,5 +806,9 @@ export {
     type ArchivedPageVersion,
     type ArchiveVersions,
     type ArchiveContext,
+    type GrapherChecksums,
+    type GrapherChecksumsObjectWithHash,
+    type MultiDimChecksums,
+    type MultiDimChecksumsObjectWithHash,
 } from "./domainTypes/Archive.js"
 export { type AdditionalGrapherDataFetchFn } from "./grapherTypes/GrapherTypes.js"
diff --git a/baker/archival/archivalFileUtils.ts b/serverUtils/archivalFileUtils.ts
similarity index 81%
rename from baker/archival/archivalFileUtils.ts
rename to serverUtils/archivalFileUtils.ts
index c21f375d9cc..0a64f8e22de 100644
--- a/baker/archival/archivalFileUtils.ts
+++ b/serverUtils/archivalFileUtils.ts
@@ -1,6 +1,6 @@
 import fs from "fs-extra"
 import path from "path"
-import { hashBase36, hashBase36FromStream } from "../../serverUtils/hash.js"
+import { hashBase36, hashBase36FromStream } from "./hash.js"
 
 const hashFile = async (file: string) => {
     const stream = await fs.createReadStream(file)
@@ -13,6 +13,8 @@ export const hashAndWriteFile = async (targetPath: string, content: string) => {
         /^(.*\/)?([^.]+\.)/,
         `$1$2${hash}.`
     )
+
+    // eslint-disable-next-line no-console
     console.log(`Writing ${targetPathWithHash}`)
     await fs.mkdirp(path.dirname(targetPathWithHash))
     await fs.writeFile(targetPathWithHash, content)
@@ -24,7 +26,9 @@ export const hashAndCopyFile = async (srcFile: string, targetDir: string) => {
     const targetFilename = path
         .basename(srcFile)
         .replace(/^(.*\/)?([^.]+\.)/, `$1$2${hash}.`)
-    const targetFile = path.resolve(targetDir, targetDir, targetFilename)
+    const targetFile = path.resolve(targetDir, targetFilename)
+
+    // eslint-disable-next-line no-console
     console.log(`Copying ${srcFile} to ${targetFile}`)
     await fs.copyFile(srcFile, targetFile)
     return targetFile
diff --git a/baker/archival/archivalUtils.ts b/serverUtils/archivalUtils.ts
similarity index 96%
rename from baker/archival/archivalUtils.ts
rename to serverUtils/archivalUtils.ts
index e8302cc8c7a..3534280715e 100644
--- a/baker/archival/archivalUtils.ts
+++ b/serverUtils/archivalUtils.ts
@@ -3,15 +3,15 @@ import {
     lazy,
     convertToArchivalDateStringIfNecessary,
 } from "@ourworldindata/utils"
-import { AssetMap } from "@ourworldindata/types"
 import {
+    AssetMap,
     GrapherChecksums,
     GrapherChecksumsObjectWithHash,
     MultiDimChecksums,
     MultiDimChecksumsObjectWithHash,
-} from "./archivalChecksum.js"
+} from "@ourworldindata/types"
 import { simpleGit } from "simple-git"
-import { ARCHIVE_BASE_URL } from "../../settings/serverSettings.js"
+import { ARCHIVE_BASE_URL } from "../settings/serverSettings.js"
 
 export interface GrapherArchivalManifest {
     assets: {
diff --git a/site/GrapherWithFallback.tsx b/site/GrapherWithFallback.tsx
index 7c37c9b40a3..c587773f614 100644
--- a/site/GrapherWithFallback.tsx
+++ b/site/GrapherWithFallback.tsx
@@ -1,8 +1,9 @@
-import { GRAPHER_PREVIEW_CLASS, GrapherInterface } from "@ourworldindata/types"
+import { GRAPHER_PREVIEW_CLASS } from "@ourworldindata/types"
 import { GrapherFigureView } from "./GrapherFigureView.js"
 import cx from "classnames"
 import GrapherImage from "./GrapherImage.js"
 import { useIntersectionObserver, useIsClient } from "usehooks-ts"
+import { GrapherProgrammaticInterface } from "@ourworldindata/grapher"
 
 export interface GrapherWithFallbackProps {
     slug?: string
@@ -11,7 +12,7 @@ export interface GrapherWithFallbackProps {
     className?: string
     id?: string
     enablePopulatingUrlParams?: boolean
-    config: Partial<GrapherInterface>
+    config: Partial<GrapherProgrammaticInterface>
     queryStr?: string
     isEmbeddedInAnOwidPage: boolean
     isEmbeddedInADataPage: boolean
diff --git a/site/blocks/RelatedCharts.tsx b/site/blocks/RelatedCharts.tsx
index 3bd18f8c98b..f398bc95436 100644
--- a/site/blocks/RelatedCharts.tsx
+++ b/site/blocks/RelatedCharts.tsx
@@ -53,7 +53,7 @@ export const RelatedCharts = ({
             enablePopulatingUrlParams={true}
             isEmbeddedInAnOwidPage={true}
             isEmbeddedInADataPage={false}
-            config={{}}
+            config={{ archivedChartInfo: activeChart.archivedChartInfo }}
             isPreviewing={isPreviewing}
         />
     )
diff --git a/site/gdocs/components/Chart.tsx b/site/gdocs/components/Chart.tsx
index d63268c0feb..aca5250c3e9 100644
--- a/site/gdocs/components/Chart.tsx
+++ b/site/gdocs/components/Chart.tsx
@@ -102,7 +102,13 @@ export default function Chart({
         return config
     }, [d.title, d.subtitle, d.controls, d.tabs, isExplorer])
 
-    const chartConfig = customizedChartConfig
+    const chartConfig = useMemo(
+        () => ({
+            archivedChartInfo: linkedChart?.archivedChartInfo,
+            ...customizedChartConfig,
+        }),
+        [linkedChart?.archivedChartInfo, customizedChartConfig]
+    )
 
     if (!linkedChart) return null
     return (


Test patch:
diff --git a/site/GrapherPage.test.tsx b/site/GrapherPage.test.tsx
index f36b05340da..15829102f4d 100755
--- a/site/GrapherPage.test.tsx
+++ b/site/GrapherPage.test.tsx
@@ -36,11 +36,13 @@ beforeAll(() => {
             title: "Chart 1",
             slug: "chart-1",
             keyChartLevel: KeyChartLevel.Middle,
+            chartId: 1,
         },
         {
             title: "Chart 2",
             slug: "chart-2",
             keyChartLevel: KeyChartLevel.Top,
+            chartId: 2,
         },
     ]
 })
diff --git a/site/blocks/RelatedCharts.test.tsx b/site/blocks/RelatedCharts.test.tsx
index f5de9394947..8d60a49679c 100755
--- a/site/blocks/RelatedCharts.test.tsx
+++ b/site/blocks/RelatedCharts.test.tsx
@@ -16,11 +16,13 @@ const charts = [
         title: "Chart 1",
         slug: "chart-1",
         keyChartLevel: KeyChartLevel.Middle,
+        chartId: 1,
     },
     {
         title: "Chart 2",
         slug: "chart-2",
         keyChartLevel: KeyChartLevel.Top,
+        chartId: 2,
     },
 ]