DEV Community

stale_orbit
stale_orbit

Posted on

What a NocoBase upgrade actually changes

NocoBase ships fast. Between September 5th and 11th it published six patches. The changelog tells you what the team changed. It does not tell you what will happen to your database, your screens, your workflows.

Someone raised the same concern on the official forum, in Chinese (t/14099). In my translation, the gist is:

I'm on V2.1.30 and don't dare move to V2.2.X, because I don't know what the upgrade will change. The changelog describes it, but the actual impact is hard to judge.

The post also asks for an upgrade matrix: how big each change is, what it affects, and whether you can skip versions.

Two people replied. One upgrades every version in order and never skips one, "for fear of problems." The other says their system is already live and they worry an upgrade will bring bugs. There is no staff reply.

So I counted.

Test setup: NocoBase 2.1.39 → 2.2.14 (official Docker images) + PostgreSQL 16, September 20th. Three instances:

  • Upgraded: 2.1.39 installed fresh, a page built through the admin UI, then upgraded in place to 2.2.14 and diffed against snapshots.
  • Workflows: measured the same way on a separate instance (2.1.39 fresh, workflows created, upgraded to 2.2.14).
  • Fresh: 2.2.14 installed fresh, to compare against the upgraded one.

Table and column counts exclude the collections I created for the test. The screenshots come from a later run of the same steps. Source links point at the v2.2.14 tag.

The short version

What I looked at 2.1.39 → 2.2.14
Tables 100 → 104
Columns 855 → 899
Dropped tables or columns none
Columns added to existing tables two
A page I built in the UI (one table block) renders the same
Plugin states 74 enabled, all kept; 2 added
Workflow configs byte-identical

And one thing that is not in the changelog: the page you build in the admin UI is not stored where you would look for it. More on that below.

The structure barely moves

Four tables appear:

Table What it is for
uiLayouts UI layout (new in 2.2)
desktopRoutesUiLayouts join table between the above and desktop routes
aiUsageEvents AI usage records
userWorkflowTaskStats per-user workflow task counts

Of the 44 new columns, 42 belong to those four tables. Existing tables gain two columns total:

workflows.invalid        boolean
aiConversations.scope    character varying
Enter fullscreen mode Exit fullscreen mode

Nothing is dropped. Not a table, not a column.

The page still renders

Counting columns is the easy part. The forum's real worry is "will my screens break," so I built one before upgrading: a collection called items with three rows, a page added to the menu through the admin UI, a table block bound to items, and the title column turned on.

Writing the schema straight through the API would have been faster, and it would not have been a valid test. Real users build pages in the UI, and — as it turns out — the UI stores them somewhere else entirely.

On 2.1.39 the page listed three rows. I snapshotted, upgraded to 2.2.14, reloaded the browser, and opened it again. Same three rows, same column order, same "Total 3 items" in the pager. The only difference on screen was the version number in the help menu.

Before the upgrade (2.1.39)

A page built in the admin UI on 2.1.39, listing three rows

After the upgrade (2.2.14)

The same page after upgrading to 2.2.14. Only the version number has changed

The stored definition matched too:

before after
flowModels (the page and its blocks) 8 rows 8 rows, every hash identical
desktopRoutes (the menu) 2 rows no diff
uiSchemas 67 rows no diff
uiSchemaTreePath 343 rows no diff

Where your pages actually live

Here is the part worth keeping even if you never upgrade.

A page created through the admin UI does not go into uiSchemas. It goes into flowModels, and its route has a different type:

Built in the admin UI Written to uiSchemas via API
desktopRoutes.type flowPage page
Definition stored in flowModels uiSchemas
Renders yes no

That last row is not a typo. Insert a Page → Grid → Grid.Row → Grid.Col → CardItem hierarchy into uiSchemas, point a type: "page" route at it, and you get a working menu entry with a blank body. On 2.1.39 and on 2.2.14 alike. uiSchemas is the older engine; the UI does not write to it.

flowModels has three columns — uid, name, and a JSON options — and the whole tree lives in the JSON:

key in options meaning
use model name (TableBlockModel, TableColumnModel, …)
parentId parent's uid
subKey / subType which slot of the parent, and whether it is an object or an array
sortIndex order among siblings
stepParams the model's actual settings

My one-block page is eight rows:

RouteModel → RootPageModel
RouteModel → BlockGridModel → TableBlockModel(items)
                               ├ TableActionsColumnModel
                               └ TableColumnModel(title) → DisplayTextFieldModel
Enter fullscreen mode Exit fullscreen mode

The binding to a collection sits in stepParams:

{
  "use": "TableBlockModel",
  "subKey": "items",
  "parentId": "53434fe0b8c",
  "stepParams": {
    "resourceSettings": {
      "init": { "dataSourceKey": "main", "collectionName": "items" }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

If you want to verify any of this on your own instance, query flowModels, not uiSchemas:

select uid, md5(options::text) from "flowModels" order by uid;
Enter fullscreen mode Exit fullscreen mode

Run it before and after. Row counts alone will not tell you whether a definition was rewritten.

uiLayouts does not arrive empty

uiLayouts and desktopRoutesUiLayouts are new in 2.2, and it would be reasonable to assume they sit empty until you use the feature. They do not.

uiLayouts              : admin-layout-model (desktop) / mobile-layout-model (mobile)
desktopRoutesUiLayouts : both existing routes → admin-layout-model
Enter fullscreen mode Exit fullscreen mode

Migrations in the ui-layout plugin do this. ensure-default-admin-layout creates the two layouts. backfill-admin-layout-desktop-routes then goes through every row in desktopRoutes and attaches each route that has no layout yet to admin-layout-model. A third one, backfill-late-admin-layout-desktop-routes, runs the same backfill again later in startup, for routes that older menu migrations create at that stage.

Nothing in your definitions is rewritten, but these are not empty tables that just show up. A migration reads your rows and writes new ones that reference them.

Plugins and workflows

Plugins: 74 on 2.1.39, all enabled. 76 after, all enabled. The two additions are block-comment and ui-layout — the latter owns the tables above. Nothing was disabled and nothing was removed.

For workflows I built a collection event and an action event (the kind you bind to a button), each with a condition node, then upgraded:

before after
workflows (all columns, config included) 2 rows no diff
flow_nodes (node config included) 2 rows no diff

The configs are small — {"mode": 1, "collection": "orders"} and {"collection": "orders"} — and they came through unchanged. Worth knowing, because a bound workflow silently losing its target is the kind of breakage you find weeks later.

Upgraded and freshly installed end up identical

The other half of the anxiety: does an instance you have been upgrading for a year drift away from a clean install?

I put a fresh 2.2.14 next to the upgraded one and diffed every row of information_schema.columns — table name, column name, type.

tables columns migrations
Upgraded 104 899 22
Fresh install 104 899 18
Diff 0 0 4

Structurally they are indistinguishable. The path taken is not.

The four extra migrations all belong to the AI employee plugin (plugin-ai/src/server/migrations):

20260407170416-ai-employee-knowledge-base-add-key
20260424000000-remove-cole-ai-employee
20260428175558-update-ai-employee-category
20260429175132-ai-employee-deprecated-orin
Enter fullscreen mode Exit fullscreen mode

All four change existing rows in aiEmployees: three update them (copying knowledge-base IDs into a new knowledgeBaseKeys field, changing three employees' category, marking one as deprecated) and one deletes an employee. Each declares appVersion = '<2.2.0', so it runs only when the instance comes from a version before 2.2. A fresh 2.2.14 install creates those rows in their final form and skips all four. Checked the other direction too: no migration exists only on the fresh install.

So "the schema matches" is true and incomplete. Existing data went through transformations that new data never sees.

One trap while counting

A fresh install reports zero migrations at first.

select count(*) from migrations;   -- 0
Enter fullscreen mode Exit fullscreen mode

104 tables, 899 columns, one user, 76 plugins enabled — the install is finished. The table is just empty. Restart the app and it reads 18. Count before that restart and you will conclude that a fresh install runs no migrations at all, which is wrong.

What I did not measure

  • Skipping versions. I went 2.1.39 → 2.2.14 in one jump. Whether one-patch-at-a-time behaves differently — the thing the forum thread is actually asking for — is untested.
  • Anything more complex than one table block. No forms, no relations, no conditional visibility.
  • MySQL. PostgreSQL only.
  • Volume. My data was tiny, so migration duration and failure rates are unmeasured.
  • Third-party plugins. Stock image.

Downgrading, separately, is not supported. In an earlier, separate test (2.2.5 → 2.1.44), the rolled-back version started but was internally broken.

So

For 2.1.39 → 2.2.14 on PostgreSQL: nothing was dropped, the page I built still renders, plugin states and workflow configs came through untouched, and the result is structurally identical to a clean install.

That is a narrow claim, and getting it meant setting up separate instances and diffing their databases. The request in that forum thread — publish what each upgrade path does — is a fair one.

If you go measure your own instance, remember which table to open. It is flowModels.

(Measured on NocoBase 2.1.39 → 2.2.14 with PostgreSQL 16, September 20th 2026. latest was 2.2.15 at the time of writing. Behavior changes between versions; check yours.)

Top comments (0)