AdminCP Pages
Create powerful admin interfaces for your plugins with protected pages, custom layouts, and integrated navigation.
Pages & Layouts
AdminCP follows the same patterns like from Layouts and Pages but uses the src/routes/admin directory. Like everywhere in VitNode, render text with translations - see Messages.
Pages
import { getTranslations } from "next-intl/server";
export default async function Page() {
const t = await getTranslations("@vitnode/blog.admin.settings");
return (
<div>
<h1>{t("title")}</h1>
<p>{t("desc")}</p>
</div>
);
}Layouts
import { getTranslations } from "next-intl/server";
export default async function AdminRootLayout({
children,
}: {
children: React.ReactNode;
}) {
const t = await getTranslations("@vitnode/blog.admin.settings");
return (
<div className="min-h-screen bg-gray-50">
<header className="border-b bg-white p-4">
<h1 className="text-xl font-semibold">{t("title")}</h1>
</header>
{children}
</div>
);
}All pages & layouts in the src/routes/admin directory are automatically
protected and require admin authentication.
Navigation items
To create own navigation items in the admin sidebar you can just create nav object inside admin in your plugin config.
Define items in config
As an example let's create a navigation item for blog categories:
import { buildPlugin } from "@vitnode/core/lib/plugin";
import { SettingsIcon } from "lucide-react";
import { configPlugin } from "./config";
export const blogPlugin = () => {
return buildPlugin({
...configPlugin,
admin: {
nav: [
{
id: "settings",
href: "/admin/blog/settings",
icon: <SettingsIcon />,
},
],
},
});
};Nested Navigation
To create nested navigation items, you can use the items property:
import { buildPlugin } from "@vitnode/core/lib/plugin";
import { ListIcon, SettingsIcon } from "lucide-react";
import { configPlugin } from "./config";
export const blogPlugin = () => {
return buildPlugin({
...configPlugin,
admin: {
nav: [
{
id: "settings",
href: "/admin/blog/settings",
icon: <SettingsIcon />,
items: [
{
id: "general",
href: "/admin/blog/settings/general",
},
{
id: "seo",
href: "/admin/blog/settings/seo",
},
],
},
],
},
});
};Open in new tab
To open a navigation item in a new tab, you can use the isOpenInNewTab property:
import { buildPlugin } from "@vitnode/core/lib/plugin";
import { SettingsIcon } from "lucide-react";
import { configPlugin } from "./config";
export const blogPlugin = () => {
return buildPlugin({
...configPlugin,
admin: {
nav: [
{
id: "settings",
href: "/admin/blog/settings",
icon: <SettingsIcon />,
isOpenInNewTab: true,
},
],
},
});
};Gate by permission
To hide a navigation item from admins who lack a staff permission, add a permission. It takes { module, permission } - the plugin is inferred from your plugin, so you only reference the module and permission id you declared in permissionStaff:
import { buildPlugin } from "@vitnode/core/lib/plugin";
import { ListIcon } from "lucide-react";
import { configPlugin } from "./config";
export const blogPlugin = () => {
return buildPlugin({
...configPlugin,
admin: {
nav: [
{
id: "categories",
href: "/admin/blog/categories",
icon: <ListIcon />,
permission: { module: "categories", permission: "can_view" },
},
],
},
});
};The same permission field works on nested items. A parent item disappears once all of its children are hidden, and items without a permission are always shown.
Hiding a nav item only hides the link - it does not protect the route. Guard the page itself and the underlying API too. See Staff Permissions for the full pattern.
Add translation
VitNode automatically uses the id as translation key for the navigation item.
{
"@vitnode/blog": {
"title": "Blog",
"admin": {
"nav": {
"settings": "Settings"
}
}
}
}