Packages
Peers packages are how the app loads extensible behavior: tables and tools (and related metadata) on the server/runtime side, routes for URL mapping in the browser, and UI bundles for React screens. The desktop app and PWA install package versions from bundled assets or synced updates, then each device chooses which version to run within a group (or personal space), using group-level defaults on IPackage when the device has no override.
At a high level, a package contributes:
- Runtime (Node / Electron main, or PWA runtime) — table definitions, tool instances, assistants, workflows, and navigation metadata (
appNavs). - Routes — small bundle that registers URL paths before UI loads.
- UI — lazy-loaded React components registered by
peersUIId.
Package lifecycle
Packages follow a three-phase lifecycle: dev → beta → stable. Disk updates create dev versions; promotion to beta or stable is explicit in the Versions UI. Dev records sync to the group but never auto-activate on other devices unless those devices opt in.
When you activate a different version on this device, routes and UI bundles reload without a full page refresh.
See Package lifecycle for development workflow, per-device pin/follow settings, and releasing to the group. See Package lifecycle design for design rationale and remaining roadmap work.
Creating a new package
The fastest way to start a new package is from the peers-package-template. Clone or copy it into your packages directory:
cp -r peers-package-template ~/peers/packages/my-app
cd ~/peers/packages/my-app
Project structure
The template provides a standard package layout:
my-app/
├── src/
│ ├── consts.ts # Package and component IDs
│ ├── package.ts # definePackage() — runtime metadata
│ ├── routes.ts # URL path registrations (eager)
│ ├── uis.ts # React component registry (lazy)
│ └── ui/
│ └── app.tsx # Your first screen component
├── webpack.package.config.js
├── webpack.routes.config.js
├── webpack.uis.config.js
└── package.json
Each webpack config builds one of the three output bundles:
| Bundle | Built from | Purpose |
|---|---|---|
package.bundle.js | src/package.ts | Runtime metadata: tables, tools, contracts |
routes.bundle.js | src/routes.ts | URL path mappings (loaded eagerly at startup) |
uis.bundle.js | src/uis.ts | React components (loaded lazily on navigation) |
See Routes and UI for how these bundles are loaded and why they are split.
Set up IDs
The template ships with placeholder IDs in src/consts.ts:
export const packageId = "<package-id>";
export const packageName = "<package-name>";
export const appScreenId = "<app-screen-id>";
export const contractId = "<contract-id>";
Replace each placeholder with a value generated by newid() from @peers-app/peers-sdk. All Peers IDs must be exactly 25 alphanumeric characters. You can generate IDs with the CLI:
peers tools run new-id
Update packageName to your package's display name and set peers.packageId in package.json to match.
Build and run
npm install
npm run build
This produces dist/package.bundle.js, dist/routes.bundle.js, and dist/uis.bundle.js. After building, reload the UI to pick up the new package:
peers ui reload
During development, use npm run dev to watch all three bundles for changes.
Webpack externals
The runtime provides React, @peers-app/peers-sdk, @peers-app/peers-ui, and zod as globals. Your webpack configs must list these as externals so they are not bundled — the template configs already do this.
Defining a package
The src/package.ts file uses definePackage() from @peers-app/peers-sdk to declare what your package provides at runtime:
import { definePackage } from "@peers-app/peers-sdk";
import { version } from "../package.json";
import { contractId, packageId, packageName } from "./consts";
const packageDefinition = definePackage((pkg) => {
pkg.packageId = packageId;
pkg.version = version;
const main = pkg.contract(contractId, 1, packageName);
// main.tables = [...];
// main.tools = [...];
// main.toolInstances = [...];
// main.tableDefinitions = [...];
pkg.appNavs = [{
name: packageName,
iconClassName: "bi bi-list-ul",
navigationPath: "app",
}];
});
(exports as any).packageDefinition = packageDefinition;
appNavs declares the navigation items that appear in the Apps launcher. The navigationPath corresponds to the route path registered in src/routes.ts.
Note: versionTag and contract devTag are not set in code. The platform assigns these based on the package's promotion state.
Related topics
- Routes and UI — how routes and UI bundles work, how to author them, and why to prefer route-based rendering.
- Package lifecycle — develop, release, and run versions (dev / beta / stable).
- Package contracts — versioned interfaces between packages (
definePackage, validation, registry). - Package lifecycle design — design doc and shipped vs planned work.
- System: Tables — how Peers models data with tables and reactivity.
- System: Workflows — how tools run in workflow runs (often used together with package tools).