diff --git a/opentech/public/projects/migrations/0001_initial.py b/opentech/public/projects/migrations/0001_initial.py
index 91221490357a3d5d0f5e7cfa753d447cfe6284ad..eaa4ee3ef1b2bce9f64778a51137016a787d0983 100644
--- a/opentech/public/projects/migrations/0001_initial.py
+++ b/opentech/public/projects/migrations/0001_initial.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.11.8 on 2018-01-05 16:18
+# Generated by Django 1.11.8 on 2018-01-15 16:50
 from __future__ import unicode_literals
 
 from django.db import migrations, models
@@ -18,11 +18,19 @@ class Migration(migrations.Migration):
     initial = True
 
     dependencies = [
-        ('wagtailcore', '0040_page_draft_title'),
         ('images', '0001_initial'),
+        ('wagtailcore', '0040_page_draft_title'),
     ]
 
     operations = [
+        migrations.CreateModel(
+            name='ProjectContactDetails',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('service', models.CharField(choices=[('website', 'Main Website URL'), ('twitter', 'Twitter Handle'), ('github', 'Github Organisation or Project')], max_length=200)),
+                ('value', models.CharField(max_length=255)),
+            ],
+        ),
         migrations.CreateModel(
             name='ProjectIndexPage',
             fields=[
@@ -31,6 +39,7 @@ class Migration(migrations.Migration):
                 ('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')),
             ],
@@ -47,7 +56,9 @@ class Migration(migrations.Migration):
                 ('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)),
-                ('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))))), ('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)))))))),
+                ('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)))))))),
+                ('header_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')),
+                ('icon', 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')),
             ],
@@ -69,4 +80,9 @@ class Migration(migrations.Migration):
                 'abstract': False,
             },
         ),
+        migrations.AddField(
+            model_name='projectcontactdetails',
+            name='project_page',
+            field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='contact_details', to='projects.ProjectPage'),
+        ),
     ]
diff --git a/opentech/public/projects/models.py b/opentech/public/projects/models.py
index 004942abff01e30f43d240a7e1afa2262951c652..3535dc2cb6a634cc57793cff47100fd5473b017c 100644
--- a/opentech/public/projects/models.py
+++ b/opentech/public/projects/models.py
@@ -1,6 +1,8 @@
 from django.db import models
 from django.conf import settings
+from django.core.exceptions import ValidationError
 from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
+from django.core.validators import URLValidator
 
 from modelcluster.fields import ParentalKey
 from wagtail.wagtailadmin.edit_handlers import (
@@ -11,6 +13,7 @@ from wagtail.wagtailadmin.edit_handlers import (
 )
 
 from wagtail.wagtailcore.fields import StreamField
+from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
 from wagtail.wagtailsearch import index
 
 from opentech.public.utils.blocks import StoryBlock
@@ -20,6 +23,51 @@ from opentech.public.utils.models import (
 )
 
 
+class ProjectContactDetails(models.Model):
+    project_page = ParentalKey(
+        'ProjectPage',
+        related_name='contact_details'
+    )
+    site_titles = (
+        ('website', "Main Website URL"),
+        ('twitter', "Twitter Handle"),
+        ('github', "Github Organisation or Project"),
+    )
+    site_urls = (
+        ('website', ''),
+        ('twitter', 'https://twitter.com/'),
+        ('github', 'https://github.com/'),
+    )
+    service = models.CharField(
+        max_length=200,
+        choices=site_titles,
+    )
+    value = models.CharField(max_length=255)
+
+    @property
+    def url(self):
+        return dict(self.site_urls)[self.service] + self.value
+
+    def service_name(self):
+        site_display = {
+            'twitter': '@' + self.value,
+            'github': 'Github',
+            'website': 'Main Website',
+        }
+        return site_display[self.service]
+
+    def clean(self):
+        if self.service == 'twitter' and self.value.startswith('@'):
+            self.username = self.username[1:]
+
+        if self.service == 'website':
+            validate = URLValidator()
+            try:
+                validate(self.value)
+            except ValidationError as e:
+                raise ValidationError({'value': e})
+
+
 class ProjectPageRelatedPage(RelatedPage):
     source_page = ParentalKey('ProjectPage', related_name='related_pages')
 
@@ -33,13 +81,18 @@ class ProjectPage(BasePage):
     parent_page_types = ['ProjectIndexPage']
 
     introduction = models.TextField(blank=True)
+    icon = models.ForeignKey(
+        'images.CustomImage',
+        null=True,
+        blank=True,
+        related_name='+',
+        on_delete=models.SET_NULL
+    )
     body = StreamField(StoryBlock())
 
     # Fields to add:
     # otf_status
     # status
-    # social_accounts
-    # website
     # funding
 
     search_fields = BasePage.search_fields + [
@@ -48,8 +101,10 @@ class ProjectPage(BasePage):
     ]
 
     content_panels = BasePage.content_panels + [
+        ImageChooserPanel('icon'),
         FieldPanel('introduction'),
         StreamFieldPanel('body'),
+        InlinePanel('contact_details', label="Contact Details"),
         InlinePanel('related_pages', label="Related pages"),
     ]
 
diff --git a/opentech/public/projects/templates/projects/project_page.html b/opentech/public/projects/templates/projects/project_page.html
new file mode 100644
index 0000000000000000000000000000000000000000..a6740d230d749e74e899a4a7ab1f2a88a4f8099a
--- /dev/null
+++ b/opentech/public/projects/templates/projects/project_page.html
@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+
+{% load wagtailcore_tags wagtailimages_tags %}
+
+
+{% block content %}
+    <div class="intro">
+        <div class="container">
+            {% if page.icon %}
+                {% image page.icon original %}
+            {% endif %}
+            <h1>{{ page.title }}</h1>
+            {% if page.introduction %}
+                <p>{{ page.introduction }}</p>
+            {% endif %}
+
+            {{ page.body }}
+
+            {% with contact_details=page.contact_details.all %}
+                {% if contact_details %}
+                    {% for contact in contact_details %}
+                <p><a href="{{ contact.url }}">
+                    <span class="icon {{ contact.service }}">{{ contact.service_name }}
+                        </a></p>
+                    {% endfor %}
+
+                {% endif %}
+            {% endwith %}
+
+        </div>
+    </div>
+
+{% endblock content %}