Skip to content

External collections

By default, builder execute reads project-builder.json — and every collection, factory and schema it registers — from the working directory. --manifest points those reads somewhere else on the same machine, while generated files still land in the working directory.

The motivating case is git worktrees: your schematics are not committed to the application repository, so a worktree created next to the main checkout does not have them. With --manifest, every checkout runs the same schematics from one place.

Root What it is What it is used for
Manifest root The directory named by --manifest (or the working directory when it is not given) All reads: project-builder.json, collection.json, factories, schema.json, templates. Paths registered in the manifest resolve against this directory.
Workspace The working directory All writes: generated files, and the @pbuilder/sdk the run uses

Without --manifest both roots are the working directory, which is why you normally never notice the difference.

Keep the schematics in a directory of their own, outside the application checkouts, with no node_modules in it or above it:

~/work/
├── app-schematics/ ← manifest root
│ ├── project-builder.json
│ └── schematics/
│ └── hello/
│ ├── factory.ts
│ ├── schema.json
│ └── schema.generated.ts
├── app/ ← main checkout
└── app-feature-x/ ← git worktree of app
app-schematics/project-builder.json
{
"collections": {
"default": { "hello": { "path": "./schematics/hello" } }
}
}
  1. Install the SDK once, outside the checkouts. It must be version 0.3.1 or later: older versions cannot be loaded by a factory outside the workspace.

    Terminal window
    bun add -g @pbuilder/sdk

    The package directory this creates, ~/.bun/install/global/node_modules/@pbuilder/sdk, is the SDK root. For other global installs, see what the root must be.

  2. Run the schematic, naming both roots. --manifest and --sdk-root go before <collection>:<schematic>:

    Terminal window
    cd ~/work/app-feature-x
    builder execute --manifest=../app-schematics \
    --sdk-root=$HOME/.bun/install/global/node_modules/@pbuilder/sdk \
    default:hello --name=payments

    The factory and its schema are read from ~/work/app-schematics; the generated file is written to ~/work/app-feature-x/src/services/payments.ts. The checkout needs no node_modules of its own: the only other entry the run writes is node_modules/@pbuilder/sdk, a link to the SDK root.

  3. Inspect the collection the same way:

    Terminal window
    builder info --manifest=../app-schematics default

The same command works from the main checkout — every checkout, worktree or not, uses the shared directory and the shared SDK.

  • A directory or the file itself. --manifest=../app-schematics and --manifest=../app-schematics/project-builder.json are equivalent.
  • Relative or absolute. A relative value resolves against the working directory. The path is canonicalised once, following symbolic links.
  • Naming the working directory is the same as not passing the flag.
  • Placement. For execute, --manifest must come before <collection>:<schematic> — after it, it is rejected with invalid_input instead of being passed to the schematic. info accepts it before or after its argument.

Tools that spawn builder inside a checkout — scripts, editors, agents — can set BUILDER_MANIFEST and BUILDER_SDK_ROOT instead of passing the flags. Set once, they let every checkout run the shared schematics with plain commands:

Terminal window
export BUILDER_MANIFEST=~/work/app-schematics
export BUILDER_SDK_ROOT=~/.bun/install/global/node_modules/@pbuilder/sdk
builder execute default:hello --name=orders
  • Each flag always wins over its variable. A flag that names an unusable directory fails on its own; it never falls back to the variable.
  • When BUILDER_MANIFEST points somewhere other than the working directory, every run prints the warn_manifest_root_ambient warning with the resolved path, so an ambient setting never goes unnoticed. A root from BUILDER_SDK_ROOT prints warn_sdk_root_ambient, which names the variable but not the path — check the variable itself to see which SDK ran.
  • The shell expands ~ in the export lines above. A settings file for an editor or agent does not, so write full paths there.
  • Stopped using BUILDER_SDK_ROOT? The link it left in each checkout makes the next run fail with execute_manifest_path_escape. See --sdk-root and BUILDER_SDK_ROOT.

The manifest root decides which code runs, so the CLI only accepts one that nobody else can modify. It refuses:

  • a root that is writable by its group or by other users;
  • a root with a directory above it that is group-writable, or world-writable without the sticky bit;
  • a root, or a directory above it, owned by a user other than you or root;
  • the filesystem root, and your home directory itself (a directory inside your home is fine).
  • builder new schematic always writes to the working directory. If --manifest or BUILDER_MANIFEST names a different directory, it refuses with manifest_scoped_authoring_refused before writing anything. To author in the shared directory, cd into it and run builder new schematic there. The shared directory has no SDK, and new schematic does not read BUILDER_SDK_ROOT, so the scaffold skips schema.generated.ts with a warning. Regenerate types with the SDK root’s own script — codegen writes inside the project you name:

    Terminal window
    node $BUILDER_SDK_ROOT/dist/bin/pbuilder-codegen.js --project ~/work/app-schematics
  • SDK settings in the external manifest are ignored. An sdk.root or sdk.version in the external project-builder.json is not used — --sdk-root, BUILDER_SDK_ROOT or the workspace’s own installed SDK supply the SDK, and the workspace’s own package.json sets the version requirement. When the external manifest declares an sdk.root, the run warns with warn_manifest_sdk_root_ignored.

  • Other commands do not accept --manifest: passing it to init, for example, fails with manifest_root_unsupported_command.

Every path the external manifest registers must be relative to the manifest root and stay inside it:

  • An absolute path fails with collection_absolute_path.
  • A path that resolves outside the manifest root — for example ../app/schematics/hello — fails as escaping the named manifest root.