Hype StackHypeStack

Build your own pack

A pack is a folder with a manifest.json and a files/ directory. The manifest says where each file goes and how it wires into the app. That's the whole format. Nothing gets compiled, nothing gets published to us, and no code of yours runs on anyone else's machine.

Packs you build are community packs. They install through their own command, hype-stack community, so it's always obvious when a project is pulling in code that didn't come from the Hype Stack catalog.

Start one

bash
npx @hype-stack/cli community init pack-analytics

You get a manifest, a files/ tree for the surfaces you picked, and a README. It validates as-is, so you can install it immediately and then fill it in.

The loop

Write code under files/, declare it in the manifest, then:

bash
npx @hype-stack/cli community validate

Run it as often as you like. It checks the same things the installer does, plus the ones that fail silently: a file sitting in files/ that no manifest entry declares never gets copied, so the pack installs "successfully" and the feature is missing a piece.

Install it into a real project to try it:

bash
npx @hype-stack/cli community add ../pack-analytics

Re-run it with --force after each change to overwrite what you installed last time.

Files and where they land

Every entry in files has a source (relative to the pack) and a target:

json
{
  "source": "files/frontend/src/features/analytics",
  "target": "{{frontend.features}}/analytics",
  "strategy": "create"
}

Targets are path templates, not literal paths. {{frontend.features}} resolves against the project's own stack.json, so your pack lands correctly in a project whose directories are laid out differently from yours. The apps you can address are backend, frontend, admin, mobile, extension, and enums.

A source can be a single file or a whole directory. A directory copies everything under it.

Strategies

StrategyWhat it does
createWrites the file, and asks before overwriting an existing one. The default
skip-if-existsWrites it only if nothing is there. Never overwrites, never asks
overrideAlways wins. For files the pack cannot function without
mergeSplices the source into the target at a marker string
merge-jsonDeep-merges a JSON source into an existing JSON target

Slots

Tag a file with a slot (frontend, admin, mobile, extension) and it only installs when the project actually ships that app. Files with no slot always install.

From your editor

The MCP server has two tools for this loop. pack_authoring_guide returns the format, the rules, and, inside a project, its real path templates, so an agent reads the conventions instead of guessing them. validate_pack runs the same checks as community validate on a directory and hands back the errors and warnings. Then add_packs { community: ["./pack-analytics"] } installs it.

Wiring into the app

Copying files isn't usually enough: a backend route has to get registered, a nav entry has to appear in the sidebar. You declare those in the manifest and the CLI patches the right file at install time. The full field list is in the manifest reference.

Database models

Ship .prisma files, never migrations.

Prisma's multi-file schema means a pack contributes models by dropping a file into the schema directory:

json
{
  "source": "files/backend/prisma/schema/analytics.prisma",
  "target": "{{backend.prisma}}/schema/analytics.prisma",
  "strategy": "create"
}

The CLI notices and offers to run prisma migrate dev in the project, which generates a migration against that project's own history. A migration you ship instead would diverge the first time anyone ran migrate dev themselves.

That schema directory is a shared namespace: your models sit next to the starter's and every other pack's. Before writing anything, the CLI checks whether a model, enum, type, or view you declare is already taken, and refuses with both filenames if it is. Prefix your models if there's any chance of a clash.

Dependencies

dependencies names what must be installed first. If your code imports from a starter, name the starter family, not one engine, so your pack works with whichever starter the project has:

json
"dependencies": ["starter-saas"]

A family id (starter-saas, pack-billing) means "any variant of this family": the CLI pins it to the variant already selected or installed, and pulls the family default in only when there is none. A concrete pack name works too, for packs that are not part of a family.

npm packages go in dependencies_npm, per app:

json
"dependencies_npm": { "frontend": { "recharts": "^2.15.0" } }

Publish it

There's no registry to submit to. Push the folder to a repository or publish it to npm, and people install it directly:

bash
npx @hype-stack/cli community add github:you/pack-analytics#v1.0.0
npx @hype-stack/cli community add npm:@you/pack-analytics

Both are pinned the same way anything else is: a git ref or an npm version. A private repo or a private package is already a paid pack, gated by the buyer's own git or npm credentials.

To host packs on your own URL, with your own keys and a purchase link when someone isn't licensed, see Run your own registry. Buyers then install with @you/pack-analytics.

Either way, the CLI records where each pack came from in the project's stack.json, so anyone can run community list later and see whose code is in there.

Editor support

Point $schema at the published schema and your editor will complete and check the manifest as you type. community init does this for you:

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