Getting Started
End-to-end tutorial from app registration to loading a federated remote in the Shell host.
Audience: Shell host + federated remote developers
Time: ~1–2 hours first run (excluding Control Plane stack setup)
This walkthrough takes you from zero to a published federated app loaded by the Shell host, calling Control Plane APIs with IdPlane auth.
What you will build
Portal (register app + publish remoteEntry.js)
↓
Control Plane (manifest + bootstrap API)
↓
Shell host (loads remote via Module Federation)
↓
Federated remote (your React pages + optional API calls)Prerequisites
Infrastructure (usually already running for your team)
| Component | Default URL | Notes |
|---|---|---|
| Control Plane API | http://localhost:8088 | make compose-up in groundfloor-client-portal |
| Customer Portal | http://localhost:3000 | npm run dev:customer |
| IdPlane | https://auth.groundfloor.cloud | Platform realm; see .env.example |
| Dataplane | http://localhost:8080 | Sibling repo groundfloor-dataplane-oss |
| MinIO (files / publish) | http://localhost:9000 | Phase 2 deps compose profile |
Optional for LLM/secrets locally: Infisical + LiteLLM — see Phase 2 deps runbook.
Repos / downloads
| Source | Role |
|---|---|
groundfloor-client-portal | Control Plane + Portal (register/publish) |
| Shell starter-kit ZIP | Federated remote template — download from Portal → Apps → your Shell app → Download starter-kit |
| Shell host (optional locally) | Loads published remotes; not required to build/publish a remote |
You do not need the private Shell monorepo to create a Shell app.
Access
- IdPlane user with access to a workspace (member with at least
read) - Workspace UUID (from Portal URL:
/workspaces/{uuid}/…)
Step 1 — Register the app (Portal)
- Sign in to the Customer Portal with IdPlane.
- Open a workspace → Apps → Register app.
- Set:
- Name: e.g.
My Federated App - Slug: e.g.
my-federated-app(URL-safe, unique per workspace) - Kind: Shell (federated)
- Name: e.g.
- Paste a manifest (minimum viable):
{
"version": "1.0",
"appId": "my-federated-app",
"name": "My Federated App",
"routes": [
{ "path": "home", "title": "Home", "icon": "Home", "layout": [] }
],
"theme": {
"primaryColor": "#2563EB",
"accentColor": "#7C3AED",
"mode": "light"
},
"remoteUrl": ""
}- Save. Note app id (UUID) and slug on the app detail page.
See 09-manifest-and-routes.md for field details.
Step 2 — Build the federated remote (starter-kit)
- In Customer Portal, open your Shell app → Download starter-kit (ZIP).
- Unzip and install:
unzip shell-starter-kit-*.zip
cd shell-starter-kit
npm install # or pnpm install- Match Portal:
- Set
appIdingroundfloor.manifest.jsonto this app’s slug (same as Portal manifest). - Set
APP_IDinvite.config.tsto the same value. - Align
routes[]with pages undersrc/.
- Set
- Build a Portal-ready bundle:
npm run release
# → release.zip with remoteEntry.js at the zip rootWhat is remoteEntry.js?
You do not create or edit this file by hand. It is the Module Federation entry produced by Vite when you build the starter-kit:
| After… | Where it appears | What it is |
|---|---|---|
npm run build / npm run release | dist/assets/remoteEntry.js | Bundled JS the Shell host loads |
npm run release (recommended) | Also copied to release/remoteEntry.js and packed into release.zip at the zip root | What you upload in Portal → Releases |
Confirm it exists:
ls dist/assets/remoteEntry.js # after build
ls release/remoteEntry.js # after npm run release
unzip -l release.zip | head # should list remoteEntry.js at top levelContents: minified/bundled JavaScript (federation bootstrap + your src/App.tsx graph). Treat it like main.js from any SPA build — inspect in DevTools if needed; do not hand-author it. Your editable source is src/ + groundfloor.manifest.json + vite.config.ts.
- For local Shell host testing without publishing, run
npm run dev(serveshttp://localhost:4175/assets/remoteEntry.js) ornpm run preview:release, and setSHELL_REMOTE_DEV_URLon the Shell host to that URL.
Step 3 — Configure the Shell host
Set environment variables (.env.local or deploy config):
CONTROLPLANE_URL=http://localhost:8088
SHELL_WORKSPACE_ID=<workspace-uuid-from-portal>
SHELL_REMOTE_DEV_URL=http://localhost:3002/remoteEntry.js # optional until publishedIdPlane (Shell host browser client):
NEXT_PUBLIC_IDPLANE_URL=https://auth.groundfloor.cloud
NEXT_PUBLIC_IDPLANE_REALM=groundfloor_dev
NEXT_PUBLIC_IDPLANE_CLIENT_ID=groundfloor-portal # or dedicated Shell client
NEXT_PUBLIC_API_URL=http://localhost:8088Ensure the Shell client's id is listed in Control Plane IDPLANE_AUDIENCE.
Wire bootstrap on layout load — see 02-shell-bootstrap.md.
Step 4 — Publish the remote (Portal)
- Portal → workspace → your app → Releases / Publish build.
- Upload
remoteEntry.jsor a.zipwithremoteEntry.jsat the zip root. - Wait for finalize; confirm build number and remote URL on app detail.
Verify bootstrap (no auth):
export CONTROLPLANE_URL=http://localhost:8088
export WORKSPACE_ID=<uuid>
export APP_SLUG=my-federated-app
curl -s "${CONTROLPLANE_URL}/v1/public/workspaces/${WORKSPACE_ID}/apps/by-slug?slug=${APP_SLUG}" \
| jq '{slug, remoteUrl: .manifest.remoteUrl, build: .release.build_number}'manifest.remoteUrl must be non-empty after publish.
Step 5 — Load in Shell
- Start Shell host dev server.
- Open
/apps/my-federated-app/home(or your Shell's route convention). - Shell should:
- Fetch bootstrap JSON
- Load
remoteEntry.jsfrommanifest.remoteUrl - Render your federated page inside the Shell chrome
If unpublished, Shell falls back to SHELL_REMOTE_DEV_URL.
Step 6 — Call Control Plane from your remote (optional)
- Obtain IdPlane token from Shell auth context — 03-authentication.md.
- Configure
@groundfloor/api-client:
import { configureControlPlaneClient, setControlPlaneAuthProvider } from "@groundfloor/api-client";
configureControlPlaneClient({ baseURL: process.env.NEXT_PUBLIC_API_URL });
setControlPlaneAuthProvider(async () => getAccessToken()); // Shell / IdPlane auth helper- Try a read-only call, e.g. list vault collections or secrets keys:
// GET /v1/workspaces/{id}/vault/collectionsFor LLM: mint virtual key (admin) → store in Secrets → call LiteLLM — 07-llm-gateway.md.
Checklist
| Step | Done? |
|---|---|
App registered (shell_federated) | ☐ |
Manifest validates (routes, theme, appId) | ☐ |
Remote builds to remoteEntry.js | ☐ |
| Published at least once | ☐ |
Bootstrap curl returns remoteUrl | ☐ |
Shell env: CONTROLPLANE_URL, SHELL_WORKSPACE_ID | ☐ |
| Shell loads federated route | ☐ |
| IdPlane token reaches Control Plane APIs (optional) | ☐ |
Next steps
| Goal | Document |
|---|---|
| Bootstrap details | 02-shell-bootstrap.md |
| Auth wiring | 03-authentication.md |
| Customer data | 04-data-vault.md |
| File uploads | 05-files.md |
| Secrets / LLM | 06-secrets.md, 07-llm-gateway.md |
| curl recipes | 11-local-dev-recipes.md |
| When things break | 12-troubleshooting.md |
Control Plane dev stack (maintainers)
If you need to run Control Plane itself from source:
cd groundfloor-client-portal
make install && npm install && npm run codegen
docker compose -f deploy/docker-compose.phase2-deps.yml --profile all up -d
make e2e-up
make compose-up
npm run dev:customer
curl http://localhost:8088/healthSee repo README.md for full details.