Hype StackHypeStack

Manifest reference

manifest.json is the whole interface between a pack and the CLI. Point $schema at the published copy and your editor will check it as you type:

json
{ "$schema": "https://www.hype-stack.dev/schema/pack-manifest-v1.json" }

Identity

FieldRequiredWhat it does
nameyesKebab-case identifier. Match the directory name
versionyesSemver. Recorded in the project so a later install can tell versions apart
displayNameyesWhat people see in the picker
descriptionyesOne line, shown next to the name
categoryyesstarter, layout, or feature. The catalog supplies this for our own packs, so a community pack has to declare it, and validate fails without it
tierschemafree or paid. The published schema requires it, so write free. Nothing in a community pack is licensed, so paid has no effect and validate warns about it
family-Makes the pack a swappable variant of a family (starter-saas, layout, pack-billing). Two packs in one family replace each other
dependenciesyesPacks, or family ids, that must be installed first. [] when the pack stands alone
peerPacks-Packs that make this one better if present, but are not required
adminCompatible-Layouts only: whether the admin shell can render this layout

Files

files is a list of { source, target, strategy }. source is relative to the pack root, target is a path template. See Build your own pack for the strategies and slots.

dependencies_npm adds npm packages per app:

json
"dependencies_npm": { "frontend": { "recharts": "^2.15.0" }, "backend": { "zod": "^4.0.0" } }

allowBuilds records the verdict for a dependency that runs a build script. pnpm 12 refuses to install one without a verdict, so the CLI writes yours into the project's pnpm-workspace.yaml:

json
"allowBuilds": { "sharp": true }

What a pack needs from the starter

FieldFor
providesStarters only. What the starter gives a project: auth, organizations, permissions, invitations, admin.
requiresLayouts and feature packs. What the pack needs from the starter, from the same list.

The installer checks requires against the union of what is installed and what is selected, and refuses the plan by name when something is missing. A pack that reads the active organization declares organizations and never lands on a starter without one. The free starter-auth-betterauth provides all five (every account owns a personal workspace, the admin console is sign-in plus admin management, and the invitation inbox exists without a way to send), so every feature pack installs on it. Swapping it for a SaaS starter later is an upgrade: compose --packs starter-saas-betterauth replaces the starter's files, removes what the free one shipped on its own, and registers the admin app again.

Wiring

Copying files is rarely enough. These fields tell the CLI to patch a known file in the project so your code is actually reached. Each one is idempotent, so reinstalling never duplicates an entry.

Backend

FieldPatchesFor
routesroutes/index.tsRegistering a Hono route under a prefix
socketssockets/index.tsA WebSocket handler
contextcontext.tsA value on the backend request context
envenv.config.ts, .env.example, .env.testEnvironment variables the pack needs
jobssrc/jobs/index.tsA scheduled background job (see below)
queuessrc/queues/index.tsA task queue for slow work (see below)
enumspackages/enums/src/index.tsShared enums both sides import
permissionsthe shared enums packageNew permission subjects and role grants

An env entry names the Zod schema file to merge and the variables to add. Each variable is a plain default, or an object that tells onboard how to treat it: required prompts, generated rolls a secret at install, optional is offered once per group.

json
"env": [
  {
    "import": "analyticsEnvSchema",
    "from": "./analytics.env",
    "envExample": {
      "section": "config",
      "vars": {
        "ANALYTICS_WRITE_KEY": { "value": "", "setup": "required" },
        "ANALYTICS_FLUSH_MS": "10000"
      }
    }
  }
]

A jobs entry names an exported JobDefinition and the file it lives in. The template's scheduler picks it up at boot; see Scheduled jobs.

json
"jobs": [{ "import": "analyticsRollupJob", "from": "./features/analytics/jobs/rollup" }]

A queues entry names an exported QueueDefinition (built with defineQueue) and the file it lives in, relative to src/queues/index.ts. The template starts workers for it at boot; see Task queue. It needs template v1.7.0 or newer, and the install stops with a clear message on an older project.

json
"queues": [{ "import": "reportExportQueue", "from": "./report-export.queue" }]

Frontend

FieldPatchesFor
frontendRoutesfrontend-url/frontend-url.tsTyping a route your pack adds
sdkConfigurationssrc/api/sdk.tsHyperFetch client configuration
navconstants/nav.constants.tsA sidebar entry
navbarActionsconstants/navbar-actions.tsxAn icon in the navbar
userMenuActionsconstants/navbar-actions.tsxAn item in the avatar dropdown
sidebarActionsconstants/navbar-actions.tsxA block above a sidebar layout's navigation
drawerActionsmobile constants/navbar-actions.tsxA row at the foot of the mobile drawer
providerscomponents/providers/providers.tsxA React provider mounted at the app root
settingsTabsconstants/settings-tabs.constants.tsxA tab on the settings page
onboardingStepssetup-organization/steps/index.tsA step in organization onboarding

The four action fields are the shell action areas. Each is an exported array the installed layout renders, so a pack never imports the layout and a layout never imports the pack. The installer records every action in stack.json and rebuilds the arrays after each install, so it does not matter whether your pack lands before or after the layout. A drawerActions component receives closeDrawer and owns whatever sheet it opens.

Admin

dashboardRows, orgDetailsRows, orgDetailsCards, and orgDetailsTabs inject components into the admin app: a row on the dashboard, a row or card on an organization's detail page, or a whole tab next to Overview, Members, and Flags. They're skipped entirely in projects with no admin app.

Whole-app

postLoginRedirect sets where the app goes after login. shouldInvertLogo flips logo colours in light mode, for marks drawn on dark backgrounds.

Copy

User-visible strings belong in a message catalog fragment, not in your JSX:

json
{
  "source": "files/frontend/messages/en.json",
  "target": "{{frontend.root}}/messages/en.json",
  "strategy": "merge-json",
  "slot": "frontend"
}

Keys are flat snake_case prefixed with your pack's feature (analytics_page_title). Call them as m.analytics_page_title(). Chrome the codemods generate (nav entries, settings tabs) takes a messageKey next to label instead of a hardcoded string, so it translates with everything else.

After the install

postInstall is a list of lines printed once the pack lands. Use it for the things you genuinely cannot do for someone: an API key to create, a webhook to point somewhere, a decision they have to make.

json
"postInstall": [
  "Set ANALYTICS_WRITE_KEY in .env with a key from your provider dashboard",
  "Events are batched every 10s; change it in src/features/analytics/config.ts"
]

Fields you can ignore

previewNav, preview, previewAdmin, previewMobile, and previewExtension drive the live previews on the Hype Stack marketplace. They do nothing in a community pack.

Two things are deliberately not fields. There is no migrations: ship .prisma files as ordinary files entries and let the CLI generate the migration in the project. There is no hook or script field: a pack is data, and nothing in it executes.