---
title: "Upgrades"
description: "Move every MainSDK package to a new release together with one command that applies the release's codemods and main.yaml moves, runs sync, check and your tests, and opens a pull request; and find out when a security release is not installed."
section: "interfaces"
status: "Building for 0.5 · ships as a beta API"
spec: "upgrade"
canonical: "https://mainsdk.dev/docs/interfaces/upgrade"
updated: "2026-10-28"
---

# Upgrades

> Move every MainSDK package to a new release together with one command that applies the release's codemods and main.yaml moves, runs sync, check and your tests, and opens a pull request; and find out when a security release is not installed.

Status: Building for 0.5 · ships as a beta API · Canonical: https://mainsdk.dev/docs/interfaces/upgrade · Updated: 2026-10-28

`mainsdk upgrade` moves an app to the next MainSDK release with one command, so apps built on a 0.x API are not stranded. It runs in your own tooling and is free.

It moves every `mainsdk` and `@mainsdk/*` package together, applies the release's codemods and `main.yaml` key moves, refreshes `main.lock`, runs `check` and your tests, and lists the few steps that need a person. During 0.x a minor release may change an API.

## At a glance

- Status: Building for 0.5 · ships as a beta API
- Phase: 1 (Core, public beta)
- Milestone: 0.5 Pilot ready
- Plan: free in your app (Apache-2.0)
- Spec: https://github.com/hubxero/mainsdk/blob/develop/docs/specs/upgrade.md
- Category: Developer experience

Calls:

- `mainsdk upgrade`
- `mainsdk upgrade --check`

In main.yaml:

```yaml

```

Guarantees, whatever the driver:

- Dry run by default for planning; applying needs a clean git tree and works on a new branch
- Every MainSDK package moves together to one exact version
- Codemods run on your own TypeScript compiler, are idempotent and never touch node_modules
- Steps a person has to do are listed with file and line
- The default branch never moves


## Drivers

Pick one in main.yaml with `upgrade.driver`.

| Driver | Id in main.yaml | Default | Note |
| --- | --- | --- | --- |
| npm registry and private registries from .npmrc | `npm-registry` | yes |  |
| Git and the gh CLI | `git` |  |  |

## Codes

| Code | Where | Meaning |
| --- | --- | --- |
| `MAIN-UPGRADE-0002` | check | MainSDK packages are at different versions in the workspace; they move only together |
| `MAIN-UPGRADE-0005` | check | A release marked `security` is newer than the installed version |
| `MAIN-UPGRADE-0010` | check | A release's API report has a removed or changed export with no upgrade note entry, or an entry's codemod has no fixture |
| `MAIN-UPGRADE-0001` | runtime | The git working tree has uncommitted changes, or the folder isn't a git repository; nothing was changed |
| `MAIN-UPGRADE-0003` | runtime | A codemod couldn't run (no `typescript` in the app, or a file didn't parse); it is listed as a manual step with file and line |
| `MAIN-UPGRADE-0004` | runtime | The registry couldn't be reached, so the target version is unknown; nothing was changed |
| `MAIN-UPGRADE-0006` | runtime | After the upgrade, the install, `sync`, `check` or the app's tests failed; the branch is kept and the pull request is a draft listing the failures |

## Features

| Feature | Phase | Milestone | Spec ticket |
| --- | --- | --- | --- |
| upgrade --plan from the registry and the release notes | 1 | 0.5 | `upgrade#plan` |
| Branch | 1 | 0.5 | `upgrade#apply` |
| Idempotent codemods and main.yaml key moves | 1 | 0.5 | `upgrade#codemods` |
| Sync | 1 | 0.5 | `upgrade#verify-pr` |
| upgrade --check for security releases | 1 | 0.5 | `upgrade#security-check` |
| Release gate for notes and codemod fixtures | 1 | 0.5 | `upgrade#notes-gate` |

## MainSDK Cloud

Everything on this page is free in your app.


## Quick start

```bash
npx mainsdk upgrade --to latest --plan    # what would change; nothing is written
npx mainsdk upgrade --to latest           # branch, bump, install, codemods, sync, check, tests, pull request
npx mainsdk upgrade --check               # exit 1 when a security release is not installed
```

`--plan` reads the installed version from your lockfile and the target from the registry's dist-tags. It prints, in version order, every change between the two: each codemod, each manual step, each `main.yaml` move that applies to your file, and every call site of a call the target removes. With `--to <exact version>` and release notes the installed CLI already has, it plans offline.

## Configuration

`upgrade` has no `main.yaml` section. It takes command-line options.

| Option | Meaning |
| --- | --- |
| `--to <version>` or `--to latest` | The target release (default `latest`, from the registry's dist-tags) |
| `--plan` | Print every change and write nothing |
| `--check` | Exit 1 when a security release is not installed |
| `--json` | Machine-readable output |

## What you get

1. Refuses a dirty working tree or a folder outside git (`MAIN-UPGRADE-0001`) and changes nothing.
2. Creates the branch `chore/upgrade-mainsdk-<version>` from the current commit.
3. Sets every MainSDK dependency in every workspace `package.json` to the exact target version, and runs your package manager's install (the one your lockfile names).
4. Runs the newly installed CLI as `mainsdk upgrade --continue --from <old version>`, so the target release's notes and codemods do the rest: the `main.yaml` moves in every layer file (comments and order kept), then the codemods.
5. Runs `sync`, `check` and your `test` script, commits once with the versions, codemods and manual steps in the message, and opens a pull request. A failure gives `MAIN-UPGRADE-0006`, keeps the branch and opens a draft that lists each failure. The default branch never moves.

## Codemods

Codemods run on the TypeScript compiler API loaded from your own `typescript`; the CLI bundles no compiler. Each is idempotent and touches only your source, never `node_modules` or generated output. With no `typescript`, or a file that doesn't parse, the change becomes a manual step with file and line (`MAIN-UPGRADE-0003`).

## Security releases

`mainsdk upgrade --check` exits 1 with `MAIN-UPGRADE-0005` when the registry has a release newer than yours whose notes say `security: true`. The answer is cached for 24 hours under `.mainsdk/cache/`. Offline it reports `MAIN-UPGRADE-0004` as info and exits 0. `mainsdk check` shows the cached result and never calls the network for it, and reports `MAIN-UPGRADE-0002` when your MainSDK packages are at different versions.

## Release notes

Each release of the CLI package ships `upgrade/<version>.json` for itself and every earlier version:

```json
{
  "version": "0.6.0",
  "security": false,
  "changes": [
    { "id": "share-expires-in", "kind": "breaking", "summary": "share() takes expiresIn instead of expires", "codemod": "share-expires-in" },
    { "id": "realtime-base", "kind": "manual", "summary": "Move custom realtime paths under realtime.base", "steps": "Set realtime.base to the path your proxy forwards" }
  ],
  "main-yaml": [ { "from": "share.expiry", "to": "share.max-expiry" } ]
}
```

`kind` is `breaking` (with a `codemod`), `manual` (with `steps`) or `info`. A change may list `calls` it removes and `exports` it changes. In the MainSDK repository the release gate fails with `MAIN-UPGRADE-0010` when a removed or changed export has no `breaking` or `manual` entry, or a codemod has no passing `before/after` fixture.

## Errors and checks

| Code | When |
| --- | --- |
| `MAIN-UPGRADE-0001` | The working tree is dirty or the folder is outside git; nothing changes |
| `MAIN-UPGRADE-0002` | Your MainSDK packages are at different versions (`mainsdk check`) |
| `MAIN-UPGRADE-0003` | A codemod could not run (no `typescript`, or a file that doesn't parse); it becomes a manual step |
| `MAIN-UPGRADE-0004` | Offline during `upgrade --check` (info; exits 0) |
| `MAIN-UPGRADE-0005` | A security release newer than yours is not installed |
| `MAIN-UPGRADE-0006` | A step failed after the bump; the branch is kept and a draft pull request lists each failure |
| `MAIN-UPGRADE-0010` | In the MainSDK repository, a removed or changed export has no release-note entry, or a codemod has no passing fixture |

Each code's cause and fix is in [check codes](https://mainsdk.dev/docs/reference/check-codes).

## Swapping drivers

`upgrade` has no driver. It reads the installed version from your lockfile and the target from the registry's dist-tags, and uses the package manager your lockfile names.

## Testing

`upgrade` runs your own `test` script after the bump. Run `npx mainsdk upgrade --to latest --plan` first to see every change without writing anything; with an exact `--to` version and release notes the installed CLI already has, the plan runs offline.

## Next

- [Upgrade MainSDK](https://mainsdk.dev/docs/guides/upgrade-mainsdk) walks through a full upgrade.
- [Check codes](https://mainsdk.dev/docs/reference/check-codes) lists every `MAIN-UPGRADE-*` code.
- [Dependencies](https://mainsdk.dev/docs/interfaces/deps) checks your lockfile, licences and advisories.
