Express + TypeORM
Install paneljs, @paneljs/express, and @paneljs/typeorm into your Express + TypeORM app and open /admin.
Requirements
- Node.js
^20.19 || ^22.12 || >=24or Bun>=1.3 - Express 4 or 5
typeorm^0.3.20- An initialized TypeORM
DataSourcewith your entities registered
Introspection reads live entity metadata. The DataSource must already be initialize()d before you pass it to the adapter.
Install
CLI, from the root of this app (does not rewrite source):
npx paneljs@latest init --framework express --orm typeorm --yesOr add the packages yourself:
$ npm install paneljs @paneljs/express @paneljs/typeorm$ pnpm add paneljs @paneljs/express @paneljs/typeorm$ yarn add paneljs @paneljs/express @paneljs/typeorm$ bun add paneljs @paneljs/express @paneljs/typeormExpress and TypeORM are peers. If this app does not have them yet:
$ npm install express typeorm$ pnpm add express typeorm$ yarn add express typeorm$ bun add express typeormAdd it to your server
Initialize the DataSource first, then create the admin:
import express from "express";
import { createAdmin } from "paneljs";
import { mount } from "@paneljs/express";
import { typeormAdapter } from "@paneljs/typeorm";
import { dataSource } from "./data-source.js";
await dataSource.initialize();
const app = express();
const admin = createAdmin({
adapter: typeormAdapter({ dataSource }),
siteName: "Admin",
auth: {
getCurrentUser: async (req) => {
const user = await getOperatorFromYourAuth(req);
if (!user) return null;
return {
id: user.id,
email: user.email,
role: user.role,
isSuperAdmin: user.role === "SUPER_ADMIN",
};
},
},
});
admin.register("User");
admin.register("Post");
await mount(app, admin);
app.listen(3000);Replace "User" / "Post" with entity names registered on the DataSource (@Entity() class name or EntitySchema name), PascalCase, exact.
typeormAdapter throws if the DataSource is not initialized.
Built-in login
If you want /admin/login instead of getCurrentUser, add the auth entities to the DataSource and switch auth mode:
import { builtInAuthEntities, typeormAdapter } from "@paneljs/typeorm";
const dataSource = new DataSource({
type: "postgres",
url: process.env.DATABASE_URL,
entities: [...appEntities, ...builtInAuthEntities({ identifier: "email" })],
});
await dataSource.initialize();
const admin = createAdmin({
adapter: typeormAdapter({ dataSource }),
auth: { mode: "built-in", identifier: "email" },
});Do not register("ExpressAdminUser"). See Authentication.
Create the first operator with npx paneljs createsuperuser --config ./paneljs.config.mjs. The config must export an initialized typeormAdapter and the built-in auth settings. See Authentication.
Open the admin
Start your server and visit:
http://localhost:3000/adminYou should see a sidebar with every entity you registered. register("User") with no second argument is enough for a list and a form. Defaults come from entity metadata.
What to do next
- Wire it into your app —
listDisplay,searchFields,listFilter,scope, actions, audit - Authentication — built-in login or an existing session
- TypeORM adapter —
DataSource,builtInAuthEntities,typeormActionWhere - How it works — why
mountis async