diff --git a/app/assets/javascripts/discourse/app/components/plugin-outlet.hbs b/app/assets/javascripts/discourse/app/components/plugin-outlet.hbs
index 15c15cb1c29..496e6a6a682 100644
--- a/app/assets/javascripts/discourse/app/components/plugin-outlet.hbs
+++ b/app/assets/javascripts/discourse/app/components/plugin-outlet.hbs
@@ -22,8 +22,16 @@
       {{/if}}
     {{/each}}
   </this.wrapperComponent>
-{{else if this.connectorsExist}}
+{{else if (this.connectorsExist hasBlock=(has-block))}}
   {{! The modern path: no wrapper element = no classic component }}
+
+  {{#if (has-block)}}
+    <PluginOutlet
+      @name={{concat @name "__before"}}
+      @outletArgs={{this.outletArgsWithDeprecations}}
+    />
+  {{/if}}
+
   {{#each (this.getConnectors hasBlock=(has-block)) as |c|}}
     {{#if c.componentClass}}
       <c.componentClass
@@ -47,6 +55,13 @@
   {{else}}
     {{yield}}
   {{/each}}
+
+  {{#if (has-block)}}
+    <PluginOutlet
+      @name={{concat @name "__after"}}
+      @outletArgs={{this.outletArgsWithDeprecations}}
+    />
+  {{/if}}
 {{else}}
   {{yield}}
 {{/if}}
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/components/plugin-outlet.js b/app/assets/javascripts/discourse/app/components/plugin-outlet.js
index 5c621d18e10..e82a18374c6 100644
--- a/app/assets/javascripts/discourse/app/components/plugin-outlet.js
+++ b/app/assets/javascripts/discourse/app/components/plugin-outlet.js
@@ -102,8 +102,14 @@ export default class PluginOutletComponent extends GlimmerComponentWithDeprecate
     return connectors;
   }
 
-  get connectorsExist() {
-    return connectorsExist(this.args.name);
+  @bind
+  connectorsExist({ hasBlock } = {}) {
+    return (
+      connectorsExist(this.args.name) ||
+      (hasBlock &&
+        (connectorsExist(this.args.name + "__before") ||
+          connectorsExist(this.args.name + "__after")))
+    );
   }
 
   // Traditionally, pluginOutlets had an argument named 'args'. However, that name is reserved
diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js
index 715aee9c39f..8c61ed1ffdf 100644
--- a/app/assets/javascripts/discourse/app/lib/plugin-api.js
+++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js
@@ -142,7 +142,7 @@ import { modifySelectKit } from "select-kit/mixins/plugin-api";
 // docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
 // using the format described at https://keepachangelog.com/en/1.0.0/.
 
-export const PLUGIN_API_VERSION = "1.25.0";
+export const PLUGIN_API_VERSION = "1.26.0";
 
 // This helper prevents us from applying the same `modifyClass` over and over in test mode.
 function canModify(klass, type, resolverName, changes) {
@@ -1005,6 +1005,72 @@ class PluginApi {
     extraConnectorComponent(outletName, klass);
   }
 
+  /**
+   * Render a component before the content of a wrapper outlet and does not override it's content
+   *
+   * For example, if the outlet is `discovery-list-area`, you could register
+   * a component like
+   *
+   * ```javascript
+   * import MyComponent from "discourse/plugins/my-plugin/components/my-component";
+   * api.renderBeforeWrapperOutlet('discovery-list-area', MyComponent);
+   * ```
+   *
+   * Alternatively, a component could be defined inline using gjs:
+   *
+   * ```javascript
+   * api.renderBeforeWrapperOutlet('discovery-list-area', <template>Before the outlet</template>);
+   * ```
+   *
+   * Note:
+   * - the content of the outlet is not overridden when using this API, and unlike the main outlet,
+   *   multiple connectors can be registered for the same outlet.
+   * - this API only works with wrapper outlets. It won't have any effect on standard outlets.
+   * - when passing a component definition to an outlet like this, the default
+   * `@connectorTagName` of the outlet is not used. If you need a wrapper element, you'll
+   * need to add it to your component's template.
+   *
+   * @param {string} outletName - Name of plugin outlet to render into
+   * @param {Component} klass - Component class definition to be rendered
+   *
+   */
+  renderBeforeWrapperOutlet(outletName, klass) {
+    this.renderInOutlet(`${outletName}__before`, klass);
+  }
+
+  /**
+   * Render a component after the content of a wrapper outlet and does not override it's content
+   *
+   * For example, if the outlet is `discovery-list-area`, you could register
+   * a component like
+   *
+   * ```javascript
+   * import MyComponent from "discourse/plugins/my-plugin/components/my-component";
+   * api.renderAfterWrapperOutlet('discovery-list-area', MyComponent);
+   * ```
+   *
+   * Alternatively, a component could be defined inline using gjs:
+   *
+   * ```javascript
+   * api.renderAfterWrapperOutlet('discovery-list-area', <template>After the outlet</template>);
+   * ```
+   *
+   * Note:
+   * - the content of the outlet is not overridden when using this API, and unlike the main outlet,
+   *   multiple connectors can be registered for the same outlet.
+   * - this API only works with wrapper outlets. It won't have any effect on standard outlets.
+   * - when passing a component definition to an outlet like this, the default
+   * `@connectorTagName` of the outlet is not used. If you need a wrapper element, you'll
+   * need to add it to your component's template.
+   *
+   * @param {string} outletName - Name of plugin outlet to render into
+   * @param {Component} klass - Component class definition to be rendered
+   *
+   */
+  renderAfterWrapperOutlet(outletName, klass) {
+    this.renderInOutlet(`${outletName}__after`, klass);
+  }
+
   /**
    * Register a button to display at the bottom of a topic
    *
diff --git a/app/assets/javascripts/discourse/tests/integration/components/plugin-outlet-test.gjs b/app/assets/javascripts/discourse/tests/integration/components/plugin-outlet-test.gjs
index 6f41ab0f6c0..1114ca662ab 100644
--- a/app/assets/javascripts/discourse/tests/integration/components/plugin-outlet-test.gjs
+++ b/app/assets/javascripts/discourse/tests/integration/components/plugin-outlet-test.gjs
@@ -226,6 +226,88 @@ module("Integration | Component | plugin-outlet", function (hooks) {
           .dom(".broken-theme-alert-banner")
           .exists("Error banner is shown to admins");
       });
+
+      test("can render content in a automatic outlet generated before the wrapped content", async function (assert) {
+        registerTemporaryModule(
+          `${TEMPLATE_PREFIX}/outlet-with-default__before/my-connector`,
+          hbs`<span class='before-result'>Before wrapped content</span>`
+        );
+
+        await render(hbs`
+          <PluginOutlet @name="outlet-with-default" @outletArgs={{hash shouldDisplay=true}}>
+            <span class='result'>Core implementation</span>
+          </PluginOutlet>
+        `);
+
+        assert.dom(".result").hasText("Plugin implementation");
+        assert.dom(".before-result").hasText("Before wrapped content");
+      });
+
+      test("can render multiple connector `before` the same wrapped content", async function (assert) {
+        registerTemporaryModule(
+          `${TEMPLATE_PREFIX}/outlet-with-default__before/my-connector`,
+          hbs`<span class='before-result'>First connector before the wrapped content</span>`
+        );
+        registerTemporaryModule(
+          `${TEMPLATE_PREFIX}/outlet-with-default__before/my-connector2`,
+          hbs`<span class='before-result2'>Second connector before the wrapped content</span>`
+        );
+
+        await render(hbs`
+          <PluginOutlet @name="outlet-with-default" @outletArgs={{hash shouldDisplay=true}}>
+            <span class='result'>Core implementation</span>
+          </PluginOutlet>
+        `);
+
+        assert.dom(".result").hasText("Plugin implementation");
+        assert
+          .dom(".before-result")
+          .hasText("First connector before the wrapped content");
+        assert
+          .dom(".before-result2")
+          .hasText("Second connector before the wrapped content");
+      });
+
+      test("can render content in a automatic outlet generated after the wrapped content", async function (assert) {
+        registerTemporaryModule(
+          `${TEMPLATE_PREFIX}/outlet-with-default__after/my-connector`,
+          hbs`<span class='after-result'>After wrapped content</span>`
+        );
+
+        await render(hbs`
+          <PluginOutlet @name="outlet-with-default" @outletArgs={{hash shouldDisplay=true}}>
+            <span class='result'>Core implementation</span>
+          </PluginOutlet>
+        `);
+
+        assert.dom(".result").hasText("Plugin implementation");
+        assert.dom(".after-result").hasText("After wrapped content");
+      });
+
+      test("can render multiple connector `after` the same wrapped content", async function (assert) {
+        registerTemporaryModule(
+          `${TEMPLATE_PREFIX}/outlet-with-default__after/my-connector`,
+          hbs`<span class='after-result'>First connector after the wrapped content</span>`
+        );
+        registerTemporaryModule(
+          `${TEMPLATE_PREFIX}/outlet-with-default__after/my-connector2`,
+          hbs`<span class='after-result2'>Second connector after the wrapped content</span>`
+        );
+
+        await render(hbs`
+          <PluginOutlet @name="outlet-with-default" @outletArgs={{hash shouldDisplay=true}}>
+            <span class='result'>Core implementation</span>
+          </PluginOutlet>
+        `);
+
+        assert.dom(".result").hasText("Plugin implementation");
+        assert
+          .dom(".after-result")
+          .hasText("First connector after the wrapped content");
+        assert
+          .dom(".after-result2")
+          .hasText("Second connector after the wrapped content");
+      });
     }
   );
 
diff --git a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md
index 9e826ff51e6..97ef5f3b1a0 100644
--- a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md
+++ b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md
@@ -7,6 +7,11 @@ in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [1.26.0] - 2024-02-21
+
+- Added `renderBeforeWrapperOutlet` which is used for rendering components before the content of wrapper plugin outlets 
+- Added `renderAfterWrapperOutlet` which is used for rendering components after the content of wrapper plugin outlets 
+
 ## [1.25.0] - 2024-02-05
 
 - Added `addComposerImageWrapperButton` which is used to add a custom button to the composer preview's image wrapper that appears on hover of an uploaded image.