Status: implemented in the current source branch. Importing configuration does not publish a Worker, attach a route, change DNS, or switch production traffic.
Forge deliberately separates repository-wide migration intent from build configuration:
.forge/config.json is static JSON. It lists Deploy projects, their roots,
repository defaults, and proposed Cloudflare domain or path bindings. Forge
can inspect it at an exact Git commit without running repository code.forgeBuild.ts. That typed,
statically evaluated file describes how Forge builds one application. It is
read only when that project builds.This keeps one discoverable repository map without turning a TypeScript file into DNS authority. A repository build never receives Cloudflare credentials, secret values, provider target identifiers, or permission to change domains or routes.
The machine-readable schema is
https://forge.smol.ai/spec/deploy/v1/repository-config.schema.json.
The optional $schema field gives editors the same contract Forge validates.
{
"$schema": "https://forge.smol.ai/spec/deploy/v1/repository-config.schema.json",
"version": 1,
"defaults": {
"production_branch": "main",
"publication_mode": "manual"
},
"projects": [
{
"root": "apps/main",
"slug": "main",
"cloudflare": {
"zone_name": "example.com",
"hostname": "www.example.com",
"bindings": [{ "kind": "custom_domain" }]
}
},
{
"root": "apps/archive",
"slug": "archive",
"cloudflare": {
"zone_name": "example.com",
"hostname": "www.example.com",
"bindings": [
{
"kind": "path_route",
"route_path": "/archive/2025",
"match_mode": "reserved_prefix"
}
]
}
}
]
}version, defaults, and projects are required. defaults requires
production_branch and publication_mode. Every project requires a unique
repository-relative root and permanent slug; it may override either
default. Forge expects <root>/forgeBuild.ts at the exact inspected commit.
Cloudflare declarations name a zone and hostname but never provider account IDs, zone IDs, Worker names, credentials, receipts, or current state. Those remain administrator-approved Forge control-plane records.
A path_route uses a wildcard-free route_path. match_mode chooses how
Forge translates that logical path into Cloudflare Worker Routes:
| Mode | Provider behavior | Use when |
|---|---|---|
exact_and_descendants |
Matches the exact path and its / descendants. This is the default when match_mode is omitted. |
Lexical siblings must remain available to another application. |
reserved_prefix |
Reserves every URL whose path text starts with the prefix. | The archive owns the whole namespace, including a query-bearing root, and lexical siblings can be intentionally unavailable. |
For /archive/2025, exact_and_descendants covers /archive/2025 and
/archive/2025/... but does not reserve /archive/20250. At Cloudflare's
route layer, a query-bearing exact root needs different treatment.
reserved_prefix maps to the broader provider pattern /archive/2025*, so it
also captures /archive/2025?utm=x, /archive/2025/..., and lexical siblings
such as /archive/20250 and /archive/2025-old.
Use reserved_prefix only when that lexical namespace is intentionally owned
by one project. Forge validates overlaps across projects and fails closed when
provider state contains ambiguous or unexpected overlapping patterns.
Repository configuration is a reviewed proposal, not a deployment primitive:
.forge/config.json, confirms each forgeBuild.ts, resolves existing
projects and approved targets, and returns a dry-run plan and digests.The exact-SHA API flow is:
GET /api/repos/:owner/:repo/sites/repository-config?sha=<40-character-sha>
POST /api/repos/:owner/:repo/sites/repository-config/importThe import request carries the inspected source/head SHA and expected configuration and plan digests. A changed branch head or plan is stale and must be inspected again.
{
"action": "import",
"source_sha": "<inspected 40-character SHA>",
"expected_head_sha": "<inspected branch-head SHA>",
"expected_config_digest": "<inspection config_digest>",
"expected_plan_digest": "<inspection plan_digest>"
}Import never enables a project, publishes a release, attaches or detaches a
domain, applies a Worker Route, changes DNS, or switches production traffic.
Removing a project or binding from .forge/config.json never means detach;
detach is a separate confirmed control-plane action.
Changing an approved provider target, zone set, or binding policy is also a separate operator action. Forge allows an identical approval to be repeated. While domains or routes remain attached, Forge also permits a narrowly fenced monotonic binding-policy expansion when the Cloudflare account, Worker, zone set, and approval state are unchanged. Existing resource IDs and D1 ID-to-name mappings must remain byte-for-byte present; additions advance the provider target generation and the attached domain/route fences atomically. Existing artifacts and releases retain their old frozen generation, so only a new build can consume the added authority. Resource removals or renames, zone changes, and provider identity changes remain blocked until every binding is detached, applied, and retired.
Imports are not atomic across independent projects: one project may complete before a later project fails. The recorded outcome is retryable. Resolve the failing contract, inspect the current exact SHA again, review its new plan digest, and import again. Do not reuse an earlier approval after project, target, zone, or binding state changes.
See Migrate an existing repository for the author workflow and the operator runbook for the control-plane and live-verification gates.