Versioning & compatibility
What a knext version number promises, which releases get fixes, and how the CLI, the packages and the operator fit together.
knext gives you one number to pin, and a small set of rules about what that number means.
One number covers three packages
knext publishes three packages, and they are always released together at the same version:
@getknext/core— theknextCLI, the Next.js adapter and the runtime@getknext/lib— the app-facing helpers (clients, health, logging)@getknext/db— the data SDK and migration runner
@getknext/core depends on the other two, so they are versioned in lockstep. Pin one and you have
pinned all three:
{
"dependencies": {
"@getknext/core": "^1.0.0"
}
}Releases before this rule took effect carry different numbers for the three packages. Those versions are internally consistent — a matching set always resolves — so nothing needs changing on your side.
What the number promises
The version follows semantic versioning over the documented public API:
- Patch and minor releases never break a public import. New capabilities arrive in minors.
- Anything that removes or changes the shape of a public import requires a major. That covers
the
knextCLI too: a removed flag, or aknext.config.tskey that stops being accepted, is a major change. - Imports under
@getknext/core/internal/carry no guarantee. They are framework wiring and can change in any release. If you need something that only exists there, open an issue — that is a signal the capability belongs on the public surface.
From 1.0 on, npm's default caret range (^1.0.0) picks up every later 1.x minor and patch, which
the rules above make safe. (Before 1.0, a caret range on a 0.x version did not pick up the next
minor, so 0.x upgrades were by hand.)
Each stable release is published to npm's latest tag. When a newer minor takes latest, an older
line stays installable: npm install -D @getknext/core@1.0 resolves the newest 1.0.x, and the
latest-1.0 dist-tag points at the same release.
Release candidates for a coming minor are published under their own dist-tag, never latest, so a
plain npm install @getknext/core always gives you a stable release. The next dist-tag carries
the 1.3 release candidates. A release candidate is a preview of a release that has not shipped yet:
try it in a branch or a staging cluster, not in production, and expect it to change before the
final release.
The three packages depend on each other with caret ranges, so an older @getknext/core can still
resolve a newer @getknext/lib or @getknext/db once a later minor exists. If you stay on an older
line, list all three packages in your own package.json at the same version.
Not every feature is covered by this policy yet — see Stability tiers for which features are Stable (covered above), Beta (may still change with notice), or Experimental (outside this policy entirely).
Before a capability is removed
Removal always takes two releases:
- It is marked deprecated in a minor release — visible in your editor through the types, named in the changelog, with a replacement to move to.
- It is removed no earlier than the next major.
So there is always a version you can run that has both the warning and the old behaviour, which is your migration window.
Which releases get fixes
The latest release line is the supported one. Fixes, including security fixes, land in the next release. There is no long-term-support promise for an older minor — knext has one maintainer, and a maintenance promise that went unmet would be worse than none.
In practice that means: upgrade forward to get a fix. Because minors never break the public surface, that is a version bump and a redeploy.
Security fixes are called out in the release notes for the release that carries them.
When a new major ships, the previous one gets a bounded window. From the day @getknext/core
2.0 is generally available, the final 1.x minor receives security fixes only, for six months,
published under the latest-1 dist-tag (npm install @getknext/core@latest-1). After that it
receives nothing. Before 2.0 is generally available, the paragraph above is the whole policy.
Supported runtimes on 1.x
The 1.x line runs on these runtimes:
- Node.js
>=22.18 - Next.js
>=16.0.0 <16.5.0
These are the floors and bound the published @getknext/core declares (engines and the next
peer dependency), so your package manager warns you when a project is outside them. New apps
scaffold with a recent Next.js release rather than the floor.
Which Next.js versions are covered
The next peer range is bounded: it admits only the Next.js minors knext has compatibility
evidence for (>=16.0.0 <16.5.0), and the upper bound is widened one minor at a time, in a knext
release, as evidence for the next minor lands.
Inside that range, the minors covered by the official Next.js compatibility suite are 16.3. The
other admitted minors work, but the compatibility results do not describe them, and knext doctor
prints a warning when your installed Next.js is not one of the covered minors.
A Next.js minor above the range is refused until a knext release admits it, and how you see that
depends on your package manager: npm stops the install with a peer-dependency conflict
(ERESOLVE), pnpm installs and prints an unmet-peer warning, and Bun installs without a message.
To try a newer minor before knext admits it, npm install --legacy-peer-deps lets npm proceed;
knext makes no compatibility claim for that combination.
The opt-in vinext build target, which compiles to a Bun single-executable, is Beta on every 1.x release. It follows the same release policy as the rest of the package, but it may still change with notice and is not part of the stability promise above; see Stability tiers.
What 2.0 changes
Version 2.0 is not yet released, so everything in this section describes what is planned, and the release notes of the day are the authority. A major is the one place the rules above allow breaking changes, and 2.0 is used to clear out what has been deprecated through 1.x:
- The Node.js and Next.js floors rise. The new floors are stated in the 2.0 release notes; nothing here promises them earlier.
- Deprecated imports and commands are removed. That includes the deprecated second name for the
command; use
knext. Removals follow the rule above: only something already deprecated, with a replacement named, is removed. - The operator does not get a new major. It stays on its own line, and the
NextAppresource keeps its API version, so the ordering rule below still applies unchanged.
Staying on 1.x is supported for the window described above; the latest-1 dist-tag is the way to
install it.
The cluster side versions separately
The operator that runs in your cluster, and the NextApp resource it reconciles, are not on the
npm version line. The operator ships as a container image with its own install bundle, and the
resource is on the Kubernetes API ladder:
apiVersion: apps.kn-next.dev/v1alpha1
kind: NextAppA major version of @getknext/core does not change that value, and a change to that value does not
force a major of the packages.
Within apps.kn-next.dev/v1alpha1 the schema is additive-only: fields are added, never removed
or narrowed. A NextApp you write today keeps applying against later operators. What can change is
what a field does — a setting for a capability that has been superseded may become inert. That is
called out in the release notes, and the operator may also record a warning event on the resource:
kubectl describe nextapp my-app # check the Events section after an upgradeIt never turns into a rejected apply.
The operator's major moves only when the operator breaks
The operator has its own version line, and a new major of @getknext/core does not give it one.
Its major changes only when the operator itself makes a change that cannot be additive — a new API
version of the resource that needs a conversion path, say. A @getknext/core 2.0 therefore pairs
with the same operator line as 1.x; what you check is the resource's API version and the ordering
rule below, not that two version numbers match.
Keeping the two in step
You do not need matching versions on the two sides. You need one ordering rule:
Upgrade the operator and its CRD first, then the CLI.
An older CLI against a newer cluster is always fine. The reverse is not, and knext deploy stops
before it changes anything when it detects it. See Upgrading for what that looks
like and how to fix it.
Operator versions
Operator releases are tagged operator-vX.Y.Z and follow semantic versioning on their own line.
On the 1.x line the minor tracks the package set (an operator 1.3.x pairs with
@getknext/core 1.3.x); the patch is independent, so an operator-only fix does not force a
package release. A new major of the packages does not give the operator a new major. What decides
whether a CLI and an operator pair is the NextApp API version they share
(apps.kn-next.dev/v1alpha1), and, within it, the operator release being at least as new as the
CLI. The pairing rule is:
| Operator vs. CLI | Result |
|---|---|
Operator serves the CLI's apps.kn-next.dev/v1alpha1, release equal or newer | Supported. Patch levels do not matter. |
Operator serves the CLI's apps.kn-next.dev/v1alpha1, release older than the CLI | Not supported — upgrade the operator first. |
| Operator does not serve the API version the CLI emits | Not supported. |
Pre-release suffixes (-rc.N) are ignored when pairing.
knext doctor checks the first half of that rule against your cluster: its NextApp CRD row
reads the API versions the installed CRD serves and compares them with the apiVersion your CLI
emits. It passes when that version is served (a CRD that serves several versions is fine as long as
one of them is yours) and fails when the CRD does not serve it. The message tells you which side to move: upgrade the
operator first when the CRD only serves older versions than your CLI emits, or upgrade the CLI to
match the operator when it only serves newer ones (a version marked deprecated still counts as
served). If the CRD cannot be read, the row fails rather than guessing. The Operator version row, described
below, is informational and does not decide the pairing.
Pin, upgrade and roll back the operator
Every operator release is published as a GitHub Release named after its tag, carrying two copies
of the same digest-pinned bundle: install-vX.Y.Z.yaml (the version is in the file name, so a
saved copy says what it is) and install.yaml. A release never changes after it is published.
operator-latest keeps working and always carries the newest stable release's install.yaml;
use it for a quick trial, not for a cluster you have to reproduce.
See which operator you run
knext doctorThe Operator version row prints the installed release, where it was read from, and the image digest, then states whether it pairs with your CLI:
PASS Operator version v1.3.0 (from label), image sha256:9f1c0a7d2b44…; compatible with CLI 1.3.0It warns, without failing the preflight, when the operator is older than the CLI on the same major
version, when the operator is an unreleased build, or when it predates operator versions and reports
none. A different major version is not a warning: the operator keeps its own version line when
@getknext/core moves to 2.0, and the row says the pairing is decided by the CRD API version.
The version is stamped on the manager Deployment, so you can also read it directly:
kubectl get deploy kn-next-operator-controller-manager -n kn-next-operator-system \
-o jsonpath='{.metadata.labels.app\.kubernetes\.io/version}{"\n"}'Pin a version
Apply the versioned bundle of the release you tested, instead of a moving address:
VERSION=1.3.0 # an operator-vX.Y.Z release from the project's GitHub Releases page
kubectl apply --server-side -f \
"https://github.com/getknext-dev/knext/releases/download/operator-v${VERSION}/install-v${VERSION}.yaml"For GitOps, commit that exact file (or reference that exact URL) so the operator version moves only when you change it. The image in the bundle is referenced by digest and is signed; check the signature before you trust a bundle, as described under Install.
Upgrade to a specific version
Apply the new version's bundle with the same command, then wait for the rollout and check the result:
kubectl apply --server-side -f \
"https://github.com/getknext-dev/knext/releases/download/operator-v${NEW_VERSION}/install-v${NEW_VERSION}.yaml"
kubectl rollout status deploy/kn-next-operator-controller-manager \
-n kn-next-operator-system --timeout=180s
knext doctorApplying the bundle also applies its NextApp CRD. Keep the order from above: operator first, then
the CLI.
Roll back to a previous version
Re-apply the previous version's bundle. It reverts the image and the CRD together:
kubectl apply --server-side -f \
"https://github.com/getknext-dev/knext/releases/download/operator-v${PREVIOUS_VERSION}/install-v${PREVIOUS_VERSION}.yaml"
kubectl rollout status deploy/kn-next-operator-controller-manager \
-n kn-next-operator-system --timeout=180sYour running apps keep serving while the operator is rolled back. Before you roll back across a
release that added a field to the NextApp resource, check that release's notes: the older CRD does
not know the field, so a value set only through it is dropped the next time that resource is
written. If the release added no field, nothing is lost.
An operator installed before versions were published (or from the rolling operator-edge
channel) reports no release version. Move it to a versioned release once, with the pin command
above, and every later upgrade and rollback has a concrete target.
TypeScript version
Scaffolded apps pin TypeScript 5.9.x by default. TypeScript 7 (the native compiler) works for your app's own build and typecheck, but breaks editor TypeScript support today — switch only if you've read what that costs.
Upgrading knext
Upgrade the operator and CRD before the CLI, and what the deploy-time schema check does when you don't.