Forge Deploy uses two repository-owned contracts. A project-root
forgeBuild.ts describes how to build one application. An optional root
.forge/config.json statically maps a monorepo's projects, defaults, and
reviewed Cloudflare binding proposals without running repository code. See
Repository configuration for its schema and safety
boundaries.
You do not need to write forgeBuild.ts from scratch: the Deploy setup screen
and sf migrate inspect the exact Git commit, suggest a typed configuration,
and can commit that file for you.
The shortest migration is:
git remote set-url origin https://forge.smol.ai/<owner>/<repo>.git
git push -u origin "$(git branch --show-current)"
sf migrateYou can also push first and open the repository's Sites tab. The browser setup and CLI use the same detection and configuration rules.
Forge builds can hydrate already-managed LFS pointers without a local
git-lfs binary. Agents may also call the standard LFS Batch/basic endpoints
directly. Install git-lfs when you want clean/smudge hydration and ordinary
local add/push behavior.
Forge uses standard Git LFS pointers and the Batch/basic transfer protocol, so
a repository remains compatible with normal Git LFS tooling. The optional
root .forge/assets.json adds reviewed Forge Asset Graph intent without
creating a Forge-only pointer format. See Forge Assets v1 for
the complete contract.
After the repository is imported, the Sites page can inspect and adopt ordinary committed blobs. This source implementation is not available on the live Forge service until a separate platform release. Its contract:
public/** trees over 50 MiB aggregate and other
individual files over 5 MiB by default;The comparisons are strict > comparisons. The v1 inspection reports fixed
50 MiB/5 MiB defaults; it does not accept threshold overrides. Asset adoption
remains a separate action from the forgeBuild.ts assistant.
The adoption card reports missing objects before apply. Upload those bytes
through Forge's standard LFS Batch/basic HTTP API, then inspect again. No local
git-lfs binary is required. Apply is exact-SHA and idempotency-key protected;
it opens a derived migration branch and pull request, returns an immutable
receipt, and suppresses the migration PR's automatic Deploy preview. It never
changes the production branch, deploys, or changes DNS.
The assistant looks at repository files rather than running application code. It detects common package managers, framework and build commands, likely asset directories, server entrypoints, and Cloudflare configuration. It then:
forgeBuild.ts;Asset adoption is a separate reviewed output. The configuration assistant does
not fold .forge/assets.json, .gitattributes, or pointer changes into a
forgeBuild.ts write.
Reading and previewing never modify the repository. Writing an existing
forgeBuild.ts requires an explicit overwrite choice. A server-side commit is
protected by the expected branch head and existing-file identity so it cannot
silently replace newer work.
For a monorepo, commit .forge/config.json at the repository root after each
project has its own forgeBuild.ts. The optional $schema URL is
https://forge.smol.ai/spec/deploy/v1/repository-config.schema.json.
An administrator first inspects an exact SHA and reviews the normalized dry-run plan. Importing that same plan creates new projects disabled. Existing projects keep their enabled or disabled state while the reviewed root, production branch, and publication mode are updated. A missing or disabled Cloudflare target remains unresolved; an already-approved target that does not allow the declared zone blocks import. Import never enables, deploys, attaches, detaches, or applies provider state; a project administrator must use the separate Cloudflare Apply action after reviewing the imported proposal.
Omitting a previous entry never requests a detach. Imports across independent projects are not atomic and can partially succeed, so after a failure or control-plane change, inspect again and approve the new plan digest before retrying. See the complete repository configuration contract.
Generated Deploy builds use cache: auto; no cache setting is required in
forgeBuild.ts. Forge detects the frozen npm, pnpm, or Bun lockfile. The first
trusted build may capture an extracted exact dependency snapshot after the
frozen install and before application code runs; a matching rebuild of that
same source SHA skips installation but still executes the complete application
build. Cache
identities include the normalized Deploy project root, so two projects that
share a repository lockfile warm independently. A cache miss or cache service
error falls back to the same frozen install and cannot change release output.
An oversized manager store is skipped instead of split or force-saved. The
default manager-cache ceiling remains 512 MiB of logical files because a very
large mounted cache can be slower than a clean frozen install. This is a cache
miss, not a deployment failure. Exact dependency snapshots have their own
512 MiB compressed ceiling. Operators may raise
the ceiling for a measured project through
CI_EXACT_CACHE_PROJECT_LIMITS={"dprj_example":805306368} without raising the
manager-store ceiling. Invalid or absent entries fail back to 512 MiB, and both
save and restore enforce the project-scoped bound. The extracted snapshot is
keyed by exact source, lockfile, runtime, project root, and runner image.
The project administrator action Full rebuild current SHA is the controlled
same-commit warm-build canary and recovery path. It deliberately bypasses
finished OpenNext artifact reuse, while retaining eligible exact-source and
exact-dependency restores for the unchanged SHA, then runs the complete
application build, validation, ISR seed, and provider publication path. Pushes
and ordinary redeploys may still take the faster finished-artifact path. This
distinction makes prebuild_reuse outcome=skipped reason=manual_full_build an
observable full-build receipt instead of relying on a cache-key perturbation or
deleting prior artifacts.
Do not use this same-SHA receipt as the warm baseline for ordinary new commits. The exact dependency identity includes the full source SHA because dependency lifecycle scripts can read arbitrary repository and monorepo-sibling source. Cross-SHA reuse of that post-install tree would be unsafe. New commits may reuse the selected manager store and source projections, but not the exact installed tree.
For unchanged OpenNext inputs, Forge has a faster path than dependency caching:
after exact-SHA checkout and configuration validation, it may reuse a verified
immutable provider artifact before dependency installation. The key includes
the source SHA, project ID and root, normalized forgeBuild.ts, Wrangler and
lockfile digests, the pinned OpenNext version and dependency specifier, Forge
and provider adapter versions, runner image and compiler toolchain, provider
target/base/policy/binding generation, and connected-asset entitlements. Old
evidence without this complete key is ineligible. A hit still rechecks that the
Cloudflare Worker Version exists, requires ready ISR evidence, and creates a
new project-scoped Forge release and preview pointer.
This path intentionally does not compare fresh build bytes. Next.js can create new draft-mode keys and serialize an otherwise identical prerender manifest in a different route order, so two correct builds of one SHA may be byte-different. Changing any keyed input forces the ordinary frozen install, build, validation, and publication path.
Forge includes a dedicated read-only assistant for the AIE hosting snapshot:
node scripts/inspect-aie-hosting-migration.mjs /path/to/aiecode2025It inspects exactly these approved project roots:
apps/mainapps/2025-sitesapps/2023-2024-sitesThe JSON report proposes a separate project-root forgeBuild.ts using
app.runtime: "opennext-cloudflare" for each project. It also checks the
project-local registry @opennextjs/cloudflare declaration, exact resolved
lockfile version, Next.js compatibility, configuration, and project-local
wrangler.toml. The vetted OpenNext 1.19.11 target accepts Next
>=15.5.18 <16 || >=16.2.6 <17: apps/main and apps/2025-sites satisfy that
window, while apps/2023-2024-sites remains blocked on its Next 13 line. Other Next.js
workspaces are reported as excluded and never silently receive deployment
authority. The assistant opens files only; it has no write option and reports a
digest of the inspected snapshot.
Run this planner before modifying AIE. Its proposed manifests are review input, not commits, builds, provider provisioning, DNS changes, or a Vercel retirement.
Run the interactive assistant in a repository:
sf migrateUse another directory:
sf migrate ../my-appFor scripts and agents, inspect the result without prompts or changes:
sf migrate --jsonWhen Forge detects both an HTTP Worker and static assets, choose the output explicitly in the wizard or in automation:
sf migrate --target assets --writeWrite the suggestion only when forgeBuild.ts does not exist:
sf migrate --writeReplacing an existing configuration and creating a Git commit are separate, explicit choices:
sf migrate --write --force --commitThe CLI does not push. Review the generated configuration before publishing it to Forge.
Forge treats both main and master as conventional primary branches:
main exists, Forge uses main;master exists, Forge uses master;main and warns that the two primary-looking
branches are likely to cause confusion;The selected branch and warning are visible before Forge writes or deploys anything. Rename or remove the unused branch when practical.
Detection is not a promise of runtime compatibility. If the repository uses Cloudflare bindings, Durable Objects, secrets, scheduled jobs, service bindings, or compatibility flags that Forge cannot yet reproduce, the assistant lists those findings instead of pretending the migration is complete.
Static assets and applications using the documented Forge runtime can migrate today. Provider-specific applications may still use Forge as their Git source while their existing deployment remains active, then adopt Forge Deploy when the required compatibility is available. Changing the Git remote does not change DNS, Cloudflare Workers, storage, or the live domain.
Forge has a narrow compatibility path for a deployment that is still published
as a Cloudflare Worker. Once the trusted publisher records that deployment's
HTTPS workers.dev version-preview origin, Forge can serve its HTTP routes
through the deployment's unlisted sites.smol.ai preview URL:
import { defineForge, importWrangler } from '@smolai/forge/config';
export default defineForge({
version: 1,
name: 'my-worker',
build: { command: 'npm run build' },
app: { entrypoint: '.output/worker.js' },
routes: [{ pattern: '/*', to: 'app.http' }],
provider: {
cloudflare: {
wrangler: importWrangler('wrangler.toml', {
shareProductionResources: true,
}),
},
},
});importWrangler() is an inert typed reference, not executable repository code.
Forge reads and validates the TOML from the exact Git commit. The explicit
shareProductionResources acknowledgement is required because this preview
inherits the connected Worker's existing bindings and can read or mutate the
same D1, KV, R2, Durable Objects, secrets, and services.
Successful pushes and administrator-requested redeploys of the current
production-branch head populate Forge's content-addressed caches. Generated
Deploy jobs automatically select the detected frozen npm, pnpm, or Bun
lockfile. npm may additionally reuse an exact dependency snapshot. That exact
snapshot is keyed by immutable commit SHA and normalized project root because
package lifecycle scripts and monorepo installs may depend on repository
source; package-manager download caches remain reusable across commits and
sibling projects. Exact dependencies are compressed immediately
after Forge's frozen install, before application build code runs. The 512 MiB
default limit applies to the compressed payload, and Forge checks the expanded
dependency size plus free workspace capacity before staging it. The snapshot
preserves every root and workspace-local node_modules tree without capturing
repository source.
In a pnpm monorepo, declare every local package used by the application as a
workspace:* dependency, including packages referenced only by framework
configuration, Webpack aliases, build scripts, or code generators. Forge's
exact-source projection follows the declared workspace dependency graph; an
undeclared sibling is intentionally excluded even when a relative path in a
configuration file points to it.
For a normal Forge Deploy build, the runner reads the exact commit and its tree
directly from Forge Git storage. It verifies every Git object digest, transfers
only that commit's files into the restricted build sandbox, creates a real
detached Git checkout, and verifies HEAD. Repository history and HTTP clone
negotiation are not in the normal build path. Repositories above the bounded
object, byte, or depth limits fall back to the existing exact-SHA Git transport;
missing or corrupt authoritative objects fail closed.
For pnpm v9 monorepos, the direct path first reads the lockfile's frozen
importers index to discover the selected project and declared transitive
workspace:* dependencies without walking unrelated sibling workspaces. If
that syntax is absent or unsupported, Forge falls back to the conservative
full-tree discovery path rather than guessing. The selected projection still
includes root package-manager files and .forge policy.
The direct path retains only the verified compressed object closure. Concurrent decompression shares a 64 MiB byte budget and releases each object immediately after its digest, type, and tree relationship have been checked; the compressed closure itself remains capped at 256 MiB.
Older immutable previews remain readable but cannot write caches. This keeps a migration retry fast without letting stale code seed a later build.
After the repository build stops, Forge's pinned compiler creates the provider
module and trusted ingest reads its assets. Forge, not the repository and not
Wrangler running inside the sandbox, uses the connected Cloudflare API. It pins
binding inheritance to the one active 100% production Worker version, uploads
static assets through Cloudflare's content-addressed asset session, and creates
an immutable Worker Version with deploy=false. No route, DNS record, schedule,
custom domain, or live traffic pointer is changed.
Cloudflare-compatible builds may also emit WebAssembly modules. Forge uploads them beside the generated ESM entrypoint with deterministic names and includes their digests in the reusable provider artifact identity.
Trusted ingest is entitlement-driven. Forge preserves the 25 MiB per-file provider ceiling, the 100,000-file provider ceiling, and a 1 GiB aggregate safety ceiling. The AIE projects may be provisioned for 1 GiB per release; ordinary projects receive their own frozen entitlement. WebAssembly remains limited to 10 MiB per module and 25 MiB in total. Binary RPC streams are counted while they are read and cancelled as soon as a limit is crossed, before another full-size copy can be allocated. OpenNext applies narrower module, asset, cache, and compressed Worker limits before connected ingest; see Deploy limits.
Selected unchanged public LFS binaries stay content-addressed during this step. Forge derives their provider-manifest identities from the verified LFS digest, then streams only the hashes Cloudflare requests in bounded base64 multipart chunks. Large repositories therefore do not need their whole public tree hydrated into the sandbox or buffered in the runner to publish a release.
Every connected Worker version also receives a Forge-managed
FORGE_SOURCE_SHA plain-text binding containing the exact 40-hex release
commit. Repository configuration must not declare that reserved name. Runtime
diagnostics can use it to distinguish a Forge preview from another provider
without depending on build-tool environment inlining.
Forge uses a pinned Wrangler compiler in credential-free dry-run mode for this compatibility bundle. This preserves Cloudflare's ESM and compiled WebAssembly module semantics without allowing repository code or Wrangler to publish, activate, route, or change provider resources.
Forge requires Cloudflare's per-version Preview URLs for every connected
release. During the first publication it enables Preview URLs on the approved
Worker while preserving the existing workers.dev setting, then requires the
version-creation receipt to contain the unique version hostname. It never uses
the mutable production Worker alias for a release preview.
Cloudflare does not generate version-specific Preview URLs for Workers that implement Durable Objects. Forge retains a bounded compatibility path for those Workers: it stages one candidate at 0% and reaches it through a version override while production remains at 100%. Only the newest such preview is reviewable; publishing another candidate replaces that slot. Do not treat historical Durable Object preview links as immutable archives. Keep Durable Objects behind a separately connected service when retained previews matter.
Wrangler remains an independent administrator recovery path. A later Forge candidate records whichever production provider version it actually observed as its immutable base, and promotion compares against that version before it changes traffic. Forge therefore returns to control without overwriting a newer unobserved recovery release.
The migration compatibility path accepts a generated ESM entrypoint up to 25 MiB before provider compression, matching large framework outputs while retaining an explicit trusted-ingest bound.
Forge validates the connected resource identities returned with that exact base version against the repository allowlist: exact D1 database, KV namespace, R2 bucket, and bound Worker identities. It can preserve secret bindings without reading secret values. The deployed application still receives the inherited bindings, which is why the shared-resource acknowledgement remains a real production-data warning rather than a publisher permission warning.
The provider artifact identity is computed before upload from the pinned
adapter, compiled module, static asset manifest, _headers and _redirects
control files, complete Wrangler configuration digest, provider target, active
base version, and binding/settings digests.
Another deployment with the same effective provider bytes reuses the verified
immutable Worker Version instead of uploading another one. Forge records the
provider version receipt before it checks the preview URL, and a deterministic
provider tag lets a retry reconcile an upload whose response was interrupted.
For the OpenNext preset, the complete prebuild key described above can reuse
that verified version before build execution; byte-level artifact reuse remains
available after a full build for other connected runtimes.
Forge exposes the immutable source commit as CLOUDFORGE_TRIGGER_SHA during
the repository build. Use it anywhere a framework asks for a build or
application version. A clock, random value, or mutable branch name changes the
effective bytes on every build, so Forge correctly treats the result as a new
provider artifact instead of reusing the previous version.
SvelteKit defaults kit.version.name to the build timestamp. Connected
SvelteKit applications should make that value commit-based:
import { execFileSync } from 'node:child_process';
const sourceVersion =
process.env.CLOUDFORGE_TRIGGER_SHA ||
process.env.CF_PAGES_COMMIT_SHA ||
process.env.GITHUB_SHA ||
execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
export default {
kit: {
// ...your adapter and other settings
version: { name: sourceVersion },
},
};This preserves SvelteKit's update and cache identity while making repeated builds of one exact commit byte-for-byte reproducible across Forge, Cloudflare Pages, GitHub Actions, and local Git. Forge does not rewrite generated files after the build.
app.http are proxied; ordinary Forge asset routes
keep using immutable Forge asset storage;no-store, baseline security headers, noindex, and
the Forge deployment receipt; andThe recorded origin must be a root HTTPS hostname beneath workers.dev.
Credentials, paths, query strings, fragments, nonstandard ports, and
lookalike domains are rejected. The provider publisher enables and records
Cloudflare's immutable per-version Preview URL whenever the provider supports
one. The no-URL Durable Object compatibility path is explicitly limited to the
single staged candidate described above; it never represents the production
Worker alias as immutable.
This path is for migration evaluation, not confidentiality or final domain cutover. A Cloudflare preview hostname may still be reachable directly if it is discovered, and direct requests do not pass through Forge's access checks. Do not place private data in a connected preview. Forge does not change the existing production route, DNS, or custom domain until an administrator separately chooses domain graduation.
For connected production routing, keep the main Worker as the Custom Domain
and declare archive applications as logical wildcard-free path_route
prefixes. The default exact_and_descendants mode creates and receipts both
host/archive and host/archive/*, preserving lexical siblings. When the
archive intentionally owns the whole lexical namespace, explicitly review
reserved_prefix: it emits host/archive*, solves the query-bearing root, and
also captures siblings such as /archive-old. Neither mode captures Next
Pages Router /_next/data/<build-id>/archive.json URLs outside the archive
prefix, so keep the reviewed application fallback for those navigations.
Deployment logs expose checkout_phase=git_objects, transferred object/byte
counts, source transfer/materialization time, bundle size, asset count, and
whether the connected version was published or reused. These are owning-surface
measurements; a cached dependency install and a reused provider version are
reported separately.
The Deploy page also presents a compact receipt. End-to-end readiness is shown separately from trusted runner phases such as provisioning, exact-SHA checkout, dependency restore, dependency installation, dependency snapshot save, application build, provider publication, and finalization. Assets and runtime reuse are independent: a deployment can reuse either one without reusing the other. Forge persists phase timings from runner-owned code; repository log output cannot create or alter this evidence.
Connected previews follow the same retention window as Forge-native previews. Forge preserves the active deployment and two newest successful rollback candidates. Once an older preview expires, Forge may delete the exact immutable Worker Version it published. That cleanup is serialized with provider activation, verifies the version is not present in Cloudflare's current deployment, removes an expired candidate from a zero-percent preview slot when needed, and retains Forge's deployment, artifact, version ID, and cleanup receipts. A 100-percent production version is never eligible. Cleanup does not delete the Worker or migrate/delete bound provider data.
Cloudflare currently labels the Delete Version API beta. If that provider operation is unavailable or fails, Forge preserves the version and its receipts and retries the fenced cleanup later.
Recent successful deployments are compared with runner-time phase bars;
queueing is excluded. Each row is labeled Fast or Full so mixed execution
paths remain visible. P50 appears after three comparable deployments and P95
after twenty. Those percentiles use the newest measured deployment's execution
path and exact effective Forge configuration across up to fifty recent
records, so they never mix Fast and Full paths or unrelated configuration
changes. The chart separately reports how many rows are shown and the
percentile sample size. Regression and improvement signals use the same
comparison identity. The page calls out a dominant checkout, restore, install,
or application-build phase, suggests a bounded next investigation, and reports
the recent runtime-reuse rate.
The general migration assistant only authors forgeBuild.ts. The AIE planner
is strictly read-only. Every new deployment still requires a valid project-root
forgeBuild.ts, read from the exact commit before the build begins.
.forge/config.json can propose projects and routing intent, but it cannot
replace build configuration or grant a build DNS authority. UI overrides
remain visible, audited exceptions; they do not replace either repository
contract.
See the public Deploy specification for
the complete syntax and
forgeBuild.ts v1 for evaluator limitations. See
Repository configuration for the repository map and
its import/apply workflow.