diff --git a/opentech/apply/activity/templates/activity/include/listing_base.html b/opentech/apply/activity/templates/activity/include/listing_base.html
index f999f30d48506338aea55ecea8cafb49b56a566a..8f13bc06b68f315de7e3a96c9ffbdeb9f0d0fa6c 100644
--- a/opentech/apply/activity/templates/activity/include/listing_base.html
+++ b/opentech/apply/activity/templates/activity/include/listing_base.html
@@ -3,10 +3,25 @@
     <div class="feed__pre-content">
         <p class="feed__label feed__label--{{ activity.type }}">{{ activity.type|capfirst }}</p>
     </div>
-    <div class="feed__content">
-        <div class="feed__meta">
+    <div class="feed__content js-feed-content">
+        <div class="feed__meta js-feed-meta">
             <p class="feed__label feed__label--{{ activity.type }} feed__label--mobile">{{ activity.type|capfirst }}</p>
             <p class="feed__meta-item"><span>{{ activity|display_author:request.user }}</span> – {{ activity.timestamp|date:"Y-m-d H:i" }}</p>
+
+            {% if editable %}
+                {% if activity.user == request.user %}
+                    <p class="feed__meta-item feed__meta-item--edit-button">
+                        <a class="link link--edit-submission is-active js-edit-comment" href="#">
+                            Edit
+                            <svg class="icon icon--pen"><use xlink:href="#pen"></use></svg>
+                        </a>
+                    </p>
+                {% endif %}
+                {% if activity.edited %}
+                    <p class="feed__meta-item feed__meta-item--last-edited">(Last edited: <span class="js-last-edited">{{ activity.edited|date:"Y-m-d H:i" }}</span>)</p>
+                {% endif %}
+            {% endif %}
+
             {% if activity.private %}
                 <p class="feed__meta-item feed__meta-item--right">
                     <svg class="icon icon--eye"><use xlink:href="#eye"></use></svg>
@@ -14,12 +29,21 @@
                 </p>
             {% endif %}
         </div>
+
         <p class="feed__heading">
             {% if submission_title %}
                 updated <a href="{{ activity.submission.get_absolute_url }}">{{ activity.submission.title }}</a>
             {% endif %}
 
-            {{ activity|display_for:request.user|submission_links|markdown|bleach }}
+            {% if editable %}
+                <div class="feed__comment js-comment" data-id="{{activity.id}}" data-comment="{{activity|display_for:request.user|to_markdown}}" data-edit-url="{% url 'funds:api:comments:edit' pk=activity.pk %}">
+                    {{ activity|display_for:request.user|submission_links|markdown|bleach }}
+                </div>
+
+                <div class="js-edit-block" aria-live="polite"></div>
+            {% else %}
+                {{ activity|display_for:request.user|submission_links|markdown|bleach }}
+            {% endif %}
 
             {% if not submission_title and activity|user_can_see_related:request.user %}
                 {% with url=activity.related_object.get_absolute_url %}
diff --git a/opentech/apply/funds/serializers.py b/opentech/apply/funds/serializers.py
index d76d8ad1c849108aac8f101830c3616a73186dc3..bf933e22d34771eb49056adeef5fcd7d3889ffd2 100644
--- a/opentech/apply/funds/serializers.py
+++ b/opentech/apply/funds/serializers.py
@@ -217,10 +217,11 @@ class RoundLabSerializer(serializers.ModelSerializer):
 class CommentSerializer(serializers.ModelSerializer):
     user = serializers.StringRelatedField()
     message = serializers.SerializerMethodField()
+    edit_url = serializers.HyperlinkedIdentityField(view_name='funds:api:comments:edit')
 
     class Meta:
         model = Activity
-        fields = ('id', 'timestamp', 'user', 'submission', 'message', 'visibility', 'edited')
+        fields = ('id', 'timestamp', 'user', 'submission', 'message', 'visibility', 'edited', 'edit_url')
 
     def get_message(self, obj):
         return bleach_value(markdown(obj.message))
@@ -228,15 +229,14 @@ class CommentSerializer(serializers.ModelSerializer):
 
 class CommentCreateSerializer(serializers.ModelSerializer):
     user = serializers.StringRelatedField()
+    edit_url = serializers.HyperlinkedIdentityField(view_name='funds:api:comments:edit')
 
     class Meta:
         model = Activity
-        fields = ('id', 'timestamp', 'user', 'message', 'visibility', 'edited')
+        fields = ('id', 'timestamp', 'user', 'message', 'visibility', 'edited', 'edit_url')
         read_only_fields = ('timestamp', 'edited',)
 
 
 class CommentEditSerializer(CommentCreateSerializer):
-    user = serializers.StringRelatedField()
-
     class Meta(CommentCreateSerializer.Meta):
         read_only_fields = ('timestamp', 'visibility', 'edited',)
diff --git a/opentech/apply/funds/templates/funds/applicationsubmission_admin_detail.html b/opentech/apply/funds/templates/funds/applicationsubmission_admin_detail.html
index 3087dda20779544542b8e1bf01b0763a006c0668..537f0b371d5aab914f588cb50f814f08f04cb5db 100644
--- a/opentech/apply/funds/templates/funds/applicationsubmission_admin_detail.html
+++ b/opentech/apply/funds/templates/funds/applicationsubmission_admin_detail.html
@@ -47,10 +47,12 @@
     {{ comment_form.media.js }}
     {{ partner_form.media.js }}
     <script src="//cdnjs.cloudflare.com/ajax/libs/fancybox/3.4.1/jquery.fancybox.min.js"></script>
+    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
     <script src="{% static 'js/apply/fancybox-global.js' %}"></script>
     <script src="{% static 'js/apply/tabs.js' %}"></script>
     <script src="{% static 'js/apply/toggle-actions-panel.js' %}"></script>
     <script src="{% static 'js/apply/toggle-reviewers.js' %}"></script>
     <script src="{% static 'js/apply/toggle-sidebar.js' %}"></script>
     <script src="{% static 'js/apply/submission-text-cleanup.js' %}"></script>
+    <script src="{% static 'js/apply/edit-comment.js' %}"></script>
 {% endblock %}
diff --git a/opentech/apply/funds/templates/funds/applicationsubmission_detail.html b/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
index 1fe5bdc008db857104a3c0c8cdc03c555533db57..f213ec869e0293accebecb7268055d6ecb59492f 100644
--- a/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
+++ b/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
@@ -141,7 +141,7 @@
     <div class="tabs__content" id="tab-2">
         <div class="feed">
             {% include "activity/include/comment_form.html" %}
-            {% include "activity/include/comment_list.html" %}
+            {% include "activity/include/comment_list.html" with editable=True %}
         </div>
     </div>
 
diff --git a/opentech/apply/funds/templates/funds/includes/activity-feed.html b/opentech/apply/funds/templates/funds/includes/activity-feed.html
index 0a3595a6d1765fd7ab028c1bc83200a646f45054..37117315ccd58c1fb4b4da12cd87404d99020698 100644
--- a/opentech/apply/funds/templates/funds/includes/activity-feed.html
+++ b/opentech/apply/funds/templates/funds/includes/activity-feed.html
@@ -15,7 +15,7 @@
 
     <div class="wrapper wrapper--medium wrapper--activity-feed js-tabs-content">
         <div class="tabs__content tabs__content--current" id="tab-1">
-            {% include "activity/include/comment_list.html" with submission_title=True %}
+            {% include "activity/include/comment_list.html" with submission_title=True editable=False %}
         </div>
 
         <div class="tabs__content" id="tab-2">
diff --git a/opentech/apply/funds/templatetags/markdown_tags.py b/opentech/apply/funds/templatetags/markdown_tags.py
index 9ba5ff19dc0828092dcfab40fdcd9ab6876f7b58..bddae4d8a14e7e8c522967c64fd4eafa261e78a7 100644
--- a/opentech/apply/funds/templatetags/markdown_tags.py
+++ b/opentech/apply/funds/templatetags/markdown_tags.py
@@ -1,11 +1,21 @@
 import mistune
+import tomd
 
 from django import template
 
 register = template.Library()
 
+mistune_markdown = mistune.Markdown()
+
 
 @register.filter
 def markdown(value):
-    markdown = mistune.Markdown()
-    return markdown(value)
+    return mistune_markdown(value)
+
+
+@register.filter
+def to_markdown(value):
+    # pass through markdown to ensure comment is a
+    # fully formed HTML block
+    value = markdown(value)
+    return tomd.convert(value)
diff --git a/opentech/static_src/src/javascript/apply/edit-comment.js b/opentech/static_src/src/javascript/apply/edit-comment.js
new file mode 100644
index 0000000000000000000000000000000000000000..eef32d7815b679927a0ec9006e4beac4a3b249c1
--- /dev/null
+++ b/opentech/static_src/src/javascript/apply/edit-comment.js
@@ -0,0 +1,145 @@
+(function ($) {
+
+    'use strict';
+
+    const comment = '.js-comment';
+    const pageDown = '.js-pagedown';
+    const feedMeta = '.js-feed-meta';
+    const editBlock = '.js-edit-block';
+    const lastEdited = '.js-last-edited';
+    const editButton = '.js-edit-comment';
+    const feedContent = '.js-feed-content';
+    const commentError = '.js-comment-error';
+    const cancelEditButton = '.js-cancel-edit';
+    const submitEditButton = '.js-submit-edit';
+
+    // handle edit
+    $(editButton).click(function (e) {
+        e.preventDefault();
+
+        closeAllEditors();
+
+        const editBlockWrapper = $(this).closest(feedContent).find(editBlock);
+        const commentWrapper = $(this).closest(feedContent).find(comment);
+        const commentContents = $(commentWrapper).attr('data-comment');
+
+        // hide the edit link and original comment
+        $(this).parent().hide();
+        $(commentWrapper).hide();
+
+        const markup = `
+            <div class="js-pagedown form">
+                <div id="wmd-button-bar-edit-comment" class="wmd-button-bar"></div>
+                <textarea id="wmd-input-edit-comment" class="wmd-input" rows="10">${commentContents}</textarea>
+                <div id="wmd-preview-edit-comment" class="wmd-preview"></div>
+                <div class="wrapper--outer-space-medium">
+                    <button class="button button--primary js-submit-edit" type="submit">Update</button>
+                    <button class="button button--white js-cancel-edit">Cancel</button>
+                </div>
+            </div>
+        `;
+
+        // add the comment to the editor
+        $(editBlockWrapper).append(markup);
+
+        // run the editor
+        initEditor();
+    });
+
+    // handle cancel
+    $(document).on('click', cancelEditButton, function () {
+        showComment(this);
+        showEditButton(this);
+        hidePageDownEditor(this);
+        if ($(commentError).length) {
+            hideError();
+        }
+    });
+
+    // handle submit
+    $(document).on('click', submitEditButton, function () {
+        const commentContainer = $(this).closest(editBlock).siblings(comment);
+        const editedComment = $(this).closest(pageDown).find('.wmd-preview').html();
+        const commentMD = $(this).closest(editBlock).find('textarea').val();
+        const editUrl = $(commentContainer).attr('data-edit-url');
+
+        fetch(editUrl, {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json',
+                'X-CSRFToken': $.cookie('csrftoken')
+            },
+            body: JSON.stringify({
+                message: editedComment
+            })
+        }).then(response => {
+            if (!response.ok) {
+                const error = Object.assign({}, response, {
+                    status: response.status,
+                    statusText: response.statusText
+                });
+                return Promise.reject(error);
+            }
+            return response.json();
+        }).then(data => {
+            updateComment(commentContainer, data.id, data.message, data.edit_url, commentMD);
+            updateLastEdited(this, data.edited);
+            showComment(this);
+            showEditButton(this);
+            hidePageDownEditor(this);
+        }).catch((error) => {
+            if (error.status === 404) {
+                handleError(this, 'Update unsuccessful. This comment has been edited elsewhere. To get the latest updates please refresh the page, but note any unsaved changes will be lost by doing so.');
+            }
+            else {
+                handleError(this, 'An error has occured. Please try again later.');
+            }
+        });
+    });
+
+    const handleError = (el, message) => {
+        $(el).closest(editBlock).append(`<p class="wrapper--error js-comment-error">${message}</p>`);
+        $(el).attr('disabled', true);
+    };
+
+    const initEditor = () => {
+        const converterOne = window.Markdown.getSanitizingConverter();
+        const commentEditor = new window.Markdown.Editor(converterOne, '-edit-comment');
+        commentEditor.run();
+    };
+
+    const showEditButton = (el) => {
+        $(el).closest(editBlock).siblings(feedMeta).find(editButton).parent().show();
+    };
+
+    const hidePageDownEditor = (el) => {
+        $(el).closest(pageDown).remove();
+    };
+
+    const showComment = (el) => {
+        $(el).closest(editBlock).siblings(comment).show();
+    };
+
+    const updateLastEdited = (el, date) => {
+        const parsedDate = new Date(date).toISOString().split('T')[0];
+        const time = new Date(date).toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
+
+        $(el).closest(feedContent).find(lastEdited).html(`${parsedDate} ${time}`);
+    };
+
+    const updateComment = (el, id, comment, editUrl, commentMarkdown) => {
+        $(el).html(comment);
+        $(el).attr('data-id', id);
+        $(el).attr('data-edit-url', editUrl);
+        $(el).attr('data-comment', commentMarkdown);
+    };
+
+    const closeAllEditors = () => {
+        $(comment).show();
+        $(pageDown).remove();
+        $(editButton).parent().show();
+    };
+
+    const hideError = () => $(commentError).remove();
+
+})(jQuery);
diff --git a/opentech/static_src/src/sass/apply/components/_feed.scss b/opentech/static_src/src/sass/apply/components/_feed.scss
index 2ab489a4bf4b1029ce4e6df43d4de6063046599f..d3afc66ee7327436224057045a9fcf735e18661c 100644
--- a/opentech/static_src/src/sass/apply/components/_feed.scss
+++ b/opentech/static_src/src/sass/apply/components/_feed.scss
@@ -101,6 +101,20 @@
             margin: 0 15px 0 0;
         }
 
+        &--edit-button {
+            border-left: 2px solid $color--mid-grey;
+            padding-left: 15px;
+        }
+
+        &--last-edited {
+            margin-right: 5px;
+            color: $color--mid-dark-grey;
+
+            span {
+                font-weight: $weight--normal;
+            }
+        }
+
         &--right {
             margin-left: auto;
         }
diff --git a/opentech/static_src/src/sass/apply/components/_wrapper.scss b/opentech/static_src/src/sass/apply/components/_wrapper.scss
index b80aa2b154d104af6634779ee87cbfa784cadd25..e7165e01b4cd95706618d388763e6ce4601e6f5f 100644
--- a/opentech/static_src/src/sass/apply/components/_wrapper.scss
+++ b/opentech/static_src/src/sass/apply/components/_wrapper.scss
@@ -89,6 +89,10 @@
         margin: 0 auto 2rem;
         background: $color--light-pink;
         border: 1px solid $color--tomato;
+
+        .feed & {
+            margin: 0 0 1rem;
+        }
     }
 
     &--bottom-space {
diff --git a/requirements.txt b/requirements.txt
index 192f6e09ab86362a00dc1f4f7af179e3984463c3..ec3bc53376c6f238efb19d9ffea89edd0134b4e9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -43,6 +43,7 @@ mistune==0.8.4
 Pillow==4.3.0
 psycopg2==2.7.3.1
 social_auth_app_django==3.1.0
+tomd==0.1.3
 wagtail~=2.2.0
 wagtail-cache==0.5.1
 whitenoise==4.0