What jspod ships, where things live, and how to customize.
An opinionated thin wrapper around JavaScriptSolidServer (JSS) aimed at first-time users. Single-user personal pods, default me/me credentials, localhost-only, batteries included.
The wrapper handles: default flags, port hopping, browser auto-open, seeding a custom landing page and account dashboard, bundling Pilot as a self-hosted Solid app, and shipping a ~200-byte default data browser. JSS is the substrate — everything that's actually a pod, an IDP, an OIDC provider, comes from JSS.
pod-data/)jspod creates pod-data/ in the current working directory on first start (or wherever you pass via --root). Everything the pod is, including identities and signing keys, lives under that single tree.
pod-data/ your pod root ├── index.html welcome page — jspod overwrites every start ├── index.html.acl public-read for the landing page (JSS-managed) ├── signin.html jspod's sign-in app — overwritten every start ├── signin.html.acl public-read ├── account.html post-sign-in dashboard — overwritten every start ├── account.html.acl public-read ├── docs.html this page — overwritten every start ├── docs.html.acl public-read ├── profile/ │ └── card.jsonld your WebID profile (JSS-seeded) ├── public/ public-read container │ ├── links.jsonld starter list of Solid apps (jspod-seeded, skip-if-exists) │ └── apps/pilot/ self-hosted Pilot (jspod-bundled, skip-if-exists) ├── private/ owner-only container ├── inbox/ LDN inbox ├── settings/ prefs + type indexes (JSS-seeded) ├── .acl root container ACL — owner read/write, no public default ├── .idp/ JSS internal identity storage (see below) └── .token-secret JWT signing secret (auto-generated, mode 0600)
index.html, signin.html, account.html, and docs.html are owned by jspod and overwritten on every start. Edits won't survive a restart. To customize, fork jspod or run JSS directly instead. The public/links.jsonld and public/apps/pilot/ bundles are skip-if-exists, so user edits there do survive.
Everything identity-related is under pod-data/.idp/:
.idp/ ├── accounts/ │ ├── <uuid>.json account record — username, WebID, bcrypt(password) │ ├── _email_index.json │ ├── _username_index.json │ └── _webid_index.json └── keys/ └── jwks.json OIDC signing keys for issued tokens
The account record contains a bcrypt hash of the password — not the plaintext. The signing keys are sensitive (anyone with these can mint valid tokens against your pod). JSS manages file modes on these; jspod doesn't touch them.
The default landing page (pod-data/index.html) is owned by jspod. Edits to it are overwritten on the next start. To genuinely customize, the options are, in increasing order of effort:
welcome.html, run ./index.js from the fork). All overwrites use your templates.pod-data/index.html pre-seeded. JSS does skip-if-exists for its own seed, so your file wins. You lose jspod's account dashboard / signin app / data browser, but you can copy any of them out of the npm package and adapt.--landing-page <file> option that would make this cleaner.jspod ships you onto rung 1 (default me/me) and makes climbing visible. The dashboard at /account.html exposes a Change password form (rung 2). Passkey support (rung 3) is on the roadmap. See jspod#6 for the full ladder framing.
To set a custom initial password without going through the UI:
JSS_SINGLE_USER_PASSWORD='your-password' npx jspod
Your entire pod — content, identity, signing keys — is one directory.
tar -czf my-pod.tar.gz pod-data/
Restore by extracting the tarball back into a pod-data directory and pointing npx jspod --root /path/to/pod-data at it.
JSS disables the /idp/account/delete endpoint in single-user mode (the account is the pod). To start completely fresh:
rm -rf pod-data npx jspod
A new me/me account is seeded on next start.
jspod bundles Pilot at /public/apps/pilot/. It's served from your own pod, so same-origin sign-in works even on strict browsers (Brave with shields up).
The bundle is copied skip-if-exists, so you can pin a specific Pilot version by editing the files locally and they'll survive jspod upgrades. Delete pod-data/public/apps/pilot/ and restart to re-seed from the current jspod's bundled copy.
jspod enables JSS's git HTTP backend by default. Any pod path you have ACL Read on is git-cloneable; any path you have ACL Write on is git-pushable. Fresh paths auto-initialize on first push (JSS 0.0.195+).
git clone http://localhost:5444/public/apps/pilot ./pilot
git clone https://github.com/solid-apps/penny.git cd penny git remote add pod http://localhost:5444/public/apps/penny git push pod main
Note: git push against a Solid pod needs the same DPoP-bound auth as any other Solid write. Easiest workaround today is to set an Authorization header via git -c http.extraHeader=... with a token obtained from /signin.html. Cleaner ergonomics are a known gap.
To opt out: npx jspod --no-git.