diff --git a/opentech/public/funds/migrations/0002_labindex_labpage.py b/opentech/public/funds/migrations/0002_labindex_labpage.py
new file mode 100644
index 0000000000000000000000000000000000000000..42af1ca714f3603957dcde5873c8592ad790e537
--- /dev/null
+++ b/opentech/public/funds/migrations/0002_labindex_labpage.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2018-01-11 14:47
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import opentech.public.funds.blocks
+import wagtail.wagtailcore.blocks
+import wagtail.wagtailcore.fields
+import wagtail.wagtaildocs.blocks
+import wagtail.wagtailembeds.blocks
+import wagtail.wagtailimages.blocks
+import wagtail.wagtailsnippets.blocks
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('images', '0001_initial'),
+        ('wagtailcore', '0040_page_draft_title'),
+        ('public_funds', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='LabIndex',
+            fields=[
+                ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
+                ('social_text', models.CharField(blank=True, max_length=255)),
+                ('listing_title', models.CharField(blank=True, help_text='Override the page title used when this page appears in listings', max_length=255)),
+                ('listing_summary', models.CharField(blank=True, help_text="The text summary used when this page appears in listings. It's also used as the description for search engines if the 'Search description' field above is not defined.", max_length=255)),
+                ('introduction', models.TextField(blank=True)),
+                ('header_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')),
+                ('listing_image', models.ForeignKey(blank=True, help_text='Choose the image you wish to be displayed when this page appears in listings', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')),
+                ('social_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page', models.Model),
+        ),
+        migrations.CreateModel(
+            name='LabPage',
+            fields=[
+                ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
+                ('social_text', models.CharField(blank=True, max_length=255)),
+                ('listing_title', models.CharField(blank=True, help_text='Override the page title used when this page appears in listings', max_length=255)),
+                ('listing_summary', models.CharField(blank=True, help_text="The text summary used when this page appears in listings. It's also used as the description for search engines if the 'Search description' field above is not defined.", max_length=255)),
+                ('introduction', models.TextField(blank=True)),
+                ('lab_link', models.URLField(blank=True, verbose_name='External link')),
+                ('link_text', models.CharField(help_text='Text to display on the button', max_length=255)),
+                ('body', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock()), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('quote', wagtail.wagtailcore.blocks.StructBlock((('quote', wagtail.wagtailcore.blocks.CharBlock(classname='title')), ('attribution', wagtail.wagtailcore.blocks.CharBlock(required=False)), ('job_title', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('embed', wagtail.wagtailembeds.blocks.EmbedBlock()), ('call_to_action', wagtail.wagtailsnippets.blocks.SnippetChooserBlock('utils.CallToActionSnippet', template='blocks/call_to_action_block.html')), ('document', wagtail.wagtailcore.blocks.StructBlock((('document', wagtail.wagtaildocs.blocks.DocumentChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('project_list', opentech.public.funds.blocks.ProjectsBlock()), ('reviewer_list', opentech.public.funds.blocks.ReviewersBlock())))),
+                ('header_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')),
+                ('lab_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.Page')),
+                ('listing_image', models.ForeignKey(blank=True, help_text='Choose the image you wish to be displayed when this page appears in listings', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')),
+                ('social_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page', models.Model),
+        ),
+    ]
diff --git a/opentech/public/funds/migrations/0003_icon_and_related_pages.py b/opentech/public/funds/migrations/0003_icon_and_related_pages.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba0f01802a568464799cae603f07efae67b5f8d8
--- /dev/null
+++ b/opentech/public/funds/migrations/0003_icon_and_related_pages.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2018-01-12 15:19
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import modelcluster.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('images', '0001_initial'),
+        ('public_funds', '0002_labindex_labpage'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='labpage',
+            name='icon',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage'),
+        ),
+        migrations.CreateModel(
+            name='LabPageRelatedPage',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
+                ('page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.Page')),
+                ('source_page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='related_pages', to='public_funds.LabPage')),
+            ],
+            options={
+                'ordering': ['sort_order'],
+                'abstract': False,
+            },
+        ),
+    ]
diff --git a/opentech/public/funds/models.py b/opentech/public/funds/models.py
index c5b012db1a3d44ee8c7b50f9714d72da77918257..c027610f3196d27416c2158d6ce8407c4bc37fd1 100644
--- a/opentech/public/funds/models.py
+++ b/opentech/public/funds/models.py
@@ -1,15 +1,23 @@
 from django.conf import settings
+from django.core.exceptions import ValidationError
 from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
 from django.db import models
 
+from modelcluster.fields import ParentalKey
 from wagtail.wagtailadmin.edit_handlers import (
     FieldPanel,
+    InlinePanel,
+    MultiFieldPanel,
     PageChooserPanel,
     StreamFieldPanel,
 )
 from wagtail.wagtailcore.fields import StreamField
+from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
 
-from opentech.public.utils.models import BasePage
+from opentech.public.utils.models import (
+    BasePage,
+    RelatedPage,
+)
 
 from .blocks import FundBlock
 
@@ -46,12 +54,98 @@ class FundIndex(BasePage):
         page = request.GET.get('page', 1)
         paginator = Paginator(funds, settings.DEFAULT_PER_PAGE)
         try:
-            news = paginator.page(page)
+            funds = paginator.page(page)
+        except PageNotAnInteger:
+            funds = paginator.page(1)
+        except EmptyPage:
+            funds = paginator.page(paginator.num_pages)
+
+        context = super().get_context(request, *args, **kwargs)
+        context.update(subpages=funds)
+        return context
+
+
+class LabPageRelatedPage(RelatedPage):
+    source_page = ParentalKey('LabPage', related_name='related_pages')
+
+
+class LabPage(BasePage):
+    subpage_types = []
+    parent_page_types = ['LabIndex']
+
+    introduction = models.TextField(blank=True)
+    icon = models.ForeignKey(
+        'images.CustomImage',
+        null=True,
+        blank=True,
+        related_name='+',
+        on_delete=models.SET_NULL
+    )
+    lab_type = models.ForeignKey(
+        'wagtailcore.Page',
+        blank=True,
+        null=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+    )
+    lab_link = models.URLField(blank=True, verbose_name='External link')
+    link_text = models.CharField(max_length=255, help_text='Text to display on the button')
+    body = StreamField(FundBlock())
+
+    content_panels = BasePage.content_panels + [
+        ImageChooserPanel('icon'),
+        FieldPanel('introduction'),
+        MultiFieldPanel([
+            # Limit to lab pages once created
+            PageChooserPanel('lab_type'),
+            FieldPanel('lab_link'),
+            FieldPanel('link_text'),
+        ], heading='Link for lab application'),
+        StreamFieldPanel('body'),
+        InlinePanel('related_pages', label="Related pages"),
+    ]
+
+    @property
+    def link_to_lab(self):
+        return self.lab_link or self.lab_type.get_url()
+
+    def clean(self):
+        if self.lab_type and self.lab_link:
+            raise ValidationError({
+                'lab_type': 'Cannot link to both a Lab page and external link',
+                'lab_link': 'Cannot link to both a Lab page and external link',
+            })
+
+        if not self.lab_type and not self.lab_link:
+            raise ValidationError({
+                'lab_type': 'Please provide a way for applicants to apply',
+                'lab_link': 'Please provide a way for applicants to apply',
+            })
+
+
+class LabIndex(BasePage):
+    subpage_types = ['LabPage']
+    parent_page_types = ['home.HomePage']
+
+    introduction = models.TextField(blank=True)
+
+    content_panels = BasePage.content_panels + [
+        FieldPanel('introduction')
+    ]
+
+    def get_context(self, request, *args, **kwargs):
+        labs = LabPage.objects.live().public().descendant_of(self)
+
+        # Pagination
+        page = request.GET.get('page', 1)
+        paginator = Paginator(labs, settings.DEFAULT_PER_PAGE)
+        try:
+            labs = paginator.page(page)
         except PageNotAnInteger:
-            news = paginator.page(1)
+            labs = paginator.page(1)
         except EmptyPage:
-            news = paginator.page(paginator.num_pages)
+            labs = paginator.page(paginator.num_pages)
 
         context = super().get_context(request, *args, **kwargs)
-        context.update(news=news)
+        context.update(subpages=labs)
         return context
diff --git a/opentech/public/funds/templates/public_funds/lab_index.html b/opentech/public/funds/templates/public_funds/lab_index.html
new file mode 100644
index 0000000000000000000000000000000000000000..ce4f0b1db6ef2f27f9269c007d362c79d43b38d4
--- /dev/null
+++ b/opentech/public/funds/templates/public_funds/lab_index.html
@@ -0,0 +1 @@
+{% extends "standardpages/index_page.html" %}
diff --git a/opentech/public/funds/templates/public_funds/lab_page.html b/opentech/public/funds/templates/public_funds/lab_page.html
new file mode 100644
index 0000000000000000000000000000000000000000..2642ccfc89ba5f7287a0ffaaf9a2ff71d6ee6d13
--- /dev/null
+++ b/opentech/public/funds/templates/public_funds/lab_page.html
@@ -0,0 +1,17 @@
+{% extends "base.html" %}
+{% load wagtailcore_tags wagtailimages_tags navigation_tags static %}
+
+{% block content %}
+    <div class="wrapper wrapper--flex">
+        <section class="section section--main">
+            <h1>{{ page.icon }}{{ page.title }}</h1>
+            <h5>{{ page.introduction }}</h5>
+
+            {% include_block page.body %}
+        </section>
+        <section>
+            <a href="{{ page.link_to_lab }}">{{ page.link_text }}</a>
+        </section>
+    </div>
+    {% include "includes/relatedcontent.html" with related_pages=page.related_pages.all %}
+{% endblock %}