Every endpoint is yours
Login was pluggable in 0.1.x; now list, read, create, update, delete and any named action are too — declared per resource, with the wire format mapped in both directions.
v0.2.0 · Next 15 & 16 · React 19
Declare your endpoints, your permissions and your modules once; get a launcher panel, a sidebar panel, generated CRUD screens, and documentation of your own installation.
# install, scaffold, and document your own config
pnpm add @cortejojicoy/admin-kit
npx admin-kit init
npx admin-kit docs
Three accounts, one password — demo.
The same build, resolved three ways.
Six decisions the kit has already made, and the reasoning is in the docs rather than in a fork of it.
Login was pluggable in 0.1.x; now list, read, create, update, delete and any named action are too — declared per resource, with the wire format mapped in both directions.
Catalog (does the module exist?), entitlement (may this tenant run it?), permission (may this user open it?) — separately configured, with none / view / full levels rather than a boolean.
A launcher for daily work that everyone lands on, and a sidebar panel for administration. Split by kind of work, not kind of account.
Plain data, so navigation resolves on the server with the user’s permissions already in hand — and a CLI can read the same config in plain Node.
One stylesheet driven by CSS custom properties. No Tailwind, no preset, no content globs — and every component takes a className.
admin-kit docs writes markdown describing your endpoints, your roles and your screens. --check in CI stops it from drifting.
AdminConfig is serializable on purpose. Icons are string keys resolved
through a registry, visibility is declarative, and functions live only in the three
places serializeConfig() strips at the boundary.
That constraint is what makes the rest work: navigation resolves on the server with the user's permissions already in hand, so the first paint shows the right menu — no client fetch, no flash of items they cannot see. And the CLI can read the same file in plain Node to write your docs.
export const adminConfig = defineAdminConfig({
app: { name: 'Axiomkit', logoIconKey: 'grid' },
auth: {
provider: 'jwt',
jwt: {
endpoints: { login: '/api/auth/login', me: '/api/auth/me' },
tokenStorage: 'server-cookie', // the browser never holds it
},
},
access: {
roles: { admin: ['*'], manager: ['users:*'] },
deny: { manager: ['users:delete'] },
},
resources: [{
name: 'users',
endpoints: {
list: '/api/users',
remove: { method: 'DELETE', path: '/api/users/:id' },
},
permissions: { list: 'users:list', remove: 'users:delete' },
}],
})import { defineAdminServerConfig } from '@cortejojicoy/admin-kit'
// Never imported from a client component. Keeping the secret out of
// admin.config.ts is what stops it reaching the browser bundle.
export const serverConfig = defineAdminServerConfig({
jwt: {
secret: process.env.JWT_SECRET,
algorithms: ['HS256'],
cookieName: 'axiomkit_session',
},
apiBaseUrl: process.env.API_BASE_URL,
})Hiding a control is not authorization, and neither is middleware. The axes decide what a user sees; the server guards decide what a request gets.
| Axis | Question | If the source cannot be read |
|---|---|---|
| Catalog | Does the module exist and is it active? | Fall back to the declared modules, so navigation never blanks |
| Entitlement | May this tenant run it? | Fail open by default |
| Permission | May this user open it? | Fail open by default |
Failing open is deliberate: a control-plane read that failed must not lock a paying
customer out of software they have paid for. Set onUnavailable: 'deny' per axis
where absence genuinely means no. Anything guarding administration itself fails closed regardless.
Each entry point names the environment it belongs to, so a server-only module cannot drift into a client bundle unnoticed.
| Sub-path | Contents | Environment |
|---|---|---|
@cortejojicoy/admin-kit | Config helpers, types, pure logic | Anywhere, including plain Node |
…/client | AdminProvider, contexts, hooks | Client |
…/data | CRUD hooks, data provider, resource types | Client |
…/access | Engine, <Can>, guards | Client |
…/ui | Panels, primitives, generated screens | Client |
…/server | Session, access gates, cookies | Server |
…/middleware | createAdminMiddleware | Edge |
…/styles.css | The stylesheet | — |
admin-kit init scaffolds both config files, the middleware, the login route and
one page per panel — then examples/app-router shows the same thing finished.