diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx
index 316b04dc0f1dc0671fc5bdc7b397a9bb918cd1e3..ff315557fa8bb89315ac9b9ded17712158a33193 100644
--- a/webapp/src/App.tsx
+++ b/webapp/src/App.tsx
@@ -12,6 +12,7 @@ import StaffGraph from "./pages/StaffGraph";
 import { FilterSelection } from "./Schema";
 import SubOrganisation from "./pages/SubOrganisation";
 import ParentOrganisation from "./pages/ParentOrganisation";
+import ECProjects from "./pages/ECProjects";
 
 
 function App(): ReactElement {
@@ -35,6 +36,7 @@ function App(): ReactElement {
           <Route path="/charging" element={<ChargingStructurePage />} />
           <Route path="/suborganisations" element={<SubOrganisation filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} />
           <Route path="/parentorganisation" element={<ParentOrganisation filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} />
+          <Route path="/ec-projects" element={<ECProjects filterSelection={filterSelection} setFilterSelection={setFilterSelection} />} />
           <Route path="*" element={<Landing />} />
         </Routes>
         <GeantFooter />
diff --git a/webapp/src/Schema.tsx b/webapp/src/Schema.tsx
index 449752b6489019b3dc9f6585ed7cb5c51c34e913..2161fa843b5e4223b20076b571fad6e01b0eb8a2 100644
--- a/webapp/src/Schema.tsx
+++ b/webapp/src/Schema.tsx
@@ -130,3 +130,9 @@ export interface Organisation {
     name: string,
     role?: string
 }
+
+export interface ECProject {
+    nren: string,
+    year: number,
+    project: string,
+}
diff --git a/webapp/src/pages/CompendiumData.tsx b/webapp/src/pages/CompendiumData.tsx
index 998d8e4399ec32a78add487dfe0eaeeae37bfb54..50eee8b2fb5c4f244820a01b531796190bdcf3e7 100644
--- a/webapp/src/pages/CompendiumData.tsx
+++ b/webapp/src/pages/CompendiumData.tsx
@@ -66,6 +66,11 @@ function CompendiumData(): ReactElement {
                                         <span>NREN Parent Organisations</span>
                                     </Link>
                                 </Row>
+                                <Row>
+                                    <Link to="/ec-projects" className="link-text-underline">
+                                        <span>NREN Involvement in European Commission Projects</span>
+                                    </Link>
+                                </Row>
                             </div>
                         </CollapsibleBox>
                     </div>
diff --git a/webapp/src/pages/ECProjects.tsx b/webapp/src/pages/ECProjects.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9691673409e1410d8b2664323aea66edd9af5a05
--- /dev/null
+++ b/webapp/src/pages/ECProjects.tsx
@@ -0,0 +1,147 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { Col, Container, Row, Table } from "react-bootstrap";
+import Filter from "../components/graphing/Filter"
+
+import {
+    ECProject,
+    FilterSelection
+} from "../Schema";
+
+async function getData(): Promise<ECProject[]> {
+    try {
+        const response = await fetch('/api/ec-project');
+        return response.json();
+    } catch (error) {
+        console.error(`Failed to load data: ${error}`);
+        throw error;
+    }
+}
+
+function getYearsAndNrens(sourceData: ECProject[]) {
+    const years = new Set<number>();
+    const nrens = new Set<string>();
+    sourceData.forEach(datapoint => {
+        years.add(datapoint.year);
+        nrens.add(datapoint.nren);
+    });
+    return { years: years, nrens: nrens };
+}
+
+interface inputProps {
+    filterSelection: FilterSelection
+    setFilterSelection: React.Dispatch<React.SetStateAction<FilterSelection>>
+}
+
+function ECProjects({ filterSelection, setFilterSelection }: inputProps) {
+    const [projectData, setProjectData] = useState<ECProject[]>();
+
+    const { years, nrens } = useMemo(
+        () => getYearsAndNrens(projectData || []),
+        [projectData]
+    );
+
+    const selectedData = (projectData || []).filter(project =>
+        filterSelection.selectedYears.includes(project.year) && filterSelection.selectedNrens.includes(project.nren)
+    );
+    const projectDataByYear = new Map<number, Map<string, ECProject[]>>();
+
+    // group ECProjects by year and nren
+    selectedData.forEach(project => {
+        if (!projectDataByYear.has(project.year)) {
+            projectDataByYear.set(project.year, new Map<string, ECProject[]>());
+        }
+        const ecProjectsForYear = projectDataByYear.get(project.year)!;
+
+        const nrenData = ecProjectsForYear.get(project.nren) || [];
+        nrenData.push(project);
+        ecProjectsForYear.set(project.nren, nrenData);
+
+    });
+
+    function nrensort(a: ECProject, b: ECProject) {
+        const byNREN = a.nren < b.nren ? -1 : a.nren > b.nren ? 1 : 0;
+        const byYear = a.year < b.year ? -1 : a.year > b.year ? 1 : 0;
+        return byYear || byNREN;
+    }
+
+    // sort projectDataByYear by year and nren, maps keep insertion order
+    projectDataByYear.forEach((nrenData, year) => {
+        const sortedNrenData = new Map<string, ECProject[]>();
+        [...nrenData.entries()].sort((a, b) => nrensort(a[1][0], b[1][0])).forEach(([nren, projects]) => {
+            sortedNrenData.set(nren, projects);
+        });
+        projectDataByYear.set(year, sortedNrenData);
+    });
+
+
+    useEffect(() => {
+        const loadData = async () => {
+            const data = await getData();
+            setProjectData(data);
+
+            // filter fallback for when nothing is selected (only last year for all nrens)
+            const { years, nrens } = getYearsAndNrens(data);
+            setFilterSelection(previous => {
+                const visibleYears = previous.selectedYears.filter(year => years.has(year));
+                const newSelectedYears = visibleYears.length ? previous.selectedYears : [Math.max(...years)];
+                const visibleNrens = previous.selectedNrens.filter(nren => nrens.has(nren));
+                const newSelectedNrens = visibleNrens.length ? previous.selectedNrens : [...nrens];
+                return { selectedYears: newSelectedYears, selectedNrens: newSelectedNrens };
+            });
+        }
+        loadData()
+    }, [setFilterSelection]);
+
+    return (
+        <Container>
+            <Row>
+                <Col xs={9}>
+                    <Row>
+                        <h3>NREN Involvement in European Commission Projects</h3>
+                    </Row>
+                    <Row>
+                        <Table>
+                            <thead>
+                                <tr>
+                                    <th>NREN</th>
+                                    <th>Year</th>
+                                    <th>EC Project Membership</th>
+                                </tr>
+                            </thead>
+                            <tbody className='table-bg-highlighted'>
+                                {Array.from(projectDataByYear.keys()).sort().map((year) => {
+                                    return projectDataByYear.get(year) && Array.from(projectDataByYear.get(year)!.entries()).map(([nren, projects]) => (
+                                        <tr key={nren + year + projects.length}>
+                                            <td style={{ whiteSpace: 'nowrap' }}>{nren}</td>
+                                            <td>{year}</td>
+                                            <td>
+                                                <ul>
+                                                    {projects.map(project => (
+                                                        <li key={project.project}>{project.project}</li>
+                                                    ))}
+                                                </ul>
+                                            </td>
+                                        </tr>
+                                    ))
+                                })}
+                            </tbody>
+                        </Table>
+                    </Row>
+                </Col>
+                <Col xs={3}>
+                    <div className="sticky-top" style={{ top: '1rem' }}>
+                        {/* the sticky-top class makes the filter follow the user as they scroll down the page */}
+                        <Filter
+                            filterOptions={{ availableYears: [...years], availableNrens: [...nrens] }}
+                            filterSelection={filterSelection}
+                            setFilterSelection={setFilterSelection}
+                        />
+                    </div>
+
+
+                </Col>
+            </Row>
+        </Container>
+    );
+}
+export default ECProjects;
diff --git a/webapp/src/scss/layout/_components.scss b/webapp/src/scss/layout/_components.scss
index 58a20fb698315bbe3d9c93837eefe5dd19b93ba6..2f629cd7f2649fa9a78d7c2af05167d150c9b9a7 100644
--- a/webapp/src/scss/layout/_components.scss
+++ b/webapp/src/scss/layout/_components.scss
@@ -86,12 +86,12 @@
 }
 
 @mixin linkHover {
-  &:hover{
+  &:hover {
     color: #003753
   }
 }
 
-.link-text{
+.link-text {
   text-decoration: none;
   color: #003753;
   @include linkHover;
@@ -108,7 +108,7 @@
 .page-footer {
   min-height: 350px;
   width: 100%;
-  bottom:0;
+  bottom: 0;
   justify-content: center;
   align-items: center;
   padding-top: 20px;
@@ -125,22 +125,45 @@
 }
 
 .btn-compendium {
-  --bs-btn-color:#fff;
-  --bs-btn-bg:#003753;
-  --bs-btn-border-color:#003753;
-  --bs-btn-hover-color:#fff;
-  --bs-btn-hover-bg:#3b536b;
-  --bs-btn-hover-border-color:#3b536b;
-  --bs-btn-focus-shadow-rgb:49,132,253;
-  --bs-btn-active-color:#f5f5f5;
-  --bs-btn-active-bg:#3b536b;
-  --bs-btn-active-border-color:#003753;
-  --bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);
-  --bs-btn-disabled-color:#fff;
-  --bs-btn-disabled-bg:#0d6efd;
-  --bs-btn-disabled-border-color:#0d6efd
+  --bs-btn-color: #fff;
+  --bs-btn-bg: #003753;
+  --bs-btn-border-color: #003753;
+  --bs-btn-hover-color: #fff;
+  --bs-btn-hover-bg: #3b536b;
+  --bs-btn-hover-border-color: #3b536b;
+  --bs-btn-focus-shadow-rgb: 49, 132, 253;
+  --bs-btn-active-color: #f5f5f5;
+  --bs-btn-active-bg: #3b536b;
+  --bs-btn-active-border-color: #003753;
+  --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+  --bs-btn-disabled-color: #fff;
+  --bs-btn-disabled-bg: #0d6efd;
+  --bs-btn-disabled-border-color: #0d6efd
 }
 
 .fit-max-content {
   min-width: max-content;
 }
+
+.table-bg-highlighted {
+  tr:nth-child(even) {
+    background-color: rgba(102, 121, 139, 0.178);
+  }
+
+  tr:hover {
+    background-color: rgba(102, 121, 139, 0.521);
+  }
+
+  // set LI icons to be a minus sign
+  li {
+    list-style-type: square;
+    list-style-position: inside;
+  }
+
+}
+
+.sticky-top {
+  position: sticky;
+  top: 0;
+  z-index: 1;
+}
\ No newline at end of file