Code snippets
This is the documentation for the latest version of Next Admin. If you are using an older version (<5.0.0
), please refer to the documentation
This page contains code snippets that you can use in your projects. These are not a part of Next Admin, but may be useful for your projects.
Some of the snippets are implemented in the example project. You can check them out in the example project or in the source code.
Authentication
The library does not provide an authentication system. If you want to add your own, you can do so by adding a role check in the page:
The following example uses next-auth to handle authentication
const { run } = createHandler({
options,
prisma,
apiBasePath: "/api/admin",
schema,
onRequest: (req) => {
const session = await getServerSession(authOptions);
const isAdmin = session?.user?.role === "SUPERADMIN";
if (!isAdmin) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
}
});
export { run as POST, run as GET, run as DELETE };
export default async function AdminPage({
params,
searchParams,
}: {
params: { [key: string]: string[] };
searchParams: { [key: string]: string | string[] | undefined } | undefined;
}) {
const session = await getServerSession(authOptions);
const isAdmin = session?.user?.role === "SUPERADMIN"; // your role check
if (!isAdmin) {
redirect('/', { permanent: false })
}
const props = await getNextAdminProps({
params: params.nextadmin,
searchParams,
basePath: "/admin",
apiBasePath: "/api/admin",
prisma,
schema,
});
return <NextAdmin {...props} dashboard={Dashboard} />;
}
Export data
By using exports options, you can export data. Next Admin only implements the CSV export button, it’s actually a link pointing to a provided url.
The API endpoint route must be defined in the exports
property of the options object. This is an example of how to implement the export API endpoint:
import { prisma } from "@/prisma";
export async function GET() {
const users = await prisma.user.findMany();
const csv = users.map((user) => {
return `${user.id},${user.name},${user.email},${user.role},${user.birthDate}`;
});
const headers = new Headers();
headers.set("Content-Type", "text/csv");
headers.set("Content-Disposition", `attachment; filename="users.csv"`);
return new Response(csv.join("\n"), {
headers,
});
}
or with a stream response:
import { prisma } from "@/prisma";
const BATCH_SIZE = 1000;
export async function GET() {
const headers = new Headers();
headers.set("Content-Type", "text/csv");
headers.set("Content-Disposition", `attachment; filename="users.csv"`);
const stream = new ReadableStream({
async start(controller) {
try {
const batchSize = BATCH_SIZE;
let skip = 0;
let users;
do {
users = await prisma.user.findMany({
skip,
take: batchSize,
});
const csv = users
.map((user) => {
return `${user.id},${user.name},${user.email},${user.role},${user.birthDate}\n`;
})
.join("");
controller.enqueue(Buffer.from(csv));
skip += batchSize;
} while (users.length === batchSize);
} catch (error) {
controller.error(error);
} finally {
controller.close();
}
},
});
return new Response(stream, { headers });
}
Note that you must secure the export route if you don’t want to expose your data to the public by adding authentication middleware.
There are two example files in the example project:
Intercepting data submission
If you want to add data to the form data before submitting it, you can make use of the edit.hooks
field of a model options. This is an example of how to add createdBy
and updatedBy
fields on a post based on the user id, and submit an email to inform the creation or edition of the post:
{
model: {
Post: {
edit: {
hooks: {
async beforeDb(data, mode, request) {
const userId = 1 // retrieve from the request / cookies, etc
// your own permission check
if (!userHasPermission(userId)) {
throw new HookError(403, { error: "Forbidden" });
}
if (mode === "create") {
data.createdBy = userId;
} else {
data.updatedBy = userId;
}
return data
},
async afterDb(data, mode, request) {
const userId = 1 // retrieve from the request / cookies, etc
sendMail({
to: "some@email.com",
subject: "Post updated",
text: `Post ${data.title} has been ${mode === "create" ? "created" : "updated"} by ${userId}`,
})
}
}
}
}
}
}
Note that this example assumes that you have a
createdBy
andupdatedBy
field on each model, if you need to check the model name, you can useparams.params[0]
.
This snippet is not implemented in the example project.
Custom input form
If you want to customize the input form, you can create a custom input component. This is an example of how to create a custom input component for the birthDate
field:
"use client";
import { CustomInputProps } from "@premieroctet/next-admin";
import DateTimePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
type Props = CustomInputProps;
const BirthDateInput = ({ value, name, onChange, disabled, required }: Props) => {
return (
<>
<DateTimePicker
selected={value ? new Date(value) : null}
onChange={(date) =>
onChange?.({
// @ts-expect-error
target: { value: date?.toISOString() ?? new Date().toISOString() },
})
}
showTimeSelect
dateFormat="dd/MM/yyyy HH:mm"
timeFormat="HH:mm"
wrapperClassName="w-full"
disabled={disabled}
required={required}
className="dark:bg-dark-nextadmin-background-subtle dark:ring-dark-nextadmin-border-strong text-nextadmin-content-inverted dark:text-dark-nextadmin-content-inverted ring-nextadmin-border-default focus:ring-nextadmin-brand-default dark:focus:ring-dark-nextadmin-brand-default block w-full rounded-md border-0 px-2 py-1.5 text-sm shadow-sm ring-1 ring-inset transition-all duration-300 placeholder:text-gray-400 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:leading-6 [&>div]:border-none"
/>
<input type="hidden" name={name} value={value ?? ""} />
</>
);
};
export default BirthDateInput;
The CustomInputProps
type is provided by Next Admin.
Note that we use a hidden input to store the value because the
DateTimePicker
component needs a different value format than what is expected by the form submission.
You can find an example of this component in the example project:
Explicit many-to-many
You might want to add sorting on a relationship, for example sort the categories of a post in a specific order. To achieve this, you have to explicitly define a model in the Prisma schema that will act as the join table. This is an example of how to implement this:
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation("author", fields: [authorId], references: [id]) // Many-to-one relation
authorId Int
categories CategoriesOnPosts[]
rate Decimal? @db.Decimal(5, 2)
order Int @default(0)
}
model Category {
id Int @id @default(autoincrement())
name String
posts CategoriesOnPosts[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
}
model CategoriesOnPosts {
id Int @default(autoincrement())
post Post @relation(fields: [postId], references: [id])
postId Int
category Category @relation(fields: [categoryId], references: [id])
categoryId Int
order Int @default(0)
@@id([postId, categoryId])
}
In the Next Admin options, you will then need to define, on a specific field, which field in the join table will be used for sorting:
{
model: {
Post: {
edit: {
fields: {
categories: {
relationOptionFormatter: (category) => {
return `${category.name} Cat.${category.id}`;
},
display: "list",
orderField: "order", // The field used in CategoriesOnPosts for sorting
relationshipSearchField: "category", // The field to use in CategoriesOnPosts
},
}
}
}
}
}
Note that you will need to use relationOptionFormatter
instead of optionFormatter
to format the content of the select input.
With the list
display, if the orderField
property is defined, you will be able to apply drag and drop sorting on the categories. Upon form submission, the order will be updated accordingly, starting from 0.
The
orderField
property can also be applied for one-to-many relationships. In that case, drag and drop will also be available.
Custom pages
You can create custom pages in the Next Admin UI. By reusing the MainLayout
component, you can create a new page with the same layout as the Next Admin pages. This is an example of how to create a custom page:
import { MainLayout } from "@premieroctet/next-admin";
import { getMainLayoutProps } from "@premieroctet/next-admin/appRouter";
import { options } from "@/options";
import { prisma } from "@/prisma";
const CustomPage = async () => {
const mainLayoutProps = getMainLayoutProps({
basePath: "/admin",
apiBasePath: "/api/admin",
/*options*/
});
return (
<MainLayout {...mainLayoutProps}>
{/*Page content*/}
</MainLayout>
);
};
export default CustomPage;
Then, if you want that route to be available on the sidebar, you can add a new route - more info:
{
[...]
pages: {
"/custom": {
title: "Custom page",
icon: "PresentationChartBarIcon",
},
},
[...]
}