Manifest and routes
Describe your Shell app's pages, navigation and colours in groundfloor.manifest.json, and link between pages.
groundfloor.manifest.json tells the Shell about your app: its name, its pages and the order they appear in the navigation, and its colours. gf apps publish sends it along with each release, so the file in your project is the one that counts.
Example
{
"version": "1.0",
"appId": "my-first-app",
"name": "My First App",
"theme": {
"primaryColor": "#2563EB",
"accentColor": "#7C3AED",
"mode": "light"
},
"routes": [
{ "path": "home", "title": "Home", "icon": "Home" },
{ "path": "records", "title": "Records", "icon": "FileText" },
{ "path": "records/:id", "title": "Record", "hidden": true }
]
}| Field | Required | Meaning |
|---|---|---|
version | Yes | The manifest format. Use "1.0" |
appId | Yes | Your app's slug. Must equal APP_ID in src/appIdentity.ts |
name | Yes | The name shown in the Shell, up to 120 characters |
theme | Yes | Your colours. See below |
routes | Yes | Your pages. See below |
description, icon | No | A short description, and an icon for the app |
remoteUrl is filled in when you publish; leave it empty.
Routes
Each route is one page of your app, at /apps/<app-slug>/<path> on your Shell site.
| Field | Required | Meaning |
|---|---|---|
path | Yes | The page's path within your app, such as records or records/:id. No leading / |
title | Yes | Its label in the navigation |
icon | No | A Lucide icon name, such as FileText |
hidden | No | true keeps the page out of the navigation but still reachable, e.g. a detail page opened from a list |
visibility | No | authenticated (the default) needs a signed-in user; public doesn't |
Add a page in two places: a route here, and the matching page component in src/App.tsx.
Link between pages
Your app lives under /apps/<app-slug>/ on the Shell site, so never write absolute links such as to="/records": they leave your app. Use the starter kit's useAppPath:
import { Link } from "react-router-dom";
import { useAppPath } from "./lib/useAppPath";
export function RecordsLink() {
return <Link to={useAppPath("records")}>All records</Link>;
}Don't wrap your app in BrowserRouter either: the Shell already provides the router, and a second one crashes the app.
Name a detail page in the breadcrumb
The Shell builds the breadcrumb from the URL, so a page like records/:id shows the raw ID. Set a readable label once you know it:
import { setBreadcrumbLabel } from "./lib/shellBridge";
useEffect(() => {
if (record?.name) setBreadcrumbLabel(record.name);
}, [record?.name]);Theme
| Field | Required | Meaning |
|---|---|---|
primaryColor | Yes | Your brand colour, as hex, such as #2563EB |
accentColor | Yes | A second colour, as hex |
mode | No | light (the default) or dark |
logoUrl | No | Your logo, as an absolute https:// URL |
The Shell uses these for the frame around your app. See Styling for your own pages.