0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 13:08:40 +08:00
discourse/.skills/discourse-admin-ui/references/plugin-admin-interfaces.md
Martin Brennan 4d7893aa2b
DEV: Add a Discourse admin UI skill (#41040)
- Add a new `discourse-admin-ui` skill distilling
https://meta.discourse.org/t/creating-consistent-admin-interfaces/326780?tl=en
for creating and reviewing consistent Discourse admin interfaces.
- Split guidance into focused references for page shells, content
patterns, filtered settings pages, and plugin admin UIs.
- Include local code anchors so agents can follow current core and
plugin examples without rereading the Meta guide.
2026-06-19 16:19:35 +10:00

159 lines
5.6 KiB
Markdown
Vendored

# Plugin Admin Interfaces
Use this reference when creating or reviewing a custom plugin admin UI under `/admin/plugins/:plugin`.
## When to build one
Build a plugin admin UI when a plugin needs more than a generated settings list: record management, setup workflows, reports, nested pages, custom tables, or multiple related configuration views. If the plugin only exposes site settings, rely on the generated settings page.
Core examples to inspect:
- Discourse AI: `plugins/discourse-ai`
- Data Explorer: `plugins/discourse-data-explorer`
- Chat Integration: `plugins/discourse-chat-integration`
- Gamification: `plugins/discourse-gamification`
## Server-side registration
Use `add_admin_route` in `plugin.rb` and pass `use_new_show_route: true` so the plugin uses the modern plugin show page:
```ruby
add_admin_route("plugin_example.title", "plugin-example", { use_new_show_route: true })
```
Some plugins pass the options hash as the third argument; others use keyword style. Follow nearby style.
Examples:
- `plugins/discourse-ai/plugin.rb`
- `plugins/discourse-data-explorer/plugin.rb`
- `plugins/discourse-chat-integration/plugin.rb`
## Route map
Plugin admin routes should attach to `admin.adminPlugins.show` with `path: "/plugins"`.
Create a route map such as `assets/javascripts/discourse/admin-plugin-example-plugin-route-map.js`:
```js
export default {
resource: "admin.adminPlugins.show",
path: "/plugins",
map() {
this.route("plugin-example-items", { path: "items" }, function () {
this.route("new");
this.route("edit", { path: "/:id/edit" });
});
},
};
```
Current examples:
- `plugins/discourse-ai/assets/javascripts/discourse/admin-discourse-ai-plugin-route-map.js`
- `plugins/discourse-data-explorer/assets/javascripts/discourse/explorer-route-map.js`
- `plugins/discourse-chat-integration/assets/javascripts/discourse/admin-chat-integration-plugin-route-map.js`
## File locations
Modern plugin admin route JS files live under:
```text
admin/assets/javascripts/discourse/routes/admin-plugins/show/
```
Templates live under:
```text
admin/assets/javascripts/discourse/templates/admin-plugins/show/
```
Simple routes usually have one template like:
```text
admin/assets/javascripts/discourse/templates/admin-plugins/show/plugin-example-items.gjs
```
Nested routes use the normal Ember structure with `index.gjs`, `new.gjs`, `edit.gjs`, or route-specific names under the route folder.
## Plugin navigation
Register plugin configuration navigation from an initializer, and only run it for admins:
```js
import { withPluginApi } from "discourse/lib/plugin-api";
const PLUGIN_ID = "plugin-example";
export default {
name: "plugin-example-admin-plugin-configuration-nav",
initialize(container) {
const currentUser = container.lookup("service:current-user");
if (!currentUser?.admin) {
return;
}
withPluginApi((api) => {
api.addAdminPluginConfigurationNav(PLUGIN_ID, [
{
label: "plugin_example.items.short_title",
route: "adminPlugins.show.plugin-example-items",
description: "plugin_example.items.description",
},
]);
});
},
};
```
The plugin's generated settings link is added automatically. Do not duplicate it in `addAdminPluginConfigurationNav`.
Top tabs are preferred for plugin navigation. Avoid introducing an inner sidebar for new plugin UIs.
Example: `plugins/discourse-ai/assets/javascripts/discourse/initializers/admin-plugin-configuration-nav.js`.
## Page structure
The plugin show wrapper renders the main `DPageHeader`; plugin index routes should normally render `DPageSubheader` to describe the selected tab and provide tab-local actions.
Use the same content primitives as core admin pages:
- `AdminConfigAreaCard` for grouped forms and setup areas.
- `AdminConfigAreaEmptyList` for empty createable lists.
- `d-table` classes for growing record lists.
- Third-level `new` and `edit` routes for forms.
- `BackButton` on new/edit pages instead of the full header.
Examples:
- Plugin subheader and table: `plugins/discourse-data-explorer/admin/assets/javascripts/admin/templates/admin-plugins/show/explorer/index.gjs`
- Plugin nested detail page: `plugins/discourse-chat-integration/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-chat-integration-providers/show.gjs`
- Plugin edit card: `plugins/discourse-ai/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs`
## Header actions outlet
If a plugin needs actions in the main plugin page header rather than inside a tab/subsection, render a component into the `admin-plugin-config-page-actions` outlet from the same initializer that registers plugin navigation.
```js
api.renderInOutlet(
"admin-plugin-config-page-actions",
PluginExampleAdminActions
);
```
The outlet receives the plugin model and `DPageHeader` action components through `outletArgs`; use those instead of hand-rolling header buttons.
Implementation anchor: `frontend/discourse/admin/components/admin-plugin-config-page.gjs`.
## Checklist
- Register the plugin admin route with `use_new_show_route: true`.
- Add a plugin route map under `admin.adminPlugins.show`.
- Add route JS and templates under `admin/assets/javascripts/discourse/...`.
- Register top-tab navigation with `api.addAdminPluginConfigurationNav` in an admin-only initializer.
- Use `DPageSubheader` inside plugin index routes.
- Use cards, tables, empty states, FormKit, and third-level routes consistently with core admin pages.
- Confirm direct reload works for every plugin route, including `new` and `edit`.
- Run `bin/lint --fix` on changed files.