diff --git a/lib/philomena/posts.ex b/lib/philomena/posts.ex
index 96f7f254..cf0c79b8 100644
--- a/lib/philomena/posts.ex
+++ b/lib/philomena/posts.ex
@@ -12,6 +12,7 @@ defmodule Philomena.Posts do
   alias Philomena.Posts.Post
   alias Philomena.Forums.Forum
   alias Philomena.Notifications
+  alias Philomena.Versions
 
   @doc """
   Gets a single post.
@@ -122,10 +123,23 @@ defmodule Philomena.Posts do
       {:error, %Ecto.Changeset{}}
 
   """
-  def update_post(%Post{} = post, attrs) do
-    post
-    |> Post.changeset(attrs)
-    |> Repo.update()
+  def update_post(%Post{} = post, editor, attrs) do
+    now = DateTime.utc_now() |> DateTime.truncate(:second)
+    current_body = post.body
+    current_reason = post.edit_reason
+
+    post_changes =
+      Post.changeset(post, attrs, now)
+
+    Multi.new
+    |> Multi.update(:post, post_changes)
+    |> Multi.run(:version, fn _repo, _changes ->
+      Versions.create_version("Post", post.id, editor.id, %{
+        "body" => current_body,
+        "edit_reason" => current_reason
+      })
+    end)
+    |> Repo.isolated_transaction(:serializable)
   end
 
   @doc """
diff --git a/lib/philomena/posts/post.ex b/lib/philomena/posts/post.ex
index d463df28..393906e7 100644
--- a/lib/philomena/posts/post.ex
+++ b/lib/philomena/posts/post.ex
@@ -24,7 +24,7 @@ defmodule Philomena.Posts.Post do
     field :topic_position, :integer
     field :hidden_from_users, :boolean, default: false
     field :anonymous, :boolean, default: false
-    field :edited_at, :naive_datetime
+    field :edited_at, :utc_datetime
     field :deletion_reason, :string, default: ""
     field :destroyed_content, :boolean, default: false
     field :name_at_post_time, :string
@@ -33,10 +33,13 @@ defmodule Philomena.Posts.Post do
   end
 
   @doc false
-  def changeset(post, attrs) do
+  def changeset(post, attrs, edited_at \\ nil) do
     post
-    |> cast(attrs, [])
-    |> validate_required([])
+    |> cast(attrs, [:body, :edit_reason])
+    |> put_change(:edited_at, edited_at)
+    |> validate_required([:body])
+    |> validate_length(:body, min: 1, max: 300_000, count: :bytes)
+    |> validate_length(:edit_reason, max: 70, count: :bytes)
   end
 
   @doc false
diff --git a/lib/philomena_web/controllers/image/comment_controller.ex b/lib/philomena_web/controllers/image/comment_controller.ex
index 185fbf87..4dca629c 100644
--- a/lib/philomena_web/controllers/image/comment_controller.ex
+++ b/lib/philomena_web/controllers/image/comment_controller.ex
@@ -100,10 +100,8 @@ defmodule PhilomenaWeb.Image.CommentController do
         |> put_flash(:info, "Comment updated successfully.")
         |> redirect(to: Routes.image_path(conn, :show, conn.assigns.image) <> "#comment_#{comment.id}")
 
-      _error ->
-        conn
-        |> put_flash(:error, "There was an error editing your comment")
-        |> redirect(to: Routes.image_path(conn, :show, conn.assigns.image))
+      {:error, :comment, changeset, _changes} ->
+        render(conn, "edit.html", comment: conn.assigns.comment, changeset: changeset)
     end
   end
 end
diff --git a/lib/philomena_web/controllers/topic/post_controller.ex b/lib/philomena_web/controllers/topic/post_controller.ex
index a90e2d4f..f91c10c3 100644
--- a/lib/philomena_web/controllers/topic/post_controller.ex
+++ b/lib/philomena_web/controllers/topic/post_controller.ex
@@ -1,15 +1,23 @@
 defmodule PhilomenaWeb.Topic.PostController do
   use PhilomenaWeb, :controller
 
-  alias Philomena.{Forums.Forum, Topics.Topic, Posts}
+  alias Philomena.{Forums.Forum, Topics.Topic, Posts.Post}
+  alias Philomena.Posts
   alias Philomena.UserStatistics
   alias Philomena.Repo
+  import Ecto.Query
 
   plug PhilomenaWeb.FilterBannedUsersPlug
   plug PhilomenaWeb.UserAttributionPlug
   plug PhilomenaWeb.CanaryMapPlug, create: :show, edit: :show, update: :show
   plug :load_and_authorize_resource, model: Forum, id_field: "short_name", id_name: "forum_id", persisted: true
+
   plug :load_topic
+  plug PhilomenaWeb.CanaryMapPlug, create: :show, edit: :show, update: :show
+  plug :authorize_resource, model: Topic, id_field: "slug", id_name: "topic_id", persisted: true
+
+  plug PhilomenaWeb.CanaryMapPlug, edit: :edit, update: :edit
+  plug :load_and_authorize_resource, model: Post, only: [:edit, :update], preload: [topic: :forum]
 
   def create(conn, %{"post" => post_params}) do
     attributes = conn.assigns.attributes
@@ -33,20 +41,46 @@ defmodule PhilomenaWeb.Topic.PostController do
     end
   end
 
-  defp load_topic(%{params: %{"topic_id" => slug}} = conn, _args) do
-    forum = conn.assigns.forum
+  def edit(conn, _params) do
+    changeset = Posts.change_post(conn.assigns.post)
+    render(conn, "edit.html", changeset: changeset)
+  end
+
+  def update(conn, %{"post" => post_params}) do
+    post = conn.assigns.post
     user = conn.assigns.current_user
 
-    with topic when not is_nil(topic) <- Repo.get_by(Topic, slug: slug, forum_id: forum.id),
-         true <- Canada.Can.can?(user, :show, topic)
-    do
-      conn
-      |> assign(:topic, topic)
-    else
-      _ ->
+    case Posts.update_post(post, user, post_params) do
+      {:ok, _post} ->
+        Posts.reindex_post(post)
+
         conn
-        |> put_flash(:error, "Couldn't access that topic")
-        |> redirect(external: conn.assigns.referrer)
+        |> put_flash(:info, "Post successfully edited.")
+        |> redirect(to: Routes.forum_topic_path(conn, :show, post.topic.forum, post.topic, post_id: post.id) <> "#post_#{post.id}")
+
+      {:error, :post, changeset, _changes} ->
+        render(conn, "edit.html", post: conn.assigns.post, changeset: changeset)
+    end
+  end
+
+  defp load_topic(conn, _opts) do
+    user = conn.assigns.current_user
+    forum = conn.assigns.forum
+    topic =
+      Topic
+      |> where(forum_id: ^forum.id, slug: ^conn.params["topic_id"])
+      |> preload(:forum)
+      |> Repo.one()
+
+    cond do
+      is_nil(topic) ->
+        PhilomenaWeb.NotFoundPlug.call(conn)
+
+      not Canada.Can.can?(user, :show, topic) ->
+        PhilomenaWeb.NotAuthorizedPlug.call(conn)
+
+      true ->
+        Plug.Conn.assign(conn, :topic, topic)
     end
   end
 end
\ No newline at end of file
diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex
index 8016bfbd..0b4aca0b 100644
--- a/lib/philomena_web/router.ex
+++ b/lib/philomena_web/router.ex
@@ -107,6 +107,7 @@ defmodule PhilomenaWeb.Router do
       resources "/topics", TopicController, only: [:new, :create] do
         resources "/subscription", Topic.SubscriptionController, only: [:create, :delete], singleton: true
         resources "/read", Topic.ReadController, only: [:create], singleton: true
+        resources "/posts", Topic.PostController, only: [:edit, :update]
       end
 
       resources "/subscription", Forum.SubscriptionController, only: [:create, :delete], singleton: true
diff --git a/lib/philomena_web/templates/image/comment/edit.html.slime b/lib/philomena_web/templates/image/comment/edit.html.slime
index 8ef211bd..01a7d7a3 100644
--- a/lib/philomena_web/templates/image/comment/edit.html.slime
+++ b/lib/philomena_web/templates/image/comment/edit.html.slime
@@ -23,6 +23,7 @@
 
       .field
         = text_input f, :edit_reason, class: "input input--wide", placeholder: "Reason for edit"
+        = error_tag f, :edit_reason
 
     .block__tab.communication-edit__tab.hidden data-tab="preview"
       ' [Loading preview...]
diff --git a/lib/philomena_web/templates/post/_post_options.html.slime b/lib/philomena_web/templates/post/_post_options.html.slime
index f0bc1374..cd83745c 100644
--- a/lib/philomena_web/templates/post/_post_options.html.slime
+++ b/lib/philomena_web/templates/post/_post_options.html.slime
@@ -6,14 +6,16 @@ div
     i.fa.fa-flag>
     ' Report
 
-  /- if post.edited_at && can?(:read, post)
-  /  br
-  /  a href=forum_topic_post_history_path(post.topic.forum, post.topic, post)
-  /    | Edited
-  /    =<> friendly_time(post.edited_at)
-  /    - if post.edit_reason.present?
-  /      | because:
-  /      =<> post.edit_reason
+  = if not is_nil(@post.edited_at) and can?(@conn, :read, @post) do
+    br
+    a href=Routes.forum_topic_post_history_path(@conn, :index, @post.topic.forum, @post.topic, @post)
+      ' Edited
+      => pretty_time(@post.edited_at)
+
+      = if @post.edit_reason not in [nil, ""] do
+        ' because:
+        => @post.edit_reason
+
 div
   - link_path = Routes.forum_topic_path(@conn, :show, @post.topic.forum, @post.topic, post_id: @post.id) <> "#post_#{@post.id}"
   - safe_author = textile_safe_author(@post)
@@ -30,7 +32,10 @@ div
   a.communication__interaction.post-reply href=link_path data-reply-url=link_path data-author=safe_author
     i.fa.fa-reply>
     ' Reply
-  /span.owner-options.hidden
-  /  strong =<> link_to edit_forum_topic_post_path(post.topic.forum, post.topic, post), class: 'communication__interaction' do
-  /    i.fas.fa-edit
-  /    ' Edit
+  
+  = if can?(@conn, :edit, @post) do
+    span.owner-options
+      strong
+        a.communication__interaction href=Routes.forum_topic_post_path(@conn, :edit, @post.topic.forum, @post.topic, @post)
+          i.fa.fa-pencil>
+          ' Edit
diff --git a/lib/philomena_web/templates/topic/post/edit.html.slime b/lib/philomena_web/templates/topic/post/edit.html.slime
new file mode 100644
index 00000000..89faf4c2
--- /dev/null
+++ b/lib/philomena_web/templates/topic/post/edit.html.slime
@@ -0,0 +1,32 @@
+= form_for @changeset, Routes.forum_topic_post_path(@conn, :update, @post.topic.forum, @post.topic, @post), fn f ->
+  = if @changeset.action do
+    .alert.alert-danger
+      p Oops, something went wrong! Please check the errors below.
+
+  .block
+    .block__header.block__header--js-tabbed
+      a.selected href="#" data-click-tab="write"
+        i.fa.fa-pencil>
+        ' Edit
+
+      a href="#" data-click-tab="preview"
+        i.fa.fa-eye>
+        ' Preview
+
+    .block__tab.communication-edit__tab.selected data-tab="write"
+      = render PhilomenaWeb.TextileView, "_help.html", conn: @conn
+      = render PhilomenaWeb.TextileView, "_toolbar.html", conn: @conn
+
+      .field
+        = textarea f, :body, class: "input input--wide input--text js-preview-input js-toolbar-input", placeholder: "Please read the site rules before posting and use [spoiler][/spoiler] for above-rating stuff.", required: true
+        = error_tag f, :body
+
+      .field
+        = text_input f, :edit_reason, class: "input input--wide", placeholder: "Reason for edit"
+        = error_tag f, :edit_reason
+
+    .block__tab.communication-edit__tab.hidden data-tab="preview"
+      ' [Loading preview...]
+
+    .block__content.communication-edit__actions
+      => submit "Edit", class: "button", data: [disable_with: raw("Posting&hellip;")]
\ No newline at end of file
diff --git a/lib/philomena_web/templates/topic/post/history/index.html.slime b/lib/philomena_web/templates/topic/post/history/index.html.slime
index 96be8d41..87c13ae4 100644
--- a/lib/philomena_web/templates/topic/post/history/index.html.slime
+++ b/lib/philomena_web/templates/topic/post/history/index.html.slime
@@ -7,39 +7,39 @@ h1
 
 = for version <- @versions do
   article.block.communication
-  .block__content.flex.flex--no-wrap
-    .flex__fixed.spacing-right
-      = render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @post, conn: @conn
+    .block__content.flex.flex--no-wrap
+      .flex__fixed.spacing-right
+        = render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @post, conn: @conn
 
-    .flex__grow.communication__body
-      span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @post, awards: true, conn: @conn
-      br
+      .flex__grow.communication__body
+        span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @post, awards: true, conn: @conn
+        br
 
-      = render PhilomenaWeb.UserAttributionView, "_anon_user_title.html", object: @post, conn: @conn
+        = render PhilomenaWeb.UserAttributionView, "_anon_user_title.html", object: @post, conn: @conn
 
-      .communication__body__text
-        = for edit <- version.difference do
-          = case edit do
-            - {:eq, value} ->
-              = escape_nl2br(value)
+        .communication__body__text
+          = for edit <- version.difference do
+            = case edit do
+              - {:eq, value} ->
+                = escape_nl2br(value)
 
-            - {:ins, value} ->
-              ins.differ = escape_nl2br(value)
+              - {:ins, value} ->
+                ins.differ = escape_nl2br(value)
 
-            - {:del, value} ->
-              del.differ = escape_nl2br(value)
+              - {:del, value} ->
+                del.differ = escape_nl2br(value)
 
-  .block__content.communication__options
-    .flex.flex--wrap.flex--spaced-out
-      div
-        = if version.edit_reason not in [nil, ""] do
-          ' Reason:
-          = version.edit_reason
-        - else
-          ' No reason given
+    .block__content.communication__options
+      .flex.flex--wrap.flex--spaced-out
+        div
+          = if version.edit_reason not in [nil, ""] do
+            ' Reason:
+            = version.edit_reason
+          - else
+            ' No reason given
 
-      .flex__right
-        ' Edited
-        => pretty_time(version.created_at)
-        ' by
-        => render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: version, conn: @conn
\ No newline at end of file
+        .flex__right
+          ' Edited
+          => pretty_time(version.created_at)
+          ' by
+          => render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: version, conn: @conn
\ No newline at end of file