18.1.0
Release notes and upgrade checklist for 18.1.0, the first minor of the v18 line: a minor that carries breaking changes. ADR-0131 finishes (an organization-less row confers nothing), a production os serve with no organization object is refused, nine metadata narrowings with automatic conversions, one federation rule at every datasource door, and an unreadable SQLite file is refused instead of replaced.
How this page was compiled. From the 18.1.0 changesets on main at
b2c0752900 (2026-10-23), before the 18.1.0 cut, through a rehearsed
pnpm run version in a throwaway clone: 73 packages move to 18.1.0, and their
## 18.1.0 sections hold 227 distinct entries. The five sections below use
the grouping the GitHub Release body uses, and the counts are that grouping's:
73 need you to act, 9 are converted for you, 41 add capabilities,
0 declare a semantic change, and 104 are other fixes. Nothing here was
read off a published 18.1.0 package or walked on an upgrade yet: every refusal
is described as its changeset describes it. Anything that lands on main before
the cut is in the CHANGELOG and not on this page.
Highlights — 18.1.0
- ⚠️ A minor that breaks. 18.1.0 is a minor version, and 82 of its
changesets are declared breaking. Under the level rule the maintainer ruled
on 2026-10-21 (#25105, "ruling A"), the v18 line takes no further major: a
change to the metadata protocol takes
minor, and every other change takespatch, breaking or not. A non-protocol breaking change keeps its BREAKING banner and its ADR-0087 disposition. A^18.0.0range resolves 18.1.0 by itself. Read the version number before you let it. - ADR-0131's last steps: an organization-less row confers nothing. A grant row
with no organization applies nowhere (
94842ba, #24872), a settings or localization row with no organization answers no organization's read (279fdcf, #25226;d196748, #25301), the first organization no longer adopts orphaned rows (e3190b9, #25246), and a table the SQL and Turso drivers create carries its tenant columnNOT NULL(5e9b100, #24940). On a database 18.0.0 created or the ownership ceremony certified, none of these rows exists. - A production
os servewith no organization object is refused.--no-serverno longer gets round it (ab488d9, #25155), and neither does a@objectstack/plugin-auththat fails to load (b2e964f, #25231). - Nine metadata narrowings come with a conversion that
os migrate metaand the stored-row loaders apply: theaddRecordblock,userfields that reference another object,has()invisibleWhen, theField.selectshorthand, tursoencryptionKey, the docs-viewer keys,externalon a managed datasource, andsys_notification_subscription. - One federation rule at every datasource door. An object's
externalbinding on a managed datasource is refused at parse, atdefineStack, at the metadata save doors, atcomposeStacks, at the datasource admin API and by Sync, as ADR-0015 §4.2 declares (fe0ec2b,bda53bb,b9c7a8f,72a302a). - A SQLite file the driver cannot read is refused, never replaced. A
corrupt or non-SQLite file fails
connect()naming the file, where a development boot used to move it aside and start empty (7f99616,d6a5fa3,27dc53b,4d99158). - A disabled package is disabled on every door. REST actions and the MCP
bridges, stdio and
/mcp, read objects and actions from the same merged view the Data API serves (258f4e6,eee2f0d,8010b64,a1e51d6,fb7501b,882372d,72fad92). - better-auth 1.7.3 → 1.7.12, for the security fixes published since
(
03966ce, #25109). Sign-ins begun before the upgrade must be started again. - A cloud-managed environment closes its local sign-up doors. This applies
only to a process started with
OS_CLOUD_MANAGED=1, which ObjectStack Cloud sets on its hosted containers (c6bdcac, #25399). It ships aspatchand is breaking: see Cloud-managed environments. Self-hosted deployments are unchanged.
What's new in 18.1.0
The version number is not the signal
Every @objectstack/* package still versions in lockstep. Since ruling A, the
level of a release says whether the metadata protocol moved, and nothing
else:
| Kind of change | Level |
|---|---|
| Added to the metadata protocol: a new authorable key, accepted value or metadata type | minor |
| Narrowed or retired: a key removed or tombstoned, a value refused, a requirement tightened | minor |
| Semantic: the meaning or default of an authorable key changes | minor |
Everything else: REST, drivers, services, the CLI, the Console, feat: and fix!: alike | patch |
So a patch release can break a REST client, a boot or a deployment
script, and 18.1.0 carries 37 changesets graded patch that say BREAKING,
beside 45 graded minor ones. Pin exact @objectstack/* versions, and read the
release notes for every version you cross. Each entry in a CHANGELOG states its
kind on its own Metadata-protocol: line, and the GitHub Release body groups the
entries under the same five headings this page uses.
Breaking changes you must act on
The 73 entries that are breaking and have no ADR-0087 conversion, and one boot
refusal (db1c7cc) that its changeset declares as an addition. Each line names
the one fix; the CHANGELOG entry carries the full FROM → TO.
Boot and deployment
- A production
os servethat composes no organization object is refused,--serveror not (ab488d9, #25155). In 18.0.0, a productionos serve --no-serverbooted when auth was on with no secret, or when the stack mounted no auth at all, and wrote rows with no owner. Now it is refused like the server boot, and a migrate-and-exit job (OS_MIGRATE_AND_EXIT=1) is too. Fix: setOS_AUTH_SECRET, and enable auth on a stack that mounts none. A development boot (os dev,--dev) and an embedded kernel are not affected. - A production
os servewhose@objectstack/plugin-authfails to load is refused, naming the load error (b2e964f, #25231),os startincluded. 18.0.0 booted it without auth, silently or with one⚠ AuthPlugin failed to loadwarning. Fix: reinstall the deployment's dependencies (npm ci,pnpm install --frozen-lockfile). - CORS reads one admission rule at every door (
02e6d73, #24873). A loopback origin beside an origin list is admitted on a development boot only, and an all-host pattern with credentials on fails the boot, as'*'already did. A comma-separatedcors.originstring passed tocreateHonoAppnow admits each origin it lists, where it admitted none. Fix: name your origins inOS_CORS_ORIGINorcors.origin, including any loopback origin a non-development deployment is reached from, or setOS_CORS_CREDENTIALS=false. OS_CLOUD_MANAGED=1withoutOS_CLOUD_API_KEYrefuses a production boot (db1c7cc, #25306), and any value ofOS_CLOUD_MANAGEDother than1or empty is refused. A runtime that binds its control plane only throughCloudConnectionPlugin'scontrolPlaneApiKeyoption is refused too: setOS_CLOUD_API_KEY.- better-auth 1.7.12 (
03966ce, #25109). A Magic Link, OAuth, social, SAML or account-linking flow begun on 18.0.0 fails after the upgrade and must be started again. An OIDC SSO provider whose configured issuer differs from its discovery document'sissuer, even by a trailing slash, is refused withissuer_mismatch. A PKCE mismatch atPOST /api/v1/auth/oauth2/tokenanswers400 invalid_grant, not401. Fix: tell users to restart pending sign-ins, and set each OIDC provider's issuer to the exact string its discovery document publishes. os migrate applyexits1when it applied nothing because a confirmation was missing (62bc129, #25004), as every otheros migratesubcommand with a--yesgate does. Fix: a script that means to apply passes--yes; one that only wants to know runsos migrate plan.
Organization ownership (ADR-0131)
On a database that 18.0.0 created, or that os migrate organization-ownership
certified, none of the rows these entries stop reading exists. They reach you
only if an embedding writes rows with no organization, or a database skipped
the ceremony.
- A grant applies only in its own organization (
94842ba, #24872). Asys_user_positionorsys_user_permission_setrow with no organization applies nowhere, and undersingleonly the Default Organization'sadmin_full_accessgrant anchors platform-admin standing (abc25a3, #25141). Fix: an embedding that composes@objectstack/plugin-securitywithout plugin-auth registers the organization object. - Settings and localization read the caller's organization only
(
279fdcf, #25226;d196748, #25301;9d60b42, #25350). A tenant- or user-scopesys_settingrow with no organization no longer answers every organization. Fix: runos migrate organization-ownership --out PLAN, then save each reported value again from inside its organization. A value meant for every organization belongs on a key declared at scopeglobal. A host that binds its own engine states its organization source atbindEngine. - The first organization no longer adopts organization-less rows:
claimOrphanOrgRowsis retired (e3190b9, #25246). Fix: on a walled store an earlier release created, run the ownership ceremony. - A table the driver creates carries its tenant column
NOT NULL(5e9b100, #24940), so a write that names no organization is refused at the database. Fix: carry the organization on the write ({ ...row, organization_id }orcontext.tenantId), or declare an object whose rows belong to no organization withsystemFields: { tenant: false }. - Deleting an organization that still owns records is refused with
409and a per-table count (688052e, #25016). An empty one is deleted whole, with its teams and organization-admin grants. Fix: delete or move its records first. - The Default Organization does not move by slug (
09e07c2, #25286;8dfd126, #25265). Undersingle, re-slugging another organization todefaultis refused403, and creating a second organization is refused when it would leave nobody who can administer. Fix: keep the slug, and move anadmin_full_accessgrant into the Default Organization instead.
Metadata the build and the save doors now refuse
Each is an error at os validate, os build, os lint and the matching save
door (422 INVALID_METADATA), where 18.0.0 accepted it.
- An HTML email template body with a hole where the mail renderer's encoding
does not suit it (
9127f23, #25212), and the same for an html or mjmlsys_notification_templatebody (284e048, #25258). Fix: move the hole into element text or a quoted plain-text attribute. For a link, declare the variabletype: 'url'and write it triple-braced as the whole double-quotedhref. - A
validations[]rule whose predicate readsparent(c5915b2, #25052). Fix: put it on the field'sreadonlyWhen/requiredWhen, whereparentis bound, or use a lifecycle hook. - A page component whose
propertiesis present but not an object (c0ae356, #25067), and a props CEL slot over the platform's parse bounds (31f0f27, #25088). Fix: writepropertiesas an object, and shrink the predicate (an||chain becomes onein [...]test). - The retired list-view
filterableFields, named (b42c621, #24878). Fix: delete it; a curated filter bar isuserFilters. - A
controlled_by_parentobject whose one requiredmaster_detailisreadonly: trueorsystem: true(02ad31a, #25181), and every write that sets such a reference (0148071, #25256). Fix: declare the reference as an ordinary required field, or give the object its ownsharingModel. - A stack that declares a webhook while
requireslacks'webhooks'or'realtime'(baedcec, #25057), refused bydefineStackwithSTACK_WEBHOOK_CAPABILITY_REQUIRED. Fix: add both tokens. - A flow
get_recordnode'slimitthat is not a whole number of at least 1 (303aef0, #25357). - A
controlled_by_parentdetail whose master the engine cannot compose a read over refuses the boot (504d8b3, #25166), where it booted and failed at read time. rollbackMetaItemandrevertCommitrefuse a version the save door refuses (69913f8, #25312), instead of writing it back live.
Datasources and drivers
- An object's
externalbinding on a managed datasource is refused from either side of the pair at the metadata save doors andcomposeStacks(bda53bb, #25257), and the datasource admin API refuses to make a datasource managed while objects bind it (b9c7a8f, #25431). Sync (remote tables, object draft, import, catalog refresh) refuses a managed datasource with400(72a302a, #25167). Fix: declare the sourceschemaMode: 'external'(or'validate-only') first, then bind objects; to make it managed, removeexternalfrom the objects first. - A SQLite file that is not a readable SQLite database fails
connect(), naming the file (7f99616, #25006;d6a5fa3, #25080), on the wasm driver too (27dc53b, #25200). A development boot no longer moves it aside and starts empty, and a read-only boot no longer opens an empty in-memory database over a file it cannot open or create (4d99158, #25007). Fix: restore from a backup, or move the file aside yourself; an absent file is created empty. - On PostgreSQL,
introspectSchemareads each table in its own schema, and refuses asearch_paththat reaches one table name in two schemas (74fcec1, #24920). Fix: point the connection at one schema.
Security: master-detail writes and delegation
- A
controlled_by_parentdetail's update, delete and batch create ask for edit access on every master involved (b289090, #25042;f46ef4d, #25066;3782b2d, #25090), and an update no longer empties a detail's master reference. Fix: grant the caller edit access on the master, and move a detail by writing another master's id, nevernull. - On a walled posture, a delegated (on-behalf-of) operation runs only in an
organization the delegator belongs to (
2e242bb, #25101;331613c, #25150;5801a4f, #25288). Fix: code that builds anExecutionContextwithonBehalfOfby hand setstenantIdto such an organization, or leaves it unset. - Deleting a business unit that still has child units is refused
(
4b8f597, #24984). Fix: re-parent or delete the children first.
REST, MCP and API clients
- A disabled package's actions are refused on REST (
258f4e6, #25305:404 OBJECT_PACKAGE_DISABLEDonPOST /api/v1/actions/OBJECT/NAME;eee2f0d, #25318: the "no declaration"404on.../global/NAME), and the MCP bridges read the merged view, solist_objects,describe_object,list_actions,run_actionand theobjectstack://objectsresources agree withGET /api/v1/meta/object, and an object declaredapiEnabled: falseis refused by every data verb (8010b64,72fad92,a1e51d6,fb7501b,882372d). Fix: nothing if the declarations say what you mean; otherwise setenable.apiEnabled, or re-enable the package. - The Data API refuses a malformed or negative page window with
400naming the parameter (17f3c65, #25260), and the published list-query schemas declare it a non-negative integer (5f30f00, #25300). Fix: send a whole count of zero or more, or leave it out. GET {base}/docs, the OpenAPI HTML viewer page, is retired (1b3b65e, #25124).GET {base}/openapi.jsonstays: point your own viewer at it.- The
/metaread doors no longer serve a webhook'ssecretorheaders(e939357, #25036). - The CSV export marks a text cell its consumer would interpret with one
leading apostrophe, and the CSV import reverses it (
c4b49c3, #25130). A program that reads the export some other way strips that apostrophe itself. - OAuth application registration takes
webornativeonly (5872450, #25038;ba244f1, #25099). Another type is refused400 INVALID_REQUEST, where it was registered asweb. POST /api/v1/packages/:id/adopt-orphansrefuses a read-only package with422 WRITABLE_PACKAGE_REQUIRED(4cef6e2, #24861). Adopt into a package you own.- A cloud-fetched marketplace install whose package ships a position,
permission set or capability name an environment-authored item holds is
refused before any write (
8d9e8b9, #25435), as the file-import install already was. - A marketplace upgrade that would un-make an access choice is refused until
it is confirmed (
448374d, #25355). Re-send the request with"confirmAccessUpgrade": "<findingSet>", or runos package install <package> --confirm-access-upgrade <findingSet>; the token confirms exactly the findings it names.
Messaging, settings and Cloud Connection
sys_notification_templaterefuses a row for a channel other thanemailorsms(d68aab6, #25028), and an html or mjml body escapes every hole's value for markup (a39389e, #25076). Fix: delete or re-channel the row; move markup a payload carried into the template body.- A notification preference's User is a
sys_userreference, and the default for every user is an empty User instead of'*'(e282f32, #25178). The boot rewrites the old'*'rows and reports a collision it cannot resolve. - A settings value that misses the format of its
email,url,phoneorcolortype is refused, saved or set as anOS_*override (3b3830c, #25022). Fix: correct the value, or unset the variable. - Branding drives the console's name, logo and colour under
single,theme_modeandsupport_emailare retired, and undergroupandisolatedthe Branding namespace is not offered (78ab647, #24959). On a walled deploymentOS_BRANDING_WORKSPACE_NAMEno longer names sign-in emails and the authenticator entry: setOS_APP_NAMEinstead. - Cloud Connection follows one rule (
c71d03a, #25143;ae58b26, #25110). Its page and its connect and disconnect requests requiremanage_platform_settings, so an organization administrator holding only Setup Access loses them. A runtime holding the environment service key is platform-managed: it serves no Cloud Connection page and refuses to disconnect.
Cloud-managed environments (OS_CLOUD_MANAGED=1)
c6bdcac (#25399) is graded patch, because no metadata moves, and is
BREAKING for a process started with OS_CLOUD_MANAGED=1. Under that mode,
every audience posture but invite_only is refused, email sign-up is off,
member invitations (resends included), admin create-user and import-users
answer 409 RESOURCE_CONFLICT, an admin password set on an identity-provider
account is refused, and only a platform administrator may set a first password.
People are invited from the organization in the cloud console and arrive on
their first sign-in; keep a local password only for the break-glass
administrator. A self-hosted deployment, which does not set the variable, is
unchanged.
Code against the published packages
| Removed | Use instead |
|---|---|
GLOBAL_TENANT from @objectstack/objectql (df8bb40) | GLOBAL_TENANT from @objectstack/driver-sql, or the literal '__global__' |
PLATFORM_OBJECT_TENANCY, classifyPlatformObjectTenancy, isPlatformObjectOutOfTenantAuditScope, tenantScopedPlatformObjects (3a440b0, #25210) | resolveTenantFieldName(schema) on the registered schema: non-null means in tenant scope |
renderNotification from @objectstack/service-messaging (1f78b64, #25285) | a sys_notification_template row, rendered by the channel at delivery |
Under strictReadonlyWrites, a write whose value a stored expression replaces (03c12a5, #25086) | leave the field out, or send the computed value; it is now refused |
Converted for you by os migrate meta
Nine narrowings carry an ADR-0087 conversion on the protocol-18 step. A live
author is refused at parse, with the prescription. Stored rows and built
artifacts are converted where they load, and os migrate meta --from 17 lists
the edits for your sources (--write applies the ones it can trace to one
literal). os migrate meta --stored lists and rewrites a deployment's stored
rows.
| Change | ADR-0087 id | The fix |
|---|---|---|
A list's addRecord: enabled is required, position defaults to top, mode and formView are retired (fde7456, #24970) | add-record-mode-form-view-removed, add-record-enabled-defaulted | write enabled: true (or false) and delete mode / formView. A stored block that omitted enabled drew no Add button; converted, it draws one |
A user field whose reference is not sys_user (2069b81, #25074) | user-field-foreign-reference-refused | change it to a lookup on that object, and choose its deleteBehavior |
A visibleWhen that hands has() anything but a field selection (f5b6b3f, #25313) | visibility-predicate-has-argument-refused | has(record.status) or 'status' in record; size(record.items) > 0 for a list |
The Field.select string shorthand keeps every character it does not document transforming, so 'no-overlay' is refused (386f60f, #25122) | field-select-shorthand-keeps-written-value | write the option explicitly with the value you store |
turso encryptionKey is removed: it never encrypted anything (2354058, #25153; 28c9a2c, #25045) | turso-config-encryption-key-removed, turso-config-local-encryption-key-refused | delete it. Beside a local file the conversion keeps it, so the row fails at driver construction until you delete it by hand |
The docs-viewer keys ApiDocumentationConfig.ui, openApi.uiPath, openApi.uiFramework (14e7239, #25180) | api-docs-viewer-keys-retired | delete them; ApiTestingUiConfigSchema and ApiTestingUiType are no longer exported |
An object's external binding on a managed datasource (fe0ec2b, #25192) | object-external-binding-managed-datasource-refused | bind it to a datasource declared schemaMode: 'external' |
sys_notification_subscription retires (63085c3, #25024) | sys-notification-subscription-retired | delete references to it; mute a topic with sys_notification_preference. The table stays in place; drop it by hand if you want |
New capabilities in 18.1.0
os db orphansreports the detail rows whose required master-detail master does not exist, and deletes them only behind--delete, after a pre-delete export (157ebd0, #25039).os migrate planlists the tenant columns still nullable and names the voluntary ownership ceremony that tightens them (6251f59, #25179), andos migrate applyon an empty database writes the birth marker, so a fresh install's firstos servestarts (19484af, #24875).- Package upgrades report what they un-make. At boot an administrator is
told when a package upgrade renamed, removed or collided with an access item
the customer switched off, cloned or authored (
54df640, #25311), and the marketplace door asks for confirmation (above). - Setup → Positions says who holds each position and what it grants,
through
GET /meta/position?include=grants(27b3f1f, #25369). Create User can grant a permission set in the same step (3306cad, #25381), and Create Key offers the key's expiry (c898bd8, #25371). - Notifications in each recipient's language.
EmitInput.keyedTextcarries catalog keys rendered per recipient at delivery (58cd58f, #25014), and every approval notification uses it. - Translations for
page:tabsitem labels (30d21f6, #25005), form view titles (eddffc3, #25035) and approval step labels (b9a79ef, #24943). - Flows.
ExecutionStatusgainsmissed, for a scheduled due time that passed while no kernel was ticking (d12e650, #25363), and a failed run carries the refusal its write node met asuserMessage/innerRefusal(045858f, #25037;9cfc575, #25147). - A bulk action's
confirmTextis atemplateslot overcountand the action's params (925fd07, #24947), and a dataset dimension may name its null bucket withnullLabel(7c68b10, #25397), which the Console draws. - A release artifact's package entry may state its own
source(b964ea1, #25359), so marketplace installs merged into an artifact list asmarketplace. - Sign-up no longer reveals an existing account under the
openandemail_domainpostures while mail is really delivered (2aaee3b, #25145).IEmailService.isConfigured()answers whether it is (fccbd9d, #25125). GET /api/v1/auth/configservesfeatures.cloudManaged(f4112a7, #25309), and the Console hides the six doors that add people while it is on.
Semantic changes in 18.1.0
No changeset in 18.1.0 declares Metadata-protocol: semantic. A change of
meaning that also breaks is listed with the breaking changes above.
Notable fixes in 18.1.0
- Upgrade and migrate.
os migrate planandapplyname the command each held drop waits for, never--allow-destructive(5f11cec, #24954;1a1f5c3, #25029), and stop offering it for a unique index the existing rows already violate (8c73398, #25217). An organization-scoped unique index keys the plain organization column (d5d8146, #24893). - Writes.
update()on PostgreSQL and MySQL stores webhook deliveries for record updates again (eee65af, #24906). An explicitorganization_idthe wall admits is stored as sent (2b1382a, #25048;5050442, #25094). A stored expression that reads anautonumberstores the numbered value (c8f4c43, #25218). - Auth. SSO domain verification accepts the TXT record it tells the
operator to publish (
425c743, #25161). An impersonation session is not held by the target's forced password reset (c2d4072, #24762). The vendored auth library keys on the client address the server established (ad8cf92, #24871;2941a19, #24931). The Magic Link mail states its real five-minute lifetime (d609f60, #25238). - Approvals and messaging. Every step of a multi-step approval names the
submitter (
28ff8a9, #24860), the inbox and record panel name flow, step and object in the reader's language (ba69b16, #24977), and a digest heading is rendered in its recipient's language (c24203f, #25170). - Datasources. A missing database client (
pg,mysql2,better-sqlite3) or optional driver package is refused with its install line (ea08ce5, #24933;b811c73, #24971). - Webhooks. A declared webhook the deployment can never deliver, and a
delivery the outbox could not record, are reported at
errorwith the fix (19e5338, #24946;3fbef41, #24862). - Setup. Passwords typed into action dialogs are masked (
7035682, #24867), forms show option labels instead of machine values (bdf762c, #25370), and the scaffolds ship a starter test so a new project'snpm testpasses (6bc8cc6, #25236;9c431ba, #25196).
The other fixes are in the per-package CHANGELOGs.
New in Console (Studio) — objectui pins in 18.1.0
18.0.0 bundled objectui at 616df6598b42. 18.1.0 bundles it at the pin
committed at the cut, 1d178e06ac5e when this page was compiled, through six
refreshes, each with its own @objectstack/console changeset: 91 entries that
objectui declared, five of them minor.
- Approvals. An approver who is not an administrator sees the whole approval request page again, and the Approvals Inbox opens a request on the page the platform serves for it.
- Setup. Positions shows holders and granted permission sets; Permission Sets and Positions ask before the Active switch turns an item off; Settings pages block Save on a value that misses its type's format or a required value left empty; Datasources offers Sync only where it can sync.
- Records and lists. The record heading draws a
nameFieldthrough its widget's read face, the/datafilter bar offers single-value relation fields, selecting a cell's text no longer opens the record, and a grid grouped by a date heads each group the way the cell draws it. - Safety and access. The console's menus, palette, inbox and language menu
ask before they drop unsaved edits; a member who can read none of an app's
objects lands on one page saying whom to ask; a page node or tab whose
visibleWhenfaults is no longer drawn. - Charts and branding. A dataset chart draws its null bucket under its
nullLabel, a single-series bar chart uses its series colour, and the console document carries the runtime branding in its<title>and Open Graph tags (7b047b4, #25317).
The full list is in the @objectstack/console CHANGELOG.
Shipped in 18.0.0, listed first in 18.1.0's CHANGELOG
Eight entries are marked Shipped in 18.0.0: their commits landed before the
18.0.0 version commit, so their code is in the 18.0.0 packages. Two reach an
upgrade, and the 18.0.0 notes
describe both as measured on 18.0.0: a production os serve with no auth
secret is refused (df8bb40, #24845), and the organization-less readers of
sharing rules, approval units and the seed loader retire (7cacad5, #24828).
Upgrade checklist
⚠️ For an 18.0.0 app. From 17.x, walk the 18.0.0 checklist first: its database step, the organization-ownership ceremony, is the one an 18.1.0 server also requires.
18.1.0
Not walked yet. This list is compiled from the changesets before the cut. No app has been moved from 18.0.0 to 18.1.0 for it, and no refusal below was read off a published package.
1. Move the pins
Move every @objectstack/* dependency to 18.1.0 together, and
@objectstack/skills with them, as in the
18.0.0 walk. Pin the exact version:
a caret range takes every later 18.x by itself, and a patch can break a
non-protocol surface.
2. Before the first boot
- Production
os serveoros start: setOS_AUTH_SECRET, and do not rely on--no-serverorOS_MIGRATE_AND_EXIT=1to boot without it. - A stack that mounts no auth (
authtier off,--preset minimal) fails a production boot: enable auth. - CORS: list every cross-origin front-end in
OS_CORS_ORIGIN, loopback origins outside development included. With credentials on,'*'and all-host patterns fail the boot. OS_CLOUD_MANAGED=1needsOS_CLOUD_API_KEY.- Every OIDC SSO provider's issuer matches its discovery document exactly.
- Tell users that sign-ins begun before the upgrade must be started again.
3. Run the build, and fix what it refuses
os validate
os build
os migrate meta --from 17 # the step-18 conversions, listed for your sourcesFix each refusal with the line in
Breaking changes you must act on and
Converted for you. The ones an app is
most likely to meet: addRecord without enabled, a webhook without
'webhooks' and 'realtime' in requires, an HTML email template hole, a
validations[] rule that reads parent, and an external object on a managed
datasource.
4. After the boot
- Run
os migrate meta --storedto list stored rows the step-18 conversions rewrite. - Run
os migrate plan. On a database 18.0.0 created, it lists the tenant columns still nullable; the ownership ceremony tightens them toNOT NULLwhen you choose to run it. - If the boot reports a notification preference collision, resolve it with
os migrate duplicatesand restart. - Check that REST and MCP callers do not depend on a disabled package's actions
or on an object declared
apiEnabled: false.
v18
The v18 line and its GA release, 18.0.0, which closes the `18.0.0-next.N` prerelease train and publishes to npm's `latest` tag. It carries ADR-0131 (organization ownership is total, with no NULL `organization_id`) and landed its breaking changes in stages, each with its own migration.
18.0.0
The v18 GA release, published 2026-10-20: organization ownership is total (ADR-0131), so a database 17.x created takes the ownership ceremony before 18.0.0 serves it; protocol 18; a flow reads one expression dialect per slot; the security catalog is metadata; API keys expire; the CORS default admits no cross-origin caller. With its upgrade checklist, walked from 17.7.0.