Admin UI
Contribute admin menu items, pages, settings, and collection-view overrides from a plugin — and control where they appear in the sidebar.
A plugin can extend the Nextly admin in two complementary ways:
contributes.admin— the declarative surface: menu entries, custom pages, a settings panel, and per-collection view overrides (D19–D23).admin(on the plugin definition) — placement & appearance: where the plugin's items sit in the sidebar and how they look (D20).
Both are @public. Admin dashboard widgets (PluginAdminWidget, D22) are reserved
but not yet rendered — they remain @experimental until M8.
Component paths
Admin contributions reference React components by string path, not by import, so the declarative surface stays serializable and Node-safe:
"<package>/<subpath>#<ExportName>"
// e.g. "@acme/nextly-plugin-reports/admin#ReportsView"Register the components those paths resolve to from your plugin's /admin entry, which
runs inside the admin shell:
// src/admin/index.ts
import { registerComponents } from "@nextlyhq/plugin-sdk/admin";
import { ReportsView } from "./ReportsView";
registerComponents({
"@acme/nextly-plugin-reports/admin#ReportsView": ReportsView,
});Running nextly generate:types emits a plugin-admin-imports.generated.ts import map so
these load with no manual host wiring (D60).
UI building blocks (@nextlyhq/ui)
Build your admin components out of the shared plugin UI kit, @nextlyhq/ui — the
semver-protected surface of React primitives the admin itself is built from (D68/D53).
The host provides it (and React) as a peer dependency, so you and the admin share one
version and one theme:
// src/admin/ReportsView.tsx
import {
Button,
Input,
Checkbox,
Select,
Card,
Table,
FormLabelWithTooltip,
} from "@nextlyhq/ui";Register components and reference admin contribution types through
@nextlyhq/plugin-sdk/admin; import UI primitives from @nextlyhq/ui.
Never import from
@nextlyhq/admin. It is an application, not a published API — reaching into it (e.g.@nextlyhq/admin/lib/*) is unsupported and will break. If a primitive you need isn't yet exported from@nextlyhq/ui, open an issue so it can be promoted to the kit rather than copied.
Declarative contributions
contributes: {
admin: {
// Sidebar entries (D20). One level of children is supported.
menu: [
{ label: "Reports", to: "/admin/plugins/reports", icon: "BarChart",
order: 10, requiredPermission: "read-reports" },
],
// Custom pages, namespaced under /admin/plugins/<slug>/<path>, RBAC-gated (D21).
pages: [
{ path: "summary", component: "@acme/nextly-plugin-reports/admin#ReportsView",
requiredPermission: "read-reports" },
],
// A settings panel rendered at /admin/plugins/<slug> (D21).
settings: { component: "@acme/nextly-plugin-reports/admin#ReportsSettings" },
// Per-collection view overrides + injection points, keyed by slug (D23).
views: {
posts: {
beforeList: "@acme/nextly-plugin-reports/admin#PostsBanner",
edit: "@acme/nextly-plugin-reports/admin#PostsEditor",
},
},
},
}menu/pages/widgets honour requiredPermission for client-side gating (see
Permissions). View override slots are list, edit,
beforeList, afterList, beforeEdit, afterEdit.
Dashboard widgets (reserved)
contributes.admin.widgets (D22) is a forward-looking contract: the PluginAdminWidget
shape is published so plugin authors can design against it, but widget rendering and the
dashboard grid are deferred to a later milestone (M8) — declaring widgets has no effect
yet. It stays @experimental until the dashboard ships.
contributes: {
admin: {
widgets: [
{
id: "reports-summary",
component: "@acme/nextly-plugin-reports/admin#SummaryWidget",
size: "half", // "full" | "half"
requiredPermission: "read-reports", // client-gated
},
],
},
}Declaring widgets now is safe (it's validated but ignored); they'll light up when the
dashboard lands. Everything else on this page is @public today.
Placement & appearance
The plugin's own admin field controls where its items live (D20):
import { AdminPlacement } from "nextly";
definePlugin({
// ...
admin: {
placement: AdminPlacement.COLLECTIONS, // COLLECTIONS | SINGLES | USERS | SETTINGS | PLUGINS | STANDALONE
order: 10, // lower = higher; default 100
after: "collections", // anchor for STANDALONE icons
},
});Placement defaults to PLUGINS. STANDALONE gives the plugin its own top-level icon,
anchored after the section named by after.
Host overrides (D49)
An integrator can override a plugin's placement without forking it, via
defineConfig({ admin: { pluginOverrides } }) — keyed by plugin name:
// nextly.config.ts
export default defineConfig({
plugins: [reports()],
admin: {
pluginOverrides: {
"@acme/nextly-plugin-reports": {
placement: AdminPlacement.SETTINGS, // move it
order: 5,
after: "users",
appearance: { label: "Analytics" }, // shallow-merged
},
},
},
});The override wins over the plugin's own admin config, so operators stay in control of
their sidebar.
See also: Permissions · Author guide.