---
title: "systhema upgrade"
description: "Plans, codemods, token migrations, peer bumps and database hooks."
requested_language: hu
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/hu/next/cli/upgrade
version: unreleased (main)
docs_index: https://docs.systhema.app/hu/next/llms.txt
---
> This page isn't translated yet. Showing English.


Upgrade an existing Systhema project to a target version. Defaults to the running CLI's own version, so usually you just install a newer CLI and run `systhema upgrade`.

```bash
systhema upgrade                                 # latest CLI's version
systhema upgrade --dry-run                       # plan only, no changes
systhema upgrade --yes --no-git-check            # CI-friendly
systhema upgrade --skip-tokens --skip-peers      # codemods only
systhema upgrade --skip my-codemod-id            # opt out of a specific migration
systhema upgrade 1.7.0                           # target a specific version
```

For the task flow, see [Upgrading](https://docs.systhema.app/hu/next/getting-started/upgrading.md).

## Options

<!-- generated:cli-flags upgrade -->

| Flag                   | Description                                                                 | Default |
| ---------------------- | --------------------------------------------------------------------------- | ------- |
| `--allow-database`     | Include database-writing hooks; --yes alone excludes them                   | `false` |
| `--apply-plan <file>`  | Apply an unchanged, approved JSON plan with --yes                           |         |
| `--drop-stale-patches` | Under --yes: allow removing @systhemaui/* pnpm patches that no longer apply | `false` |
| `--dry-run`            | Plan only, no changes                                                       | `false` |
| `--force`              | Allow dirty working tree                                                    | `false` |
| `--json`               | Print the dry-run plan as JSON                                              | `false` |
| `--no-git-check`       | Skip git repo check (CI mode)                                               | `true`  |
| `--no-install`         | Skip pnpm install step                                                      | `true`  |
| `--no-skills`          | Skip refreshing already-installed Systhema skills                           | `true`  |
| `--skip <id>`          | Opt out of a specific migration id                                          | `[]`    |
| `--skip-patch-check`   | Do not test-apply @systhemaui/* pnpm patches against the target version     | `false` |
| `--skip-peers`         | Skip peer dependency bumps                                                  | `false` |
| `--skip-tokens`        | Skip token migration phase                                                  | `false` |
| `--verbose`            | Show unchanged token migration values in the text preview                   | `false` |
| `-y, --yes`            | Use defaults without prompts; database hooks need --allow-database          | `false` |

<!-- /generated -->

### Flag behavior

- `--dry-run` — enumerate codemod file creations, edits and deletions, plus changed token files and counts of unchanged files. Uses the defaults that `--yes` would apply. JSON output keeps every per-file token value.

- `--apply-plan <file>` — with `--yes`, validate and apply a saved JSON plan. Reuses its target and execution options; refuses changed project inputs, CLI version, plan contents or migration effects before writing.

- `--allow-database` — include database-writing post-upgrade hooks. Without it, they appear as excluded in the plan. Interactive runs ask for database consent separately from dependency, code and token consent.

- `--skip-patch-check` — don't test-apply your `@systhemaui/*` pnpm patches against the target version (see below). Proceeds unverified; `pnpm install` is then the first thing that finds out.

- `--drop-stale-patches` — under `--yes`, consent to removing `@systhemaui/*` pnpm patches that no longer apply. Without it an unattended run stops instead of deleting anything.

## Review and apply a saved plan

Review and apply an unattended upgrade:

```bash
systhema upgrade --dry-run --json > /tmp/systhema-upgrade-plan.json
# Review the JSON and obtain approval, then apply that saved plan.
systhema upgrade --apply-plan /tmp/systhema-upgrade-plan.json --yes
# Run an excluded data migration deliberately when ready.
systhema migrate migrate-seo-data
```

Choose `--skip <id>`, `--skip-tokens`, `--no-install`, `--no-skills`, patch flags and `--allow-database` when generating the plan. Apply reads those choices from the artifact; it rejects overrides. Git preflight controls such as `--force` and `--no-git-check` can still be supplied at apply time. To change an execution choice, generate and approve a new plan. Run any required deferred database migrations before starting or deploying the updated app.

## General Settings data hooks

General Settings data hooks inspect the loaded Payload schema. A disabled global or tab is a successful, explicit skip. When the tab is enabled, a missing SQL `general_settings` table is an error, even when the legacy table has no data. An existing table gets the hook's destination columns before it checks whether data needs copying. SEO also ensures `seo_title_separator` and `seo_title_template`, which the current tab reads. Each column gets the definition Payload's schema gives the field: its default, NOT NULL when the field is required, the select or radio enum type on PostgreSQL, and the upload foreign key on SQLite. A required column without a default is added nullable and constrained after the copy on PostgreSQL. SQLite leaves that constraint to the schema push. On PostgreSQL the hooks also repair columns an earlier release's hook added bare (nullable, no default, varchar or integer). Each such column gets its type and default in one statement and NOT NULL after the copy. Stored values are converted only into the title separator's enum. Any other type change happens only while the column is empty, otherwise the column is logged and left for a project-owned migration. A value the enum rejects also leaves the column unchanged and logged. Columns in any other shape are left alone. On SQLite the schema push rebuilds the table to fix such columns, without data loss. Because `systhema upgrade` runs the hooks before any schema addition, a later `systhema migrate --schema` or schema push then finds those columns as expected instead of blocking on them. Existing destination data is preserved unless `--force` is supplied to the Payload script. MongoDB reads and upserts documents in Payload's `globals` collection, keyed by `globalType`, and preserves the legacy document. The hooks write into the existing General Settings row, whatever its ID. Without one, they create it together with the copied values, and give it the ID Payload would: `1` for a custom number `id` field, a UUID for a custom text `id` field or `idType: 'uuid'`, a version 7 UUID for `idType: 'uuidv7'`, and the serial default otherwise. Localized values hang off that row.

When a destination table is missing, the hooks inspect the selected SQL schema, the adapter's migration directory and `payload_migrations.batch` to choose recovery guidance. A catalog with no application tables gets initialization guidance: start the app once in dev, or run the project's migrations, then rerun the hook. A missing `users` table alone does not prove that the database is fresh, since projects can use another auth collection.

| Existing database                                                                  | Schema recovery                                                                                                                                                                                                                |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| PostgreSQL using push, with a dev marker at batch `-1`, or without migration files | Run nav renames first. Review `systhema migrate --schema --dry-run`, then apply `systhema migrate --schema`. Do not generate a full-schema baseline against an existing database or run `payload migrate` with the dev marker. |
| Migration files with an existing snapshot baseline and no dev marker               | Run `pnpm payload migrate:create`, review the DDL, then `pnpm payload migrate` before rerunning the data hook.                                                                                                                 |
| SQLite using push or without a migration baseline                                  | Review a development schema push or project-owned SQLite DDL. The additive `--schema` command supports PostgreSQL only.                                                                                                        |

The additive schema command needs `@systhemaui/cli` and `@systhemaui/payload` at 1.7.0-canary.16 or later; an older Payload package answers `systhema migrate --schema` with `Unknown command`, and the CLI says so. Preserve the legacy `seo` and `emails` data until copied. A generated Payload migration can propose dropping those tables. Run nav renames before schema additions, then settings copies. Run a pending localized-status transition before generic schema additions. If an upgrade hook fails, the CLI exits nonzero and lists the failed, deferred and not-yet-run hooks in recovery order as `systhema migrate <id>` commands. Apply required schema additions between the nav and settings commands.

The settings hooks honor PostgreSQL's configured `schemaName`, but their column repair is deliberately limited. It does not create the complete General Settings schema, related tables, upload foreign keys or indexes, including the FK and index for `seo_image_id`. Reconcile the complete schema through the appropriate workflow above before copying settings and deploying. The hooks do not generate deployable Payload migration files. Apply the same reviewed schema and data steps to each environment.

`optin-cookie-consent-tab` preserves General Settings options supplied to `withSysthema`, including `cookieConsent.enabled: false`, expressions and spread-based options. `packages.payload.generalSettings` in `systhema.config.ts` is not a supported setting and has no effect. The codemod inserts an opt-in only into unconfigured plugin options. It resolves local `const` and `let` objects, TypeScript `satisfies` and `as` wrappers, and aliased `withSysthema` imports. A runtime expression for the banner's `enabled` flag continues to count as configured. If the banner is configured but the plugin options cannot be resolved safely, the codemod stays visible and returns a skipped note naming the file for manual action.

If General Settings is already configured and the admin tab is wanted, opt in manually. Enabling the tab requires SQL columns, localized fields and child tables. No paired cookie data hook creates those objects; the codemod's description and completion notes explain both schema workflows above.

The other bundled data hooks were checked for the same early-success paths. `migrate-localize-status` now rejects skipped configured targets with missing versions tables instead of calling them already migrated. Its existing transaction and migration-file handling remain in place. `migrate-header-nav-dbnames` keeps its catalog-driven reconciliation of both historical naming generations, including constraints, indexes, enums and sequences. `unify-block-slugs-data` propagates database errors; its SQLite JSON parse skip is intentional because it scans ordinary text columns as well as Lexical content. Header and status migrations keep their fresh, nonexistent SQLite-file no-op behavior. These hooks do not certify that a new database has been initialized.

Keep `--yes` separate from `--allow-database`: unattended file upgrades should not infer permission to write whichever database `DATABASE_URI` selects. Doctor currently checks live Header rename drift and localized publication status, but does not certify General Settings schema or completion of every data hook. It deliberately does not mark all same-version hooks as outstanding, since they may have run manually or from another machine. A future deployment gate should hard-fail on verified config/schema mismatches, using the target database and resolved config, rather than a local skipped-hook flag. Until that coverage exists, a clean doctor result is not proof that every deployment migration ran.

## The plan file

The JSON contains `schemaVersion`, `planId`, source and target versions, normalized options, a project input hash, dependencies, peers, patch actions and blockers, codemods, tokens, hooks, generator commands and skill refresh targets. Each codemod lists file actions and before/after hashes. Each token entry includes the migration definition and per-file nodes, so `$extensions`, customised values, missing tokens and `onlyWhen` misses remain visible. Hooks state `writesDatabase`, the command, selection status and the exact `--skip` flag. This classification is independent of the Lane A/B schema policy; omitted `writesDatabase` metadata defaults to database access.

Saved-plan execution writes the source bytes verified against the approved file hashes. The preview runs filesystem migrations in a temporary copy, in execution order. It never runs hooks or imports project config. Its input hash covers project files except `.git`, `node_modules`, `.next`, `dist`, `coverage`, `.turbo` and `.cache`. The installed Lexical package manifest is included because a codemod reads it. Symlinks are skipped without following their targets. A planned edit is refused if its destination or any parent inside the project is a symlink, including newly created files; apply checks all destinations again before writing. Symlink target contents are not bound inputs. Regular files remain included even when `.gitignore` excludes them, because local config can affect migrations. Token/patch paths outside the project are refused. Store redirected JSON outside the project as above, so opening the output file does not become a planning input.

Upgrade prefers non-empty project scripts named `sync`, `generate:types` and `generate:importmap`, preserving loader hooks and environment setup. Without a matching script it uses the project-local binary through `<pm> exec`. JSON commands, the text preview and recovery hints show the selected command. Core proxy and doctor commands still call the binary directly so a `sync` script invoking `systhema sync` cannot recurse.

The plan binds the migration choices and project files, not external state. Install and generator commands are listed separately because their output files are determined at execution. Package lifecycle scripts and Payload generators can load project code. Database contents are not queried, and hook approval covers the named command against `DATABASE_URI` at execution time; the URI itself is never printed. A plan that excludes hooks does not certify arbitrary project scripts as free of database effects.

## The upgrade pipeline

The upgrade pipeline:

1. **Codemods** — run registered code transformations (config-shape changes, prop renames, package.json script updates).
2. **Token migrations** — apply registered token migrations against `src/tokens/*.json` (adds, renames, modifications, removes, and whole new collections written into `tokens/manifest.json`).
3. **Dependency bumps**. bump `@systhemaui/*` and registered peer-dependency versions in `package.json`. For Payload projects, every installed `@payloadcms/*` package (database adapters like `@payloadcms/db-postgres`, plus optional plugins such as `@payloadcms/storage-s3`) is also aligned to the same version as `payload`, so the app doesn't crash at boot with a "Mismatching `payload` dependency versions" error. Siblings already at or above the target are left untouched. **If your project hard-pins the Payload family to one exact version**. `"payload": "3.90.1"`, usually mirrored into `pnpm.overrides`. any family package the upgrade adds or bumps is written at **that** version rather than at a caret range, so a freshly added peer can't resolve one patch ahead of the rest and trip the same boot check. A pin that sits below Systhema's declared requirement is moved to the exact new floor and remains an exact pin. The range-keyed Lexical patch supports compatible patch releases, but it does not change the pin policy. The upgrade also writes `pnpm.overrides` for the whole family, including the `@payloadcms/*` packages `@systhemaui/payload` depends on itself, so no transitive sibling can resolve ahead of the pin.
4. **pnpm patch reconciliation** — rewrite `pnpm.patchedDependencies` keys that name a version this upgrade moves past, and remove the patches that no longer apply to the new version (see below).
5. **Install** — run `pnpm install --no-frozen-lockfile`, or `yarn install --no-immutable`, after rewriting `package.json` (skipped with `--no-install`).
6. **Managed-file refresh** (Payload only) — `systhema-core payload create-app-files --override`. A managed file you edited is kept and the new template lands beside it as `<path>.new`; a managed file you **deleted** stays deleted (`Skipped <path> (deleted locally).`), because `.systhema/managed-files.json` recorded that Systhema once wrote it. A managed path the ledger has never seen is still created, so a managed file added by a new release arrives normally. Note that deleting `public/systhema-icon.svg` also removes the admin favicon — `withSysthema()`'s `admin.meta.icons` points at it.
7. **Sync** — `systhema-core sync` (regenerates token/type artifacts **and** the agent-facing TOON references).
8. **Payload generators** (Payload only) — `payload generate:types` then `payload generate:importmap`. Required after any Systhema bump that ships a new admin component path: the importMap needs to re-register it before the admin can resolve `getFromImportMap` calls at runtime. They load the project's `.env`; the upgrade reports removed import-map keys and type exports when generation shrinks the output. Failures here warn but don't abort the upgrade (so the rest of the work isn't lost); you can re-run them manually if the warning fires.
9. **Refresh skills** — re-install the now-bundled version of the [skills](https://docs.systhema.app/hu/next/cli/skills.md) the project **already has installed**, into exactly the scope+agent locations where they're present (both `project` and `user` scope are inspected). This keeps installed skills in step with the upgraded CLI without you re-running `systhema skills install`. It only refreshes skills that are already present — it never adds a skill you didn't opt into, so a project with none installed is a clean no-op. Skip it with `--no-skills`; failures warn but don't abort the upgrade.

10. **Post-upgrade data migrations** — with `--allow-database`, the database steps that go with the new version (see [`systhema migrate`](https://docs.systhema.app/hu/next/cli/migrate.md) and [Database migrations](https://docs.systhema.app/hu/next/payload/database/migrations.md)).

Migrations are idempotent — running upgrade twice in a row produces no changes the second time.

## Token changes in 1.7

**1.7 adds the `easings` token collection.** Motion moved out of a table baked into `@systhemaui/core` and into Figma variables, so the upgrade writes `src/tokens/easings.value.tokens.json` (23 named curves) and adds it to `src/tokens/manifest.json` after `calc`. Core emits the curves as `--ease-*`, so a curve you change in Figma reaches the site. Nothing else in the project is touched, and the change is offered as one batch confirm you can decline. A project that declines, or that skips the token phase with `--skip-tokens`, keeps computing exactly what it computed before: core falls back to its built-in easings.net table. Already have a hand-copied export at that filename? The upgrade keeps your file and only writes the manifest entry.

The sibling `transition` collection is **not** written. It is optional, Systhema exports none, and its per-component values are a motion language a project's designer authors. Core reads one whenever your own Figma file declares it — export it into `src/tokens/` and add the manifest entry, and the `--transition-*` custom properties appear.

**Five `foundations` colours become composed colours.** `text-muted`, the two `surface-shadow-highlighted*`, `overlay-bg` and `line-muted` gain a `com.figma.composedColor` block, so each is an aliased colour at a fixed opacity instead of a flattened hex, and follows its source through a colour-mode switch or a re-seeded brand colour. Four of them render the colour they already render. `line-muted` is the exception and changes what you see: it becomes `foundations.line` at 25% where it used to be plain black (white in dark mode) at 25%. Each change is guarded on the value Systhema shipped, so a project that already picked its own colour keeps it and is never offered the change.

## Prereleases and re-runs

**Upgrading off a prerelease.** A project pinned to a prerelease (`1.6.0-internal.<sha>`, `1.6.0-canary.2`, …) may predate migrations that later landed under that same release, so `1.6.0`'s migrations are replanned whenever you upgrade away from a `1.6.0` prerelease — to another `1.6.0` prerelease, to stable `1.6.0`, or straight to `1.7.0`. Anything already applied is a no-op.

**Re-running on the version you already have.** `package.json` gets bumped partway through the pipeline, so a run that fails after that point (a broken install, an interrupted terminal) leaves the project declaring the new version with the later steps never done. Re-running `systhema upgrade` therefore does **not** stop at "Already on `<version>`" — it re-evaluates that version's own codemods and post-upgrade steps and runs the ones that still apply, so an interrupted upgrade can finish where it left off. Nothing is bumped, and the token-migration phase is not re-offered (it runs before the install, so it can't be the phase a failed install stranded). A project whose codemods are all done still answers `Already on <version>. Nothing to do.` — the plan is built from each codemod's own `applicable()` check, not from the version number. The one thing a re-run cannot see is whether a post-upgrade data hook already ran (that lives in the database), so a hook the project's type qualifies for is offered again and runs only with `--allow-database`; every hook is idempotent and reports `nothing to migrate` on a finished project. `systhema doctor` leaves such a hook out of its `pending-migrations` count for the same reason — otherwise every Payload project sitting on the current version would carry the warning forever.

## Pre-flight checks

**Pre-flight: pnpm patches are re-keyed, or the run stops.** A `pnpm.patchedDependencies` entry is registered under a `<package>@<version>` key. Bumping that package orphans the key and `pnpm install` aborts the entire install with `ERR_PNPM_UNUSED_PATCH` — which, before the bump was reconciled, left the project half-upgraded: `package.json` rewritten, codemods and token migrations applied, `node_modules` untouched.

So the keys are reconciled as part of the upgrade:

- **`@systhemaui/*` patches** are re-keyed to the version being installed automatically. The patch **file** moves with the key when its name follows pnpm's own `patches/<escaped-name>@<version>.patch` convention, so the key and the filename never disagree; a hand-named file stays where it is. The plan lists every move under a **Patched dependencies** heading before you confirm.
- **Any other package the upgrade bumps** is only known as a range, not as a resolved version, so its key cannot be rewritten for you. The upgrade **stops before writing anything**, lists the entries, and tells you to re-key them by hand and re-run.
- **Any Payload-family package the override pass moves** (`pnpm.overrides` entries bumped alongside `payload`, transitive ones included) is checked the same way.
- **Systhema's own `@payloadcms/richtext-lexical` patch** is exempt only when the Dependencies phase will reconcile it after peer bumps and the written lexical range intersects the bundled patch range.
- Patches for packages the upgrade doesn't touch, and bare-name keys (`"lodash"` with no version), are left alone.

**Pre-flight: your `@systhemaui/*` patches are test-applied to the new version.** Moving a key onto the new version doesn't make the diff apply there: the code it edits may have moved or changed. pnpm then aborts the whole install with `ERR_PNPM_PATCH_FAILED`, again after `package.json` is on disk. So before writing anything, the upgrade downloads the target version of each patched `@systhemaui/*` package into a temp directory (through your project's own `.npmrc`, so private-registry auth is whatever it already is — nothing is written into `node_modules`) and test-applies the patch. Three outcomes:

- **It still applies** → the patch is carried forward, key and filename both moved. The check is run twice before it gives up: strictly first, then once more ignoring whitespace, because pnpm's own applier tolerates whitespace differences in context lines that `git apply` rejects. A patch counts as stale only when both passes fail.
- **It no longer applies** → the `pnpm.patchedDependencies` entry is **removed**, and the diff is **renamed to `<patch>.stale.patch`** beside the original rather than deleted, so you can still read it and rebase it against the new version by hand if you still want the change. Because dropping the entry is destructive, it is gated like every other destructive upgrade step: interactively you're asked per patch (default Yes, showing the failing hunk), and under `--yes` you must pass `--drop-stale-patches`. Without it the run **stops before writing anything** and prints what to do.
- **The version couldn't be fetched at all** (offline, a token that can't read the `@systhemaui` scope) → the upgrade **fails closed and stops before writing anything**, because "unknown" and "fine" are not the same answer. Fix the connection or the token and re-run, or pass `--skip-patch-check` to proceed unverified.

**Pre-flight: shape-changing steps need Payload migrations.** Post-upgrade steps come in two kinds. Name reconciliations (Lane A) require the same database consent as data-shape changes. A step that changes the SHAPE of stored data (Lane B) needs Payload to own the schema transition, so if the project has no Payload migration directory the upgrade **stops before touching anything** and tells you to run `payload migrate:create` first. Pass `--skip <step-id>` to proceed without it. The full reasoning — and the upgrade order that keeps data safe — is in [Database migrations](https://docs.systhema.app/hu/next/payload/database/migrations.md).

The Dependencies phase reconciles Systhema's bundled `@payloadcms/richtext-lexical` patch when the written lexical range accepts its target. It removes stale Systhema-owned patch entries, preserves hand-authored patches, and writes the current patch before the install. See [Installation](https://docs.systhema.app/hu/next/payload/installation.md) for what the patch does.
