Guide
Parse Dashboard
An admin console over every class, query, user and role — the thing you would otherwise build or buy. It is one install away and already wired into the generated project, but it is not bundled, and the reason is worth understanding before you expose it.
What you get
Parse Dashboard is the official web console for a Parse Server. It is a real application, not a table viewer:
| Area | What it does |
|---|---|
| Browser | Every class as a spreadsheet — read, edit, add and delete rows, including pointers and files |
| Query editor | Constraint builder plus raw queries, saved for reuse |
| Users & roles | Inspect accounts, see role membership, reset a password, log a session out |
| Schema | Add and remove classes, fields and indexes, and see the CLP for each class |
| Cloud code | Read the deployed cloud code and job status |
| Push | Compose and send a push notification, with delivery stats |
| Config | Edit Parse.Config values live, without a deploy |
| Logs | Server logs, filtered by level |
For a decorator-driven project the browser and the schema view are the useful pair:
your @ParseClass and @ParseField declarations become the schema
at boot, and the dashboard is where you confirm that what you declared is what the
database actually has.
Getting it
Two commands. Nothing to configure.
npm install parse-dashboard
npm run dev
It appears at http://localhost:1337/dashboard. The generated
app.ts already looks for it:
/**
* Mount Parse Dashboard at /dashboard, if it is installed.
*
* It is NOT a dependency of this project. The dashboard is a bundled React
* app and pulls in a lot for something plenty of services never expose, so it
* is opt-in - the same treatment node-cron and swagger-ui-express get.
*
* Returns what happened, so the startup banner can tell the truth about it.
*/
function mountDashboard(app: express.Express): 'mounted' | 'absent' | 'unsafe' {
let ParseDashboard;
try {
ParseDashboard = require('parse-dashboard');
} catch {
return 'absent'; // not installed: hint, do not fail
}
// The dashboard holds the master key. Unauthenticated, it is a full read
// and write console for your entire database, reachable by anyone who
// finds the URL - so credentials are not optional.
const user = process.env.DASHBOARD_USER || 'admin';
const pass = process.env.DASHBOARD_PASS || (IS_PRODUCTION ? '' : 'change-me-now');
// Refuse rather than expose. A dashboard that did not start is a bug
// report; one that started wide open is an incident.
if (!pass) return 'unsafe';
const dashboard = new ParseDashboard(
{
apps: [{ serverURL: SERVER_URL, appId: APP_ID, masterKey: MASTER_KEY,
appName: 'my-api' }],
users: [{ user, pass }],
},
{
// It refuses to serve over plain HTTP unless told to, which is the
// right default - the master key would be in flight.
allowInsecureHTTP: !IS_PRODUCTION,
}
);
app.use('/dashboard', dashboard);
return 'mounted';
}
The startup banner reports which of the three states you are in:
Docs http://localhost:1337/api-docs
Dashboard http://localhost:1337/dashboard # installed and mounted
Dashboard npm install parse-dashboard, then restart # not installed
Dashboard not mounted: set DASHBOARD_PASS # production, no password
Why it is not bundled
Three reasons, in order of how much they matter:
- It is a large install. The dashboard is a bundled React application with its own dependency tree — a few hundred packages that every generated project would pay for whether or not it ever opens the console.
- Plenty of services never want one. A backend behind a mobile app may have no human operators at all, and an admin console is then only an attack surface.
- Exposing it is a decision. It carries the master key. That should be an explicit act, not something a scaffolder did on your behalf while you were reading the README.
So it follows the same rule this library applies to node-cron and
swagger-ui-express: detected if present, a one-line hint if not, never a
crash.
It holds the master key
Credentials
# .env
DASHBOARD_USER=admin
DASHBOARD_PASS=something-long-and-unguessable
DASHBOARD_PASS | Development | Production |
|---|---|---|
| set | mounts, uses it | mounts, uses it |
| unset | mounts with change-me-now, warns every boot |
refuses to mount, says why |
The asymmetry is deliberate. On a laptop, a console you have to configure before you can look at your own data is friction for no benefit. In production, a console that silently came up with a published default password is the worst outcome available, so it does not come up at all.
HTTPS
Parse Dashboard refuses to serve over plain HTTP unless you pass
allowInsecureHTTP, because the master key would be travelling in clear. The
template passes it only when NODE_ENV is not production. If you
terminate TLS at a proxy and the dashboard sees HTTP behind it, that is what
trustProxy is for — do not reach for allowInsecureHTTP to make
the warning go away.
Read-only operators
Not everyone who needs to look at data needs to change it. Parse Server has a read-only master key, and the dashboard has read-only users to go with it:
// ParseServer options
readOnlyMasterKey: process.env.READ_ONLY_MASTER_KEY,
// ParseDashboard config
apps: [{
serverURL: SERVER_URL,
appId: APP_ID,
masterKey: MASTER_KEY,
readOnlyMasterKey: process.env.READ_ONLY_MASTER_KEY,
appName: 'my-api',
}],
users: [
{ user: 'admin', pass: process.env.DASHBOARD_PASS },
// Sees everything, can change nothing. Give support and analytics this
// one rather than sharing the account that can drop a class.
{ user: 'support', pass: process.env.SUPPORT_PASS, readOnly: true },
],
allowAggregationForReadOnlyMasterKey will default
to false in a future version. Aggregation pipelines can contain
write-capable stages such as $out and $merge, so a read-only key
that may run them is not entirely read-only. Set it explicitly rather than inheriting
whichever default your version happens to have.
How it sits with the middleware
The dashboard is mounted at /dashboard, outside
MOUNT_PATH, so restrictRoutes never sees its requests. That is
not a loophole: the dashboard talks to the API with the master key, and the master key
bypasses restrictRoutes anyway.
// Everything under /api is gated by restrictRoutes.
app.use(MOUNT_PATH, restrictRoutes);
app.use(MOUNT_PATH, parseServer.app);
// /dashboard is not, and does not need to be - it authenticates its own
// users, and reaches the API as master.
app.use('/dashboard', dashboard);
Which also means the dashboard is unaffected by
allowAuthRoutes
and by any CLP you set — it will happily browse a class whose CLP denies everything, as
in the orders tutorial. That is expected,
and it is a good way to confirm a CLP is doing its job: locked out through the API,
visible in the dashboard.
Exposing it in production
In rough order of preference:
-
Do not. Run it locally against the production
serverURLwhen you need it, so there is no permanently reachable console at all. - Behind a VPN or an auth proxy — SSO, an allowlist, whatever your organisation already uses. The dashboard's own credentials then become a second factor rather than the only one.
- On its own host, separate from the API, so a path on your public domain never serves it.
-
Exposed on the API host with a strong password. Workable, and the option with
the least margin for error. Give operators
readOnlyaccounts.
For the first option, point a local dashboard at the deployed server — no code change, and nothing extra listening in production:
npx parse-dashboard \
--appId "$APP_ID" \
--masterKey "$MASTER_KEY" \
--serverURL "https://api.example.com/api" \
--appName production
When it does not appear
| Symptom | Cause |
|---|---|
Banner says npm install parse-dashboard |
Not installed, or installed without restarting |
Banner says not mounted: set DASHBOARD_PASS |
NODE_ENV=production with no password — set one |
Parse Dashboard can only be remotely accessed via HTTPS |
Serving over plain HTTP outside development; terminate TLS, or set trustProxy if a proxy already does |
| Login page loads, credentials rejected | DASHBOARD_USER / DASHBOARD_PASS changed but the server was not restarted |
| Logs in, then "unable to connect" | serverURL is not reachable from the dashboard process — check it includes your mount path, e.g. /api, not just the host |
| Classes list is empty | The models never registered. See Troubleshooting — usually importFiles pointed at .ts with the default ['.js'] |
Next: Permissions & ACL for the rules the dashboard is deliberately allowed to ignore, and Seeding data for creating the roles you will see in it.