AdminJSは、Node.jsのサーバーにデータベースのモデルを渡すだけで、一覧・検索・作成・編集・削除の管理画面を自動生成するオープンソースのフレームワークです。この記事では、2026年10月27日時点のnpm最新版AdminJS 7.8.17をExpressとPrismaで実際に動かし、導入手順・認証・カスタマイズに加えて、今の依存関係で入らない組み合わせや、ログインが止まる設定の落とし穴まで、実行結果をもとに説明します。最終リリースから1年以上更新が止まっている点を踏まえた採用判断も最後に書きます。
まとめ:AdminJS導入の要点(2026年10月時点)
- 役割:ORMのモデルからCRUD画面を自動生成する。本体はMITライセンスで無料。リレーション管理の
@adminjs/relationsだけは買い切りの有料アドオン。 - 動作要件:7系はESM形式だけで配布。
package.jsonを"type": "module"にしてimportで書くのが公式の前提。enginesの指定はNode 16以上で、Node 26.5.0でも起動を確認。 - 入らない組み合わせ:
@adminjs/prismaはPrisma 5・6まで、@adminjs/typeormはTypeORM 0.3系まで。現行のPrisma 7.10.1やTypeORM 1.1.2と一緒に入れるとnpmがERESOLVEで止まる。 - 止まる設定:
express.json()などのボディパーサーをAdminJSより前に置くと、Express 4ではログインが500、Express 5では応答が返らない。AdminJSのルーターを先にマウントする。 - 採用判断:本体の最終リリースは2025年7月15日。社内向けの小さな管理画面を短期間で用意するなら今も選べるが、長く育てる管理画面ならReact-adminやRefineを先に検討する。
AdminJSとは:モデルから管理画面を生成するNode.jsツール
AdminJSはSoftwareBrothersが開発し、GitHubのスター数は8,994(2026年10月27日時点)です。Express・Fastify・NestJS・Koa・Hapi・Adonis向けのプラグインがあり、既存のNode.jsサーバーに組み込む形で動きます。Node.js自体の位置づけや版の選び方はNode.jsとは?仕組み・版の選び方・標準機能で動かす手順を実装目線で解説を参照してください。
自動生成の仕組み:アダプタ・リソース・アクション
AdminJSは3つの部品で画面を組み立てます。アダプタがORM(Prisma・TypeORM・Sequelize・Mongooseなど)のモデル定義を読み、リソースが1モデル=1メニューとして一覧・詳細・編集フォームを作り、アクション(list・show・new・edit・delete・bulkDelete・search)が各操作を受け持ちます。モデルを1つ登録すれば画面が1つ増え、列の型から入力欄の種類(文字・数値・真偽値・日付)が決まります。
画面はReactで描かれ、ブラウザは/admin/api/resources/<リソース名>/actions/<アクション名>というJSON APIを呼びます。後述の検証でも、このAPIへフォームデータを送るとレコードが作成されました。認証を付けないとこのAPIも誰でも呼べる状態になるため、公開サーバーに置くなら認証は必須です。
ライセンス・動作要件と有料アドオン
本体adminjsはMITライセンスで、npmの最新版は7.8.17(2025年7月15日公開)です。7.0.0(2023年4月)からESM形式だけで配布されており、公式ドキュメントもimportでの読み込みを前提にしています。なお、ESMのrequireに対応したNode.js 26.5.0ではrequire('adminjs').defaultでも読み込めました。
公式ドキュメントによると、多対多などのリレーションを画面で編集する@adminjs/relationsは有料機能(買い切り)で、購入するとライセンスキーが発行される仕組みです。一方、@adminjs/expressや@adminjs/themesなどはnpm上のライセンス表記が「SEE LICENSE IN LICENSE」になっているため、社内の利用規程でOSSライセンスを確認する運用なら、同梱のLICENSEファイルを読んでから導入してください。
AdminJSをExpressに組み込む手順
ここからは、Node.js 26.5.0・npm 11.17.0・Express 5.3.0・@adminjs/express 6.1.1の組み合わせで実際に動かした手順です。
ESMプロジェクトの設定と依存パッケージの導入
最初にpackage.jsonを"type": "module"にします。こうしておくと.jsのファイルがESMとして扱われ、以降のコードをimport文のまま実行できます(.mjsの拡張子にする方法でも同じです)。express-formidableとexpress-sessionは@adminjs/expressのpeer依存なので、自分で入れます。
mkdir admin-panel && cd admin-panel
npm init -y
npm pkg set type=module
npm i [email protected] @adminjs/[email protected] [email protected] [email protected] [email protected]
npm i @adminjs/[email protected] @prisma/[email protected] [email protected]
版は検証に使ったものに固定しています。最後の行のとおり、Prismaは6系を明示します。理由は「Prisma 7・TypeORM 1.xとアダプタのpeer依存制約」の節で説明します。
Prismaモデルの定義とSQLiteへの反映
例として投稿テーブル1つのスキーマを作り、SQLiteに反映します。prisma db pushはクライアントの生成(Prisma Client 6.19.3)まで行います。
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(false)
createdAt DateTime @default(now())
}
npx prisma db push
DefaultAuthProviderによる認証と管理画面の起動
次のコードをapp.jsとして保存し、ADMIN_EMAIL・ADMIN_PASSWORD・COOKIE_SECRET(openssl rand -hex 32などで作る32バイト以上の乱数)を環境変数に入れてnode app.jsで起動します。http://localhost:3000/adminでログイン画面が開き、左メニューに「Post」が出ます。
import AdminJS, { ComponentLoader, DefaultAuthProvider } from 'adminjs'
import AdminJSExpress from '@adminjs/express'
import { Database, Resource, getModelByName } from '@adminjs/prisma'
import { PrismaClient } from '@prisma/client'
import express from 'express'
AdminJS.registerAdapter({ Database, Resource })
const prisma = new PrismaClient()
const componentLoader = new ComponentLoader()
const admin = new AdminJS({
rootPath: '/admin',
componentLoader,
resources: [
{
resource: { model: getModelByName('Post'), client: prisma },
options: {
properties: {
createdAt: { isVisible: { list: true, show: true, edit: false, filter: true } },
},
},
},
],
})
const provider = new DefaultAuthProvider({
componentLoader,
authenticate: async ({ email, password }) =>
email === process.env.ADMIN_EMAIL && password === process.env.ADMIN_PASSWORD
? { email }
: null,
})
const adminRouter = AdminJSExpress.buildAuthenticatedRouter(
admin,
{ cookieName: 'adminjs', cookiePassword: process.env.COOKIE_SECRET, provider },
null,
{ resave: false, saveUninitialized: false },
)
if (process.env.NODE_ENV !== 'production') await admin.watch()
const app = express()
app.use(admin.options.rootPath, adminRouter) // AdminJSを先にマウントする
app.use(express.json()) // 自前APIのパーサーはAdminJSより後ろ
app.listen(3000, () => console.log('http://localhost:3000/admin'))
このコードで、ログインのPOSTは302で/adminへ転送され、取得したCookieを付けて/admin/api/resources/Post/actions/newへフォームを送ると「successfullyCreated」が返り、一覧APIに作成したレコードが並びました。誤ったパスワードではログイン画面(200)が再表示され、未ログインでAPIを呼ぶと/admin/loginへ302で戻されます。
認証の書き方は、7.4.0で入ったDefaultAuthProviderを使っています。公式ドキュメントは、従来のauthenticateオプションを次のメジャー版で削除すると予告しています。authenticateとproviderを両方渡すとbuildAuthenticatedRouterは例外で止まるため、どちらか一方にします。なお、このルーターはセッションの署名にcookiePasswordを使い、第4引数で渡したsecretは上書きされます。
サンプルは平文で比較していますが、実運用ではユーザーテーブルに保存したハッシュと照合してください。またNODE_ENV=productionで起動すると、express-sessionが「既定のMemoryStoreは1プロセスを超えて拡張できない」と警告します。本番運用では単一プロセスでもMemoryStoreを避け、第4引数のstoreにRedisやPostgreSQLなどの本番用セッションストアを渡します。
Express 5・NestJS 11のボディパーサーとマウント順序
AdminJSのフォーム送信はexpress-formidableがリクエスト本文を読みます。その前にexpress.json()やexpress.urlencoded()が本文を読み切ると、AdminJS側は空のストリームを待つことになります。順番を変えて実測した結果は次のとおりです。
| 構成 | ログインPOSTの結果 |
|---|---|
| Express 4.22.3・パーサーなし | 302(成功) |
| Express 4.22.3・パーサーが先 | 500(エラー) |
| Express 5.3.0・パーサーなし | 302(成功) |
| Express 5.3.0・パーサーが先 | 8秒待っても応答なし |
| Express 5.3.0・AdminJSが先、パーサーが後 | 302(成功)・自前APIも200 |
Express 4で500になるのは、@adminjs/expressがreq._bodyの有無で旧body-parserの使用を検出し、OldBodyParserUsedErrorを返すからです。Express 5ではこの検出に掛からず、エラーも出ないまま止まります。原因に気づきにくいのはExpress 5のほうです。
NestJS 11は内部でExpress 5を使い、既定でボディパーサーが有効なため、同じ停止が起きます。AdminJSのIssue #1739にはNest 11で動かなくなった報告が続いており、Nest 10へ戻した例もあります。公式ドキュメントのNestの項自体も、NestJSがESMに対応していないため動的importでの回避策になると断ったうえで、@adminjs/expressを使うよう勧めています。Nestの既存ミドルウェア設定への影響を避けたい場合は、AdminJSを別のExpressプロセスに分離する構成を推奨します。ただし、認証連携とプロセスの運用管理は別途必要です。
PrismaなどのORMアダプタによるデータベース接続
AdminJSはORMごとのアダプタをAdminJS.registerAdapter({ Database, Resource })で登録してから使います。PrismaではgetModelByName('Post')でモデル定義を取り出し、clientにPrismaClientのインスタンスを渡します。登録を忘れると、NoResourceAdapterError(There are no adapters supporting one of the resource you provided)で起動が止まります。
Prisma 7・TypeORM 1.xとアダプタのpeer依存制約
2026年10月27日時点で、各アダプタの最新版が受け付けるORMの版は次のとおりです(npmレジストリのpeerDependencies)。
| アダプタ(最新版) | 対応するORM | ORMの最新版 | 現行版と同時に入るか |
|---|---|---|---|
| @adminjs/prisma 5.0.4 | @prisma/client 5・6 | 7.10.1 | 入らない(ERESOLVE) |
| @adminjs/typeorm 5.0.1 | typeorm 0.3系 | 1.1.2 | 入らない(ERESOLVE) |
| @adminjs/sequelize 4.1.1 | sequelize 6以上 | 6.37.8 | peer上は入る |
| @adminjs/mongoose 4.1.0 | mongoose 8以上 | 9.11.4 | peer上は入る |
実際にnpm i @adminjs/prisma @prisma/client@7 prisma@7を実行すると、「peer @prisma/client@”^5.0.0 || ^6.0.0″ from @adminjs/[email protected]」でインストールが止まりました。--legacy-peer-depsで押し込むことはできますが、@adminjs/prismaのIssue #63(Prisma v7対応の要望、2025年12月)には、Prisma 6.7で新しいクエリコンパイラに切り替えただけで「Resource does not have an id property」が出たという報告があり、7系での動作は保証されません。
アプリ本体がPrisma 7へ上がっている場合の選択肢は3つです。管理画面用の別パッケージでPrisma 6.19.3(npmのprevタグ)を固定する、Prismaを通さずデータベースを直接読む@adminjs/sql(2.2.6、PostgreSQLとMySQL向け)に切り替える、AdminJS以外の管理画面を選ぶ、のいずれかです。版の固定方法はPrismaのバージョン一覧|最新は7.10.1、Prisma 8はRC(2026年10月)確認方法と7への固定にまとめています。TypeORMを使っている場合も同様に0.3系(legacyタグは0.3.31)で固定します。TypeORMの1.1系での書き方はTypeORMとは?1.1系の使い方|エンティティ・CRUD・リレーション・マイグレーションを実行して解説を参照してください。
プロパティの表示制御とアクションの追加
自動生成された画面は、リソースのoptionsで調整します。よく使うのは次の4つです。
properties.<列名>.isVisible:一覧(list)・詳細(show)・編集(edit)・絞り込み(filter)ごとに表示を切り替える。上のコードでは作成日時を編集画面から外している。properties.<列名>.type:'textarea'や'richtext'で入力欄を変える。listProperties・editProperties:画面ごとに表示する列と順番を配列で指定する。actions:delete: { isAccessible: false }, bulkDelete: { isAccessible: false }で単体削除と一括削除を禁止したり、isAccessibleに関数を渡してログインユーザーの役割で出し分けたりする。独自の処理を持つアクションもactionType: 'record'などで追加できる。
権限の制御にはisAccessibleを使います。ソースでは、アクションの実行前にisAccessibleを判定し、許可されなければForbiddenErrorを投げるため、画面のボタンだけでなくAPIを直接呼ばれた場合も拒否されます。
管理画面UIのコンポーネント差し替えと配色変更
ComponentLoaderによるReactコンポーネントの登録
7系では、自作のReactコンポーネントをComponentLoaderに登録し、ダッシュボードやプロパティの表示に割り当てます。6系までのAdminJS.bundle()は使えません。部品は@adminjs/design-systemから読み込むと、標準画面と見た目がそろいます。次の設定例は既存のcomponentLoaderを使い、Dashboardの登録を既存のAdminJS生成処理より前に置いて、dashboardオプションを追加します。
// components/dashboard.jsx
import React from 'react'
import { Box, H2, Text } from '@adminjs/design-system'
const Dashboard = () => (
<Box variant="grey">
<H2>社内管理画面</H2>
<Text>今日の未公開記事を確認してください</Text>
</Box>
)
export default Dashboard
import path from 'node:path'
const Components = {
Dashboard: componentLoader.add('Dashboard', path.resolve('components/dashboard.jsx')),
}
const admin = new AdminJS({
rootPath: '/admin',
componentLoader,
dashboard: { component: Components.Dashboard },
})
開発時はadmin.watch()が変更を検知して.adminjs/bundle.jsを作り直します。実測では起動後に「Finish bundling: 2 files」と出て、/admin/frontend/assets/components.bundle.jsに登録したDashboardが含まれていました。
テーマ・ブランディングとTailwind CSSの扱い
見た目の変更は、まずbrandingオプションで行います。companyName・logo・withMadeWithLove: false(「Made with ♥ by AdminJS」の非表示)と、theme.colors.primary100などの色指定が使えます。配色一式を切り替えるなら@adminjs/themes(1.0.1)があります。
Tailwind CSSについては、標準画面がstyled-componentsで作られた@adminjs/design-system(4.1.1)で描かれているため、Tailwindのクラスで標準画面を書き換える公式の方法はありません。Tailwindを使えるのは、ComponentLoaderで差し込んだ自作コンポーネントの中だけです。その場合もTailwindのビルド結果のCSSをassets.stylesで読み込む必要があり、標準画面のCSSと競合しないよう、クラスの適用範囲を自作部分に限定してください。
本番バンドル・pnpmの依存解決・脆弱性監査
本番用バンドルの起動時生成と事前生成
adminjsのソースを読むと、NODE_ENV=productionのときはadmin.watch()が何もせず、代わりに@adminjs/expressのルーター作成時に呼ばれるinitialize()が自作コンポーネントを1回だけバンドルし、圧縮します。上のapp.jsをNODE_ENV=productionで起動しても、ログイン・作成・一覧まで同じように動きました。
本番の/admin/frontend/assets/components.bundle.jsは、このとき書き出された.adminjs/bundle.js(出力先は環境変数ADMIN_JS_TMP_DIRで変更可)がそのまま配信されます。そのため、コンテナのイメージ作成時に一度バンドルを作って含めておけば、実行時はADMIN_JS_SKIP_BUNDLE=trueでバンドル処理を省き、起動を速くできます。
pnpm・Bunで出るtiptapの「canInsertNode」エラー
pnpm 11.15.1で同じ構成を入れると、起動直後に次のエラーで止まりました。npm 11.17.0では起動できています。
SyntaxError: The requested module '@tiptap/core' does not provide an export named 'canInsertNode'
原因は依存の版のずれです。@adminjs/[email protected]は@tiptap/coreを2.1.13に固定していますが、同時に入る@tiptap/starter-kitは@tiptap/extension-horizontal-ruleを「^2.1.13」で受けるため、最新の2.27.3が入り、2.1.13のcoreに存在しない関数を読もうとします。npmは2.27.3用のcoreを入れ子で別に入れるので通り、pnpmは2.1.13のcoreに結びつけるので落ちます。Issue #1774ではBunやyarnでの報告もあり、2025年7月から未解決です。
pnpmでは、pnpm-workspace.yamlにnodeLinker: hoistedを書いて入れ直すと、npmと同じ配置になって起動できました。overridesでhorizontal-ruleを2.1.13に固定する方法はこのエラーは消えますが、今度はpnpmの厳格な配置のせいでバンドラが@babel/plugin-syntax-import-assertionsを見つけられず止まりました。pnpmで使うならnodeLinker: hoistedが手数の少ない回避策です。
npm auditの警告と「audit fix –force」の罠
この記事の手順どおりにインストールした直後のnpm auditは44件(high 4件・moderate 40件、2026年10月27日実行)でした。件数は監査データベースの更新で変わります。highの内訳は、@adminjs/design-systemが使うリッチテキストエディタtinymce 6.8.6のXSSと、Prisma CLIが依存するdeepmerge-tsの問題(prisma・@prisma/config経由で3件として数えられる)です。moderateには@tiptap/core・react-router・uuidなどが並びます。
ここでnpm audit fix --forceを実行してはいけません。npmが示す修正案は、adminjsを6.8.7へ、@adminjs/expressを5.1.0へ、@adminjs/prismaを3.0.1へ戻し、逆にprismaを7.10.1へ上げるというものです。ESM形式の7系で書いたコードが動かなくなるうえ、Prisma 7はアダプタが受け付けません。AdminJS本体の更新が止まっている以上、上流の修正は期待しにくく、管理画面を社内ネットワークやVPNの内側に置き、リッチテキストの入力者を信頼できる担当者に限るといった、外側での対策が現実的です。
2026年のメンテナンス状況と採用判断
最終リリース2025年7月15日と未解決の課題
本体のGitHubリポジトリは、7.8.17を出した2025年7月15日以降コミットがありません(2026年10月27日確認)。未解決のIssueとプルリクエストは合わせて223件で、2026年2月に立ったIssue #1801(プロジェクトの活動状況とメンテナンス方針の確認)にはメンテナーからの回答がなく、利用者から「止まっている」という書き込みや代替ツールの情報が続いています。2026年7月には脆弱性を非公開で報告する窓口を求めるIssue(#1806)も立っていますが、こちらも返答はありません。
一方でnpmの週間ダウンロード数は35,082件(2026年10月19日〜25日)あり、既存の利用は続いています。#1801にも「1年ほど更新はないが本番で使えている」という利用者の書き込みがあります。今すぐ動かなくなるわけではなく、新しい依存(Prisma 7、TypeORM 1.x、React 19の部品など)へ追随しない状態が続く、と捉えるのが正確です。
React-admin・Refineなどとの使い分け
結論として、AdminJSを選んでよいのは「Prisma 6やTypeORM 0.3系などの既存バックエンドに、社内向けのCRUD画面を数日で付けたい」場合です。画面をサーバーに同居させ、モデルを渡すだけで一覧と編集フォームが揃う手軽さは、フロントエンド側で組む枠組みにはない利点です。逆に、何年も機能を足していく管理画面、外部の取引先も使う画面、最新のORMやReactへ追随させたい画面では、更新が続いているフロントエンド側の枠組みを選ぶべきです。
| ツール | 動く場所 | npm最新版 | 週間DL | 向く場面 |
|---|---|---|---|---|
| AdminJS | Node.jsサーバー内 | 7.8.17 | 3.5万 | 既存DBにCRUD画面を急いで付ける |
| React-admin | ブラウザ(React) | 5.15.5 | 11.7万 | REST・GraphQL APIの上に作り込む |
| Refine | ブラウザ(React) | 5.0.12 | 18.9万 | 独自デザインの業務画面 |
週間DLは2026年10月19日〜25日のnpm集計(Refineは@refinedev/core)です。React-adminとRefineでは、APIに合う既存のデータプロバイダを選び、適合しない場合は実装します。導入工数は既存APIとの適合度に左右され、画面はReactコンポーネントで構成できます。更新も続いており、React-adminは5.15.5を2026年10月19日に公開、Refineはnpmの@refinedev/coreが2026年4月の5.0.12で止まっている一方、GitHubのリポジトリには2026年9月まで更新があります。それぞれの使い方はReact-adminとは?管理画面を最速で作るReactフレームワークの使い方とRefine(React)とは?react-adminとの違い・v5の使い方と管理画面の作り方で解説しています。
コードを書かずに済ませたいなら、SaaSのRetoolとは?社内管理画面を短期間で作る構成とDB直結の権限設計、内製の限界や、セルフホストできるNocoBaseとは:2.2系をDockerで立てる手順・API連携・ライセンスの注意点を解説も候補になります。バックエンドがPythonなら、同じ「モデルから管理画面」の発想で標準機能として持つDjango管理画面の設計:admin標準の範囲とModelAdmin・権限の実装が比較対象です。
よくある質問
AdminJSは無料で使えますか?
本体adminjsはMITライセンスで無料です。リレーションを画面で編集する@adminjs/relationsだけは有料の買い切りアドオンで、購入後に発行されるライセンスキーを設定して使います。@adminjs/expressなどライセンス表記が「SEE LICENSE IN LICENSE」のパッケージは、同梱のLICENSEファイルで条件を確認してください。
AdminJSはPrisma 7で使えますか?
2026年10月時点では使えません。@adminjs/prismaの最新版5.0.4はpeer依存がPrisma 5・6までで、Prisma 7.10.1と一緒に入れるとnpmがERESOLVEで止まります。対応を求めるIssueは2025年12月から未解決です。管理画面側だけPrisma 6.19.3に固定して使ってください。
AdminJSはCommonJS(require)で使えますか?
条件付きで使えます。7.0.0以降はESM形式だけで配布されているため、CommonJSのコードからは動的import()で読み込むのが公式ドキュメントの示す方法です(NestJSの手順もこの形です)。ESMのrequireに対応したNode.js 26.5.0ではrequire('adminjs').defaultでも読み込めましたが、新しく作るならpackage.jsonに"type": "module"を書いてimportで揃えるほうが、ドキュメントの例をそのまま使えます。
Admin.jsとAdminJSは同じものですか?
日本語の記事では「Admin.js」や「admin js」と書かれることがありますが、指しているのはnpmパッケージadminjs(GitHubはSoftwareBrothers/adminjs)です。検索やnpm iでは、区切りのないadminjsを使ってください。
AdminJSの開発は止まっていますか?
最終リリースは2025年7月15日の7.8.17で、2026年10月27日時点でリポジトリへのコミットもありません。メンテナンス方針を尋ねるIssueにも回答がない状態です。既存の構成では今も動くので、短期間の社内ツールには使えますが、長く運用する管理画面ではReact-adminやRefineを先に検討してください。