diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb
index 2c9e19ab61dd5932eb06a33f755709bc1798b72f..d11b5db1d8005a953a061ddbbccf0a133fd3d05f 100644
--- a/app/helpers/posts_helper.rb
+++ b/app/helpers/posts_helper.rb
@@ -15,6 +15,30 @@ module PostsHelper
     end
   end
 
+  # @param category [Category, Nil]
+  # @return [Integer] the minimum length for post bodies
+  def min_body_length(category)
+    category&.min_body_length || 30
+  end
+
+  # @param _category [Category, Nil]
+  # @return [Integer] the maximum length for post bodies
+  def max_body_length(_category)
+    30_000
+  end
+
+  # @param category [Category, Nil]
+  # @return [Integer] the minimum length for post titles
+  def min_title_length(category)
+    category&.min_title_length || 15
+  end
+
+  # @param _category [Category, Nil]
+  # @return [Integer] the maximum length for post titles
+  def max_title_length(_category)
+    [SiteSetting['MaxTitleLength'] || 255, 255].min
+  end
+
   class PostScrubber < Rails::Html::PermitScrubber
     def initialize
       super
diff --git a/app/models/concerns/post_validations.rb b/app/models/concerns/post_validations.rb
index 288d8d4eef0d1774227c4657bacc1e726f706f3c..687fa85f7746714dc1510d92c5c76ec003646b56 100644
--- a/app/models/concerns/post_validations.rb
+++ b/app/models/concerns/post_validations.rb
@@ -41,14 +41,14 @@ module PostValidations
   def stripped_minimum_body
     min_body = category.nil? ? 30 : category.min_body_length
     if (body_markdown&.gsub(/(?:^[\s\t\u2000-\u200F]+|[\s\t\u2000-\u200F]+$)/, '')&.length || 0) < min_body
-      errors.add(:body, 'must be more than 30 non-whitespace characters long')
+      errors.add(:body, "must be more than #{min_body} non-whitespace characters long")
     end
   end
 
   def stripped_minimum_title
     min_title = category.nil? ? 15 : category.min_title_length
     if (title&.gsub(/(?:^[\s\t\u2000-\u200F]+|[\s\t\u2000-\u200F]+$)/, '')&.length || 0) < min_title
-      errors.add(:title, 'must be more than 15 non-whitespace characters long')
+      errors.add(:title, "must be more than #{min_title} non-whitespace characters long")
     end
   end
 
diff --git a/app/models/post.rb b/app/models/post.rb
index 9bedb6c7cb88c0fc787fe247a695c708d27ffb90..c7c2df8759f631149eebc9f376882027d85a6210 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -28,7 +28,7 @@ class Post < ApplicationRecord
 
   serialize :tags_cache, Array
 
-  validates :body, presence: true, length: { minimum: 30, maximum: 30_000 }
+  validates :body, presence: true, length: { maximum: 30_000 }
   validates :doc_slug, uniqueness: { scope: [:community_id], case_sensitive: false }, if: -> { doc_slug.present? }
   validates :title, presence: true, if: -> { post_type.is_top_level? }
   validates :tags_cache, presence: true, if: -> { post_type.has_tags }
diff --git a/app/views/posts/_form.html.erb b/app/views/posts/_form.html.erb
index f6de1d3f6a145154506d830ce20c63b0394d5022..47a8eaf77f229315a3119d90bbe9a8829456a68d 100644
--- a/app/views/posts/_form.html.erb
+++ b/app/views/posts/_form.html.erb
@@ -54,7 +54,8 @@
     </p>
   <% end %>
 
-  <%= render 'shared/body_field', f: f, field_name: :body_markdown, field_label: t('posts.body_label'), post: post, category: category %>
+  <%= render 'shared/body_field', f: f, field_name: :body_markdown, field_label: t('posts.body_label'), post: post,
+             min_length: min_body_length(category), max_length: max_body_length(category) %>
 
   <div class="post-preview"></div>
 
@@ -65,11 +66,11 @@
     </div>
     <div>
       <span class="has-float-right has-font-size-caption js-character-count-post-title hide"
-	    data-min="<%= category&.min_title_length %>"
-	    data-max="255"
-	    data-display-at="0.75">
+            data-min="<%= min_title_length(category) %>"
+            data-max="<%= max_title_length(category) %>"
+            data-display-at="0.75">
         <i class="fas fa-ellipsis-h js-character-count__icon"></i>
-        <span class="js-character-count__count">0 / 255</span>
+        <span class="js-character-count__count">0 / <%= max_title_length(category) %></span>
       </span>
     </div>
   <% end %>
diff --git a/app/views/posts/_mdhint.html.erb b/app/views/posts/_mdhint.html.erb
index c4cdb07cfb4328b4a681febc88c7eb035bad61f4..b8e5ed06741ecdc6cb0858128fdcd3a0230a3082 100644
--- a/app/views/posts/_mdhint.html.erb
+++ b/app/views/posts/_mdhint.html.erb
@@ -1,16 +1,26 @@
+<%#
+  Widget displaying that we support markdown, linking to the help article. This also applies the min/max length for the
+  post body.
+
+  Variables:
+    min_length    : [Integer, Nil] optional, the minimum allowed length (default 30)
+    max_length    : [Integer, Nil] optional, the maximum allowed length (default 30_000)
+%>
+
 <%
-  # defaults
-  category ||= defined?(category) ? category : nil
-  %>
+  # Defaults
+  min_length = (defined?(min_length) ? min_length : nil) || 30
+  max_length = (defined?(max_length) ? max_length : nil) || 30_000
+%>
 
 <div class="form-caption widget--footer js-post-field-footer">
   We <a href="/help/formatting">support Markdown</a> for posts:
   <strong>**bold**</strong>, <em>*italics*</em>, <code>`code`</code>, two newlines for paragraphs
   <span class="has-float-right has-font-size-caption js-character-count-post-body hide"
-	data-min="<%= category&.min_body_length %>"
-        data-max="30000"
-	data-display-at="0.75">
+        data-min="<%= min_length %>"
+        data-max="<%= max_length %>"
+        data-display-at="0.75">
     <i class="fas fa-ellipsis-h js-character-count__icon"></i>
-    <span class="js-character-count__count">0 / 30000</span>
+    <span class="js-character-count__count">0 / <%= max_length %></span>
   </span>
 </div>
diff --git a/app/views/shared/_body_field.html.erb b/app/views/shared/_body_field.html.erb
index 9d92ec7ce40b1eb48190ec9839b5eea0c117eeb6..2ab596799399b4de70fee5d67b84adef1b1fbaac 100644
--- a/app/views/shared/_body_field.html.erb
+++ b/app/views/shared/_body_field.html.erb
@@ -1,7 +1,18 @@
+<%#
+  Adds a markdown body form textarea.
+  Variables:
+    post          : [ApplicationRecord, Nil] the entity to which this body field belongs (a post, a tag, a user, ...)
+    field_name    : [Symbol] the name of the body field (for the given entity)
+    field_label   : [String] the label for the field
+    min_length    : [Integer, Nil] optional, the minimum allowed length
+    max_length    : [Integer, Nil] optional, the maximum allowed length
+%>
+
 <%
-  # defaults
-  category ||= defined?(category) ? category : nil
-  %>
+  # Defaults
+  min_length = defined?(min_length) ? min_length : nil
+  max_length = defined?(max_length) ? max_length : nil
+%>
 
 <div class="form-group">
   <%= f.label field_name, field_label, class: "form-element" %>
@@ -27,7 +38,7 @@
     <%= render 'shared/markdown_tools' %>
     <%= f.text_area field_name, **({ class: classes, rows: 15, placeholder: 'Start typing your post...' }).merge(value), 
                                 data: { character_count: ".js-character-count-post-body" } %>
-    <%= render 'posts/mdhint', category: category %>
+    <%= render 'posts/mdhint', min_length: min_length, max_length: max_length %>
   </div>
   <%= hidden_field_tag "__html", nil, class: 'js-post-html' %>
 </div>
diff --git a/app/views/users/edit_profile.html.erb b/app/views/users/edit_profile.html.erb
index 376665ed209f94c4b2c860a8d7f13961525e2319..25f8c6a0c34301ea1bec9c8d58d12b943e6d93ad 100644
--- a/app/views/users/edit_profile.html.erb
+++ b/app/views/users/edit_profile.html.erb
@@ -37,7 +37,8 @@
     <%= f.text_field :username, class: 'form-element', autocomplete: 'off' %>
   </div>
 
-  <%= render 'shared/body_field', f: f, field_name: :profile_markdown, field_label: 'Profile', post: current_user %>
+  <%= render 'shared/body_field', f: f, field_name: :profile_markdown, field_label: 'Profile', post: current_user,
+             min_length: 0 %>
 
   <div class="post-preview"></div>
 
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index 4900e8052a1a980c33a50b8d7a42533830d202d2..925437b4249a030b3e43b001ee6d8427d567ba9b 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -44,10 +44,11 @@
         <% end %>
       </p>
 
-      <% if @user.profile.nil? || @user.profile.blank? %>
+      <% effective_profile = raw(sanitize(@user.profile&.strip || '', scrubber: scrubber)) %>
+      <% if effective_profile.blank? %>
         <p class="is-lead">A quiet enigma. We don't know anything about <span dir="ltr"><%= rtl_safe_username(@user) %></span> yet.</p>
       <% else %>
-        <%= raw(sanitize(@user.profile, scrubber: scrubber)) %>
+        <%= effective_profile %>
       <% end %>
       </div>