Upgrading
What changed between versions, and what you need to change.
Graft is in beta. Most releases need nothing from you, but some change a default to make it safer, and those can need a change in your project. This page lists every change that can break an existing project, newest first, with what to do about each one. For every change in a release, including fixes and new features, see the Changelog.
Upgrade your packages
All @usegraft/* packages share one version number. Upgrade them together,
naming every @usegraft/* package your project already uses and no others.
For a Next.js project on Postgres, that might be:
npm i @usegraft/core @usegraft/db @usegraft/sdk-next
npm i -D @usegraft/cli
On another framework, swap @usegraft/sdk-next for your adapter, such as
@usegraft/sdk-astro. Mixing versions is not supported. Then run
graft compile. If anything in your project relies on an old behavior, the
error's fix says what to change.
1.0.0-beta.10
Restricted trust refuses innerHTML, outerHTML and
dangerouslySetInnerHTML attributes. Vue renders
<div innerHTML="<img src=x onerror=…>"> as raw HTML, so a literal value was a
working script with no {} and no on… attribute for the existing checks to
see. The check runs in graft compile, MCP write_content and Studio saves,
on every framework, in any letter case. A body that uses one of these
attributes under the default mdxTrust = "restricted" now fails with
INPUT_VALIDATION_FAILED. React ignored the string form, so on a React site
the attribute rendered nothing anyway.
What to do: run graft compile. If it names a document, replace the
attribute with the markup itself, written as Markdown or a component. If every
author commits through code review, mdxTrust = "full" lets graft compile
accept the body, with trust: "full" at the render site. MCP write_content
and Studio saves check every body whatever mdxTrust says, so such a body has
to be committed to the repository. See
MDX safety.
An endpoint-backed createGraft in @usegraft/sdk-react refuses two
configurations it used to ignore. Any branch passed through
graft.client, "main" included, got the endpoint's own branch back, because
each content API endpoint serves one branch. Passing both endpoint and index used endpoint and ignored
index. Both now throw CONFIG_INVALID.
What to do: to read a preview branch, point a second createGraft at the
preview deployment's endpoint instead of passing branch. Pass endpoint or
index, not both. See React.
1.0.0-beta.9
A usage mistake exits with code 2, and a flag the command does not take is
refused. Before, every failure exited 1, and a flag meant for another command
was ignored, so graft init --apply succeeded and did nothing. Now an unknown
command or flag, or a missing argument, stops the command before it runs and
exits 2. An action that waits for a person's approval exits 3, and a missing or
refused credential exits 4. An unknown command no longer prints the whole help
on stdout. It prints a short message on stderr, with the closest command when
there is one:
graft: unknown command "complie".
Did you mean `graft compile`? Run `graft --help` to list the commands.
What to do: if a script reads exit code 1 as "called wrong", check for 2 instead. If a script passes a flag the command does not take, remove it. The command now stops instead of ignoring it. See Act on the exit code for every code.
graft content lists your content files, not the Postgres index. It used to
read the index, so it needed DATABASE_URL and showed what the last
graft compile wrote. It now lists the documents in your content files (or in
the database, for a project that keeps its content there), including files you
have not compiled yet. It works on the static engine with no database. Each
document's line keeps its format, and the header no longer names the branch.
What to do: nothing, unless you used graft content to see what the index
holds. To see what each compile changed, run graft compilations.
@usegraft/sdk-next no longer supports Next.js 14. Its peer range is
next@^15.0.0 || ^16.0.0, and CI no longer tests 14. Next.js 14 gets no
security fixes, and its last release has two critical remote code execution
advisories. The adapter still runs on 14.2, so a project that cannot upgrade
yet keeps working. Installing shows a peer warning, and npm refuses unless you
pass --legacy-peer-deps.
What to do: upgrade to Next.js 15.5.24 or 16.3.3 or later. Until you can, follow Run Graft on Next.js 14.
1.0.0-beta.8
Frontmatter written in JavaScript is refused. A document whose frontmatter
opens with ---js or ---javascript used to have that block run as code
wherever the file was read, including the Studio server and CI. graft compile,
Studio saves and MCP writes now refuse it with INPUT_VALIDATION_FAILED, and
read YAML or JSON frontmatter only. See
MDX safety.
What to do: if a file opens with ---js, rewrite its frontmatter as YAML.
Committing and reverting through the Studio API needs studio:publish.
POST /changes/commit and POST /compilations/:id/revert asked for
studio:write, so a contributor could commit to the checkout or roll it back
without the publish right that Studio's own Commit button checks. Both now
answer UNAUTHORIZED without studio:publish. Studio's screens need no change.
What to do: if your own code calls either route, give its token the
studio:publish scope.
graft compile refuses a reference that points at nothing. A
field.reference whose slug names no document, such as a product whose category
was deleted, stops the compile with CONTENT_REFERENCE_NOT_FOUND, naming the
file, the field and the slug. Studio and MCP writes refuse the same thing before
they write. See Choices and references.
What to do: run graft compile (or graft check). For each reference it
names, create the missing document or point the field at one that exists.
If you call checkRemoval from @usegraft/compiler yourself, pass
contentDir and drop db and branchId. It checks against the files in
contentDir, which stay right when the index is behind them.
Contributors can't commit on a local Studio. On your own machine a commit lands on the checkout at once, with no review, so it now needs the editor role. A contributor's drafts wait, and the Commit button says why. A local commit is also authored by the person signed in, not by whoever runs Studio.
What to do: nothing, unless a contributor used to commit locally. Give them the editor role, or have an editor commit their drafts.
1.0.0-beta.7
Postgres projects need graft db migrate. This release adds tables for
Studio accounts and sessions, the media library, agent tokens, site settings,
deploy hooks, content kept in the database, and the GitHub mirror. Until you
migrate, a Studio page that needs one of them answers MIGRATION_REQUIRED with
the fix, and the rest of Studio keeps working.
What to do: run graft db migrate against each database, then
graft harden <role> again if your app connects as a hardened runtime role.
A static project has nothing to migrate.
Embedding @usegraft/studio/panels needs lucide-react. Studio's icons
moved from Phosphor to Lucide, and the panels import it as an optional peer.
What to do: if you embed the panels, run npm i lucide-react and remove
@phosphor-icons/react if nothing else uses it. graft studio and
graft serve --studio need nothing.
1.0.0-beta.6
Drafts made through an invite link move to a new branch name. On a hosted Studio that saves to GitHub, a person who signed in with an invite link now gets a draft branch whose name can't collide with a GitHub login. A draft they saved before you upgraded stays on GitHub under the old name, and Studio no longer shows it.
What to do: before upgrading, have invite-link editors publish or discard
their drafts. A draft left behind is an ordinary branch named
graft-studio/drafts/…: merge it by hand, or delete it. People who sign in with
GitHub are not affected.
1.0.0-beta.5
GET and PUT /api/studio/v1/document are removed, with the DocumentDto
type from @usegraft/studio. Studio's editor moved to /entry in this
release. The new route reads and saves the same files, and it also saves to
GitHub when Studio is hosted for a team.
This affects you only if your own code calls the Studio HTTP API. The Studio UI, MCP tools and the CLI need no change, and old Studio links still open the right entry.
What to do: change each call as follows.
| Before | After |
|---|---|
GET /document?collection=…&slug=… | GET /entry?collection=…&slug=… |
sourcePath in the response | path |
PUT /document with { collection, slug, data, body } or raw | PUT /entry with the same fields, plus baseVersion |
branch in the PUT body | Not accepted. A save goes to the branch Studio serves |
DocumentDto | EntryDto |
Send the version from GET /entry back as baseVersion. If the entry changed
since you read it, the save is refused with CONTENT_CONFLICT (409) and nothing
is written. Read it again and resend. A save without baseVersion overwrites
whatever is there, as PUT /document did.
GET /entries also changed: an entry deleted in a draft stays in the list with
status: "deleted" until the deletion is published, and GET /entry returns
its published copy. If you count or filter entries, skip that status. See the
Studio API.
1.0.0-beta.4
No breaking changes. Studio and agent writes can now refresh your app's cache. See Refresh pages when content changes.
1.0.0-beta.3
@usegraft/sdk-next needs Next.js 15.5.24 or 16.3.3 or newer. Those are
the first releases with fixes for the critical Next.js security advisories
published in 2026. Anything older on either line, such as 16.3.0, is outside
the peer range, and installing shows a warning like
incorrect peer dependency "next@16.3.0". The app may still build. The warning
is about the advisories, not about Graft's code.
What to do: upgrade Next.js. npm i next@16 installs the latest 16.x, and
npm i next@15 the latest 15.x. Next.js 14 is not supported in this release: it
no longer gets security fixes, and those advisories are still open on it. A
later release runs on 14.2 again, with a warning. See
Check your Next.js version.
graft asset put refuses a key that already holds a file. It used to
replace the stored file, and the store keeps no history, so the old file was
gone. It now fails with ASSET_EXISTS, the same error the MCP put_asset tool
returns.
What to do: if a script uploads to the same key on purpose, add
--overwrite. Otherwise give each upload a new key.
1.0.0-beta.1 and 1.0.0-beta.2
No breaking changes. These releases raise dependency versions past known security advisories and fix security findings.
1.0.0-beta.0
The approval policy moved from an environment variable into your config.
GRAFT_APPROVAL_POLICY is ignored, and graft serve warns once at start if it
is still set. Move it to graft.config.ts:
// before: GRAFT_APPROVAL_POLICY=unattended in .env
export const approvalPolicy = "unattended";
An unknown value is refused rather than replaced with a default, so a typo cannot quietly weaken the policy. See Audit & approvals.
If you call createClient from @usegraft/sdk-core directly with a db,
it no longer takes one. It reads a compiled index (index) only. Pass your
Postgres handle to createDbClient from @usegraft/sdk-core/db instead:
// before: createClient({ db, collections })
import { createDbClient } from "@usegraft/sdk-core/db";
export const client = createDbClient({ db, collections });
Framework adapters' createGraft still accepts either, so app code that goes
through an adapter needs no change.
Other changes in this release need no action from you, but change behavior:
- An approval used on an unattended call is now marked as used, so it cannot authorize a later call if you tighten the policy.
run_functionover MCP now gets the same default rate limit asPOST /api/fnundergraft serve.- MDX that cannot be parsed now returns
INPUT_VALIDATION_FAILEDfromwrite_contentand Studio saves, instead of a plain error.
From 0.x to 1.0
0.x is deprecated. The 0.2.0 release tightened security defaults, and each item below can stop an older project from working until you change it.
Agents over HTTP need an identity
MCP over HTTP now refuses anonymous callers by default.
requireActoris replaced byallowAnonymous, which defaults tofalse.createGraftMcpHandlerrefuses to start with neither anactorresolver norallowAnonymous: true.graft serveallows anonymous agents only when it listens on your own machine, or when you opt in withGRAFT_MCP_ALLOW_ANONYMOUS=1. Use the opt-in only behind your own authenticating proxy: it opens the MCP tools to anyone who can reach the server.decide_approvalstill refuses anonymous callers.graft servewarns at start when the opt-in is set and it listens beyond your own machine.
What to do: give your HTTP endpoint an actor resolver. See
Auth and Connecting agents.
Permissions are scopes, not just "signed in"
write_content,put_asset, anddelete_contentrequire thecontent:writescope.graft mcpon your own machine has it automatically.- Studio routes require
studio:read,studio:write, orapprovals:decide. - Studio's
authorizeoption is replaced byauthenticate, which returns the caller and their scopes.
What to do: add the scopes your agents and users need to their tokens. See
Studio API for authenticate.
Approvals are recorded against a verified identity
decide_approval(MCP) no longer acceptsdecidedBy, and the Studio API ignores it.decideApproval(db, id, decision, decidedBy)now takes anApprovalDecider.- A function that needs approval refuses callers with no stable identity, since nobody could later tell who asked.
What to do: remove decidedBy from any code that decides approvals.
Executable MDX is refused
graft compile now fails on MDX containing {…} expressions, import,
export, or spread attributes (<Card {...props} />), and on MDX it cannot
parse.
What to do: remove executable MDX from your content, or, if every author
has commit access to your repository, set mdxTrust = "full". See
MDX safety.
Rate limits use the real client address
x-forwarded-for is ignored unless you set trustedProxyHops, and the old
x-graft-peer header is gone.
What to do: behind a proxy or CDN, set trustedProxyHops to the number of
proxies. See Rate limits.
Hardened roles need one new permission
The runtime role now needs UPDATE (status, duration_ms) on audit_log.
What to do: run graft harden <role> again with the operator login.
Safer file handling
put_assetover MCP only reads files inside your project, and only on a local server. Remote agents send the file's contents instead.- Studio and the registry refuse paths that escape their folders, including through symlinks.
- The local Studio refuses requests from other sites. See Studio API.
What to do: nothing, unless you relied on reading files outside the project.
Full changelog
Every release's complete notes are in each package's CHANGELOG.md, for example
packages/cli/CHANGELOG.md.