diff --git a/app/assets/javascripts/discourse/app/lib/transform-post.js b/app/assets/javascripts/discourse/app/lib/transform-post.js
index 2f8087795f4..e2dd44b9676 100644
--- a/app/assets/javascripts/discourse/app/lib/transform-post.js
+++ b/app/assets/javascripts/discourse/app/lib/transform-post.js
@@ -121,6 +121,7 @@ export default function transformPost(
     currentUser && (currentUser.id === post.user_id || currentUser.staff);
   postAtts.canArchiveTopic = !!details.can_archive_topic;
   postAtts.canCloseTopic = !!details.can_close_topic;
+  postAtts.canEditStaffNotes = !!details.can_edit_staff_notes;
   postAtts.canReplyAsNewTopic = !!details.can_reply_as_new_topic;
   postAtts.canReviewTopic = !!details.can_review_topic;
   postAtts.canPublishPage =
diff --git a/app/assets/javascripts/discourse/app/widgets/post-admin-menu.js b/app/assets/javascripts/discourse/app/widgets/post-admin-menu.js
index ec329103b46..5dbad8b0050 100644
--- a/app/assets/javascripts/discourse/app/widgets/post-admin-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/post-admin-menu.js
@@ -52,7 +52,7 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
     contents.push(buttonAtts);
   }
 
-  if (currentUser.staff) {
+  if (attrs.canEditStaffNotes) {
     if (attrs.noticeType) {
       contents.push({
         icon: "user-shield",
diff --git a/app/assets/javascripts/discourse/app/widgets/post-menu.js b/app/assets/javascripts/discourse/app/widgets/post-menu.js
index d014a67e60f..b985948d928 100644
--- a/app/assets/javascripts/discourse/app/widgets/post-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/post-menu.js
@@ -332,7 +332,7 @@ registerButton(
 );
 
 registerButton("admin", attrs => {
-  if (!attrs.canManage && !attrs.canWiki) {
+  if (!attrs.canManage && !attrs.canWiki && !attrs.canEditStaffNotes) {
     return;
   }
   return {
diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss
index 4a06c3b9bd5..dbb2c430c94 100644
--- a/app/assets/stylesheets/desktop/topic-post.scss
+++ b/app/assets/stylesheets/desktop/topic-post.scss
@@ -6,9 +6,11 @@
   margin-left: 0;
 }
 
-.topic-post:first-child {
-  nav.post-controls .post-admin-menu {
-    bottom: -125px;
+.staff {
+  .topic-post:first-child {
+    nav.post-controls .post-admin-menu {
+      bottom: -125px;
+    }
   }
 }
 
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 63ef5bb7901..09cde3a54d9 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -474,9 +474,8 @@ class PostsController < ApplicationController
   end
 
   def notice
-    raise Discourse::NotFound unless guardian.is_staff?
-
     post = find_post_from_params
+    raise Discourse::NotFound unless guardian.can_edit_staff_notes?(post.topic)
 
     if params[:notice].present?
       post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:custom]
diff --git a/app/serializers/topic_view_details_serializer.rb b/app/serializers/topic_view_details_serializer.rb
index 8ea96113179..e9967225384 100644
--- a/app/serializers/topic_view_details_serializer.rb
+++ b/app/serializers/topic_view_details_serializer.rb
@@ -18,7 +18,8 @@ class TopicViewDetailsSerializer < ApplicationSerializer
      :can_edit_tags,
      :can_publish_page,
      :can_close_topic,
-     :can_archive_topic]
+     :can_archive_topic,
+     :can_edit_staff_notes]
   end
 
   attributes(
@@ -136,13 +137,12 @@ class TopicViewDetailsSerializer < ApplicationSerializer
     !scope.can_edit?(object.topic) && scope.can_edit_tags?(object.topic)
   end
 
-  def include_can_close_topic?
-    scope.can_close_topic?(object.topic)
-  end
-
-  def include_can_archive_topic?
-    scope.can_archive_topic?(object.topic)
+  def can_perform_action_available_to_group_moderators?
+    @can_perform_action_available_to_group_moderators ||= scope.can_perform_action_available_to_group_moderators?(object.topic)
   end
+  alias :include_can_close_topic? :can_perform_action_available_to_group_moderators?
+  alias :include_can_archive_topic? :can_perform_action_available_to_group_moderators?
+  alias :include_can_edit_staff_notes? :can_perform_action_available_to_group_moderators?
 
   def include_can_publish_page?
     scope.can_publish_page?(object.topic)
diff --git a/lib/guardian/topic_guardian.rb b/lib/guardian/topic_guardian.rb
index e1ff4108c31..5a0f9b54df6 100644
--- a/lib/guardian/topic_guardian.rb
+++ b/lib/guardian/topic_guardian.rb
@@ -216,5 +216,6 @@ module TopicGuardian
   end
   alias :can_archive_topic? :can_perform_action_available_to_group_moderators?
   alias :can_close_topic? :can_perform_action_available_to_group_moderators?
+  alias :can_edit_staff_notes? :can_perform_action_available_to_group_moderators?
 
 end
diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb
index 3082c184a67..b200a244906 100644
--- a/spec/components/guardian_spec.rb
+++ b/spec/components/guardian_spec.rb
@@ -1779,6 +1779,28 @@ describe Guardian do
     end
   end
 
+  context "can_edit_staff_notes?" do
+    it 'returns false with a nil object' do
+      expect(Guardian.new(user).can_edit_staff_notes?(nil)).to eq(false)
+    end
+
+    it 'returns true for a staff user' do
+      expect(Guardian.new(moderator).can_edit_staff_notes?(topic)).to eq(true)
+    end
+
+    it 'returns false for a regular user' do
+      expect(Guardian.new(user).can_edit_staff_notes?(topic)).to eq(false)
+    end
+
+    it 'returns true for a group member with reviewable status' do
+      SiteSetting.enable_category_group_moderation = true
+      group = Fabricate(:group)
+      GroupUser.create!(group_id: group.id, user_id: user.id)
+      topic.category.update!(reviewable_by_group_id: group.id)
+      expect(Guardian.new(user).can_edit_staff_notes?(topic)).to eq(true)
+    end
+  end
+
   context "can_create_topic?" do
     it 'returns true for staff user' do
       expect(Guardian.new(moderator).can_create_topic?(topic)).to eq(true)
diff --git a/spec/requests/posts_controller_spec.rb b/spec/requests/posts_controller_spec.rb
index 6ccc7380d81..65d11dd5736 100644
--- a/spec/requests/posts_controller_spec.rb
+++ b/spec/requests/posts_controller_spec.rb
@@ -1795,11 +1795,9 @@ describe PostsController do
   end
 
   describe "#notice" do
-    before do
+    it 'can create and remove notices as a moderator' do
       sign_in(moderator)
-    end
 
-    it 'can create and remove notices' do
       put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello *world*!\n\nhttps://github.com/discourse/discourse" }
 
       expect(response.status).to eq(200)
@@ -1815,6 +1813,52 @@ describe PostsController do
       expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(nil)
       expect(public_post.custom_fields[Post::NOTICE_ARGS]).to eq(nil)
     end
+
+    describe 'group moderators' do
+      fab!(:group_user) { Fabricate(:group_user) }
+      let(:user) { group_user.user }
+      let(:group) { group_user.group }
+
+      before do
+        SiteSetting.enable_category_group_moderation = true
+        topic.category.update!(reviewable_by_group_id: group.id)
+
+        sign_in(user)
+      end
+
+      it 'can create and remove notices as a group moderator' do
+        put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello *world*!\n\nhttps://github.com/discourse/discourse" }
+
+        expect(response.status).to eq(200)
+        public_post.reload
+        expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(Post.notices[:custom])
+        expect(public_post.custom_fields[Post::NOTICE_ARGS]).to include('<p>Hello <em>world</em>!</p>')
+        expect(public_post.custom_fields[Post::NOTICE_ARGS]).not_to include('onebox')
+
+        put "/posts/#{public_post.id}/notice.json", params: { notice: nil }
+
+        expect(response.status).to eq(200)
+        public_post.reload
+        expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(nil)
+        expect(public_post.custom_fields[Post::NOTICE_ARGS]).to eq(nil)
+      end
+
+      it 'prevents a group moderator from altering notes outside of their category' do
+        moderatable_group = Fabricate(:group)
+        topic.category.update!(reviewable_by_group_id: moderatable_group.id)
+
+        put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello" }
+
+        expect(response.status).to eq(404)
+      end
+
+      it 'prevents a normal user from altering notes' do
+        group_user.destroy!
+        put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello" }
+
+        expect(response.status).to eq(404)
+      end
+    end
   end
 
   describe Plugin::Instance do
diff --git a/test/javascripts/acceptance/post-admin-menu-test.js b/test/javascripts/acceptance/post-admin-menu-test.js
new file mode 100644
index 00000000000..277fc9e3d98
--- /dev/null
+++ b/test/javascripts/acceptance/post-admin-menu-test.js
@@ -0,0 +1,21 @@
+import { acceptance } from "helpers/qunit-helpers";
+
+acceptance("Post - Admin Menu Anonymous Users", { loggedIn: false });
+
+QUnit.test("Enter as a anon user", async assert => {
+  await visit("/t/internationalization-localization/280");
+  await click(".show-more-actions");
+
+  assert.ok(exists("#topic"), "The topic was rendered");
+  assert.ok(!exists(".show-post-admin-menu"), "The wrench button was not rendered");
+});
+
+acceptance("Post - Admin Menu", { loggedIn: true });
+
+QUnit.test("Enter as a user with group moderator permissions", async assert => {
+  await visit("/t/topic-for-group-moderators/2480");
+  await click(".show-more-actions");
+  await click(".show-post-admin-menu");
+
+  assert.ok(exists(".add-notice"), "The add notice button was rendered");
+});
diff --git a/test/javascripts/acceptance/topic-admin-menu-test.js b/test/javascripts/acceptance/topic-admin-menu-test.js
index 759f30ba44a..a00a89be629 100644
--- a/test/javascripts/acceptance/topic-admin-menu-test.js
+++ b/test/javascripts/acceptance/topic-admin-menu-test.js
@@ -19,10 +19,10 @@ QUnit.test("Enter as a user with group moderator permissions", async assert => {
   assert.ok(exists(".toggle-admin-menu"), "The admin menu button was rendered");
 });
 
-QUnit.test("Enter as a user with group moderator permissions", async assert => {
+QUnit.test("Enter as a user with moderator and admin permissions", async assert => {
   updateCurrentUser({ moderator: true, admin: true, trust_level: 4 });
 
-  await visit("/t/topic-for-group-moderators/2480");
+  await visit("/t/internationalization-localization/280");
   assert.ok(exists("#topic"), "The topic was rendered");
   assert.ok(exists(".toggle-admin-menu"), "The admin menu button was rendered");
 });
diff --git a/test/javascripts/helpers/create-pretender.js b/test/javascripts/helpers/create-pretender.js
index 050b00d109d..3376b06c4a2 100644
--- a/test/javascripts/helpers/create-pretender.js
+++ b/test/javascripts/helpers/create-pretender.js
@@ -259,6 +259,7 @@ export function applyDefaultHandlers(pretender) {
     const json = fixturesByUrl["/t/34/1.json"];
     json.details.can_archive_topic = true;
     json.details.can_close_topic = true;
+    json.details.can_edit_staff_notes = true;
 
     return response(json);
   });