The way your app installs, updates,
and removes the capabilities it's built from. It's a
package manager that behaves like apt,
yum, or pacman — and it
doesn't use Composer at all.
Every capability your app uses — a database layer, an ORM, authentication, a view engine — has to get onto disk somehow. ForgePackageManager is that "somehow." It pulls capabilities from the sources you trust, verifies what it downloads, installs them into your module folders, and records exactly what's there so the same app can be rebuilt reproducibly.
Think of it like a trusted supplier system rather than a central marketplace you can't control. You explicitly say which sources you trust — like telling your server which repositories to use — and the manager fetches from those, and only those.
If Composer works for you, use it — the package manager isn't competing with it, and you can keep using Composer, Git submodules, or anything else. But if you've ever wished package management felt more like your Linux package manager — sources you define, units you can see, no hidden build steps — this is that.
The differences are deliberate, and they all come back to control:
| Composer-style | ForgePackageManager |
|---|---|
| Source code + build steps | Ready-made ZIP units, extract and go |
| A central registry you depend on | Sources you define and trust |
| Needs PHP runtime + tooling on servers | CLI-driven, no build tooling required |
| Packages fetched from one ecosystem | Fetch from Git, HTTP, FTP, SFTP, local, or a network share |
Use Composer if you want the central registry, the constraint resolution, and that ecosystem. Use this if you want to control where capabilities come from and install complete, immutable units. Both can coexist — it's a choice, not a competition.
Each ability is shipped as a complete, self-contained ZIP file — a unit. No compilation, no build steps, no "then run this to build it." Download the ZIP, extract it, and it's ready.
Think of it like this: instead of getting ingredients and a recipe (source code + build instructions), you get a finished meal (a complete ZIP file). Just extract and serve.
The package manager works like old-school software mirrors: capabilities live in a plain folder layout you can navigate by hand. Every registry follows the same structure:
registry-root/
├── modules.json # the index of everything available
└── modules/
└── forge-auth/
├── 0.2.4/
│ └── 0.2.4.zip # one complete unit per version
└── 0.2.3/
└── 0.2.3.zip
Because it's just folders and files, you can browse any registry manually — on GitHub, over HTTP in your browser, or with an FTP client. There's no magic; the manager just automates the navigation and the verification for you.
modules.json index
The index at the root lists every capability, its versions, where each one lives, and its integrity hash:
{
"forge-auth": {
"latest": "1.0.0",
"versions": {
"1.0.0": {
"description": "ForgeAuth 1.0.0",
"url": "forge-auth/1.0.0",
"integrity": "sha256-33bd70eead56a2855e..."
}
}
}
}
The url points to the folder relative
to modules/; the integrity
is a SHA-256 hash of the ZIP. When the manager
downloads a unit it records this hash, and when
you install from the lock file it re-verifies it —
so a tampered or corrupted download is caught.
Managing capabilities comes down to two files at the root of your app. They mirror what you're probably used to, if not by the same names.
forge.json — what you want
This is the file you edit by hand. Its
modules block lists each capability
and a version constraint (often just
"latest"):
{
"name": "My App",
"kernel": { "name": "forge-kernel", "version": "latest" },
"modules": {
"forge-storage": "latest",
"forge-error-handler": "0.1.0",
"forge-wire": "latest"
}
}
forge-lock.json — exactly what you got
This is generated for you. Every time you install,
the manager pins the resolved version, the registry
and source, the module_path, a
SHA-256 integrity hash, and a sanitized copy of the
source config (secrets stripped):
{
"modules": {
"forge-storage": {
"version": "0.2.0",
"registry": "kernel-module-registry",
"module_path": "forge-storage/0.2.0",
"integrity": "658ee144d77d034f04...",
"source_type": "git",
"source_config": {
"name": "kernel-module-registry",
"type": "git",
"url": "https://github.com/forge-kernel/kernel-module-registry",
"branch": "main",
"private": false
}
}
}
}
You don't manage this file — it's written on every install and cleaned up on every remove. Commit it to version control so any machine (or your CI/CD) can install the exact same set:
php forge.php package:install-project
package:install-project reads the lock
file and reinstalls every pinned unit, verifying
each integrity hash along the way — that's your
reproducible build.
Like apt, the manager won't run
post-install steps from a source you haven't
explicitly trusted. When you install something, it
tells you which registry it came from and asks
whether to trust that source for future
installations. Once trusted, that registry's
post-install commands run without asking each time.
Trust is recorded per registry (by name) in
storage/framework/trusted_sources.json.
You keep full control — nothing runs automatically
that you didn't approve, and you can choose to
trust or not each time. For automation, the
--trust-source flag (or
--non-interactive/--auto)
skips the prompt up front.
Separate from trust, integrity is always enforced with SHA-256 — trusting a source never disables integrity checking.
Four commands cover the whole lifecycle. All of them are registered as core commands, so there's no prefix to remember:
package:install-module
Install one or more capabilities. With no arguments it starts an interactive wizard — browse your registries, pick modules and versions (git registries show commit messages), and choose options.
php forge.php package:install-module [--module=<name[@version]>] [name[@version] ...] [--force]
php forge.php package:install-module --module=forge-wire
php forge.php package:install-module --module=forge-auth@1.2.0
php forge.php package:install-module forge-auth forge-storage forge-logger
php forge.php package:install-module forge-auth@1.2.0 forge-storage --force
Options:
--force bypass the download cache ·
--debug show debug info ·
--non-interactive/--auto skip prompts
· --trust-source auto-trust the source ·
--replace auto-replace existing modules ·
--config-mode=defaults|publish|env ·
--category=module|capability
package:remove-module
Remove an installed capability. It warns you up front — if the module ships migrations, seeders, or assets, it tells you these will be rolled back, lost, or unlinked before asking for confirmation.
php forge.php package:remove-module [--module=<name>] [name ...] [--force] [--debug]
php forge.php package:remove-module # interactive
php forge.php package:remove-module --module=forge-welcome
php forge.php package:remove-module forge-welcome forge-saas --force
package:list-modules
List the capabilities available in your configured registries, with descriptions and available versions.
php forge.php package:list-modules
package:install-project
Install everything from
forge-lock.json — the
reproducible install you run on a fresh
checkout or in CI/CD. It verifies each
pinned unit's integrity as it goes.
php forge.php package:install-project
Your trusted sources live in
config/source_list.php — created for
you on first use, pre-filled with the official
registry. Add your own registries there so you can
fetch private or internal capabilities.
<?php
return [
'registry' => [
// The official registry (pre-filled for you)
[
'name' => 'kernel-module-registry',
'type' => 'git',
'url' => 'https://github.com/forge-kernel/kernel-module-registry',
'branch' => 'main',
'private' => false,
'description' => 'Forge Kernel Official Modules',
'personal_token' => env('GITHUB_TOKEN'), // only for private repos
],
// Your own private registry
[
'name' => 'my-company-modules',
'type' => 'git',
'url' => 'https://github.com/mycompany/private-modules',
'branch' => 'main',
'private' => true,
'personal_token' => env('GITHUB_TOKEN'),
],
],
'cache_ttl' => 3600, // how long registry listings stay cached (seconds)
];
The name is what's used in the trust
flow and stored in your lock file. Keep tokens in
your .env and reference them with
env() so secrets never sit in
source_list.php.
The manager understands several kinds of sources,
so you can host capabilities wherever works for
you. Each registry entry's type picks
the transport:
When a capability declares configuration defaults, the
manager can write that config out for you after
install, controlled by
--config-mode:
defaults — keep the module's
built-in defaults; no config file is written.
publish — publish a config/<name>.php
you can edit, reading values from env with fallbacks.
env — add the module's settings
as .env overrides (for example,
forge_wire.use_minified becomes
FORGE_WIRE_USE_MINIFIED). This is
the default when you run non-interactively.
Capabilities can declare what they need. A
#[Requires(module: ..., version: ...)]
attribute on a module says "bring these along,"
and the manager installs them automatically before
the module itself:
This stays simple by design — flat units plus explicit requirements — rather than the heavy constraint-resolution engine of a tool like Composer.
These are just things that tend to work well. You do you — build however makes sense for your app.
forge-lock.json to version control for consistent deployments.source_list.php — read them from .env."latest") in forge.json when a capability matters to reproducibility.