v0.1.96 — 11 Sep 2026
Action needed after you upgrade. Exports made before this release are
unreadable. Run node scripts/repair-export-storage-bucket.mjs to see which
rows need repair, then run it again with --apply. See
Exports were unreadable.
Security — the file list no longer reports the file count of other teams
Who is affected: Deployments that serve several teams from one database.
GET /api/files computed meta.total_count with an unfiltered count over the
whole daas_files table. The count ignored the filter parameter. It also used
the service role, so row-level security did not apply to it.
On a deployment that serves several teams, the number included the files of every team:
| Files that matched the request | 14 |
total_count that the API reported | 665 |
Size of the whole daas_files table | 665 |
The count disclosed a number, not file contents. filter_count held the length
of the current page instead of the filtered total. The Files page built its
pagination from the wrong number, so it also showed many empty pages.
The route now counts with the same scope, filter, and search conditions as the page query.
Added — promote data model changes between environments
Who is affected: Anyone who moves a data model from development to staging to production.
The schema endpoints now move a data model from one environment to another. The snapshot format is compatible with Directus.
- Call
GET /api/schema/snapshoton the source environment. - Send the snapshot to
POST /api/schema/diffon the target environment. The response lists the changes and ahashof the target schema. - Send that diff, unchanged, to
POST /api/schema/applyon the target.
Apply is one transaction
Apply runs the whole diff as one transaction. This covers tables, columns, foreign keys, row-level security for new tables, and the collection, field, and relation metadata. If one statement fails, every change rolls back. The schema is never left half applied.
Column types are recreated from the source, including varchar(n),
numeric(p,s), timestamptz, jsonb, and serial. New tables get the same
row-level security policies that POST /api/collections applies.
Merge is the default
| Mode | What it does |
|---|---|
merge (default) | Creates and updates only. |
mirror | Also deletes collections, fields, and relations that the snapshot does not contain. You must ask for it. |
A scoped snapshot (version: 2, made with includeCollections or
excludeCollections) never deletes a collection. System collections and
database views are never compared, and scope fields are never deleted.
Apply protects the target
Apply needs the hash from the diff. If the target schema changed after the
diff was made, apply answers 409 SCHEMA_MODIFIED. Generate the diff again.
A change that apply cannot perform answers 400, and nothing changes:
- a primary key change, or a change to auto-increment
- a conversion between an alias field and a database column
- enum, user-defined, and array column types
Unique constraint and index changes on existing columns are not migrated.
Existing callers keep working. The snapshot still includes the tables,
columns, and foreign_keys keys. A snapshot that has only those keys still
gets the table, column, and foreign key diff. Apply still accepts a raw SQL
operations array. Those SQL operations still run one at a time and stop at
the first failure, so they are not transactional.
See Schema API for the reference, and Promoting Changes Between Environments for a step-by-step guide.
Fixed — exports were unreadable
Who is affected: Anyone who exported a collection. Existing exports need a repair.
POST /api/utils/export/[collection] wrote the export file to the files
bucket, but recorded storage: 'local' on its file record. No bucket has that
name. Every read path takes the bucket from the record, so no export was
readable through the app.
The route and the record now use the same bucket, so the two cannot drift apart again. Exports made after you upgrade are readable.
Repair existing exports
Exports made before this release stay unreadable until you repair them.
-
Make sure that
NEXT_PUBLIC_SUPABASE_URLandSUPABASE_SERVICE_ROLE_KEYare set. -
Run the script without arguments. It writes nothing, and reports the rows that it can repair:
node scripts/repair-export-storage-bucket.mjs -
Read the report. Then run the script again with
--apply:node scripts/repair-export-storage-bucket.mjs --apply
The script repoints a record only after it confirms that the object exists in
the files bucket. It skips a record whose object is missing. A second run
changes nothing.
Fixed — a replaced file kept serving the old version
Who is affected: Anyone who replaced a file inside a folder, or in a bucket
other than files.
The replace request does not send the folder or the bucket of the file. The upload built its storage path from those absent values:
- A file inside a folder had its new bytes written to the root.
- A file in any other bucket had its new bytes written to
files.
The record kept its original path, so reads kept serving the old bytes. The
request still answered 200, so nothing looked wrong.
Replace now reads the folder and the bucket from the existing record. The size and type limits of the real bucket also apply.
A file that someone replaced inside a folder, or in another bucket, before this release still shows the old version. Replace it again after you upgrade.
Three more defects in the same path are fixed:
- Replace removed the old object before it repointed the record. If the record update failed between the two steps, the record pointed at a deleted object, and the file was permanently unreadable. The old object is now removed only after the record update succeeds.
- A replacement that changed the extension, or that also moved the file to another folder, left the original object in storage.
- On the file detail page, Save reverted the new file name to the old one after a replace, and the download link kept the old file name until a reload.
Fixed — the Add Existing picker showed pages that hold nothing
Who is affected: Anyone who links existing items through an M2M, O2M, or M2A field.
The collection list uses an estimated total by default. On a small table that the database seldom analyzes, the estimate can be much too high. v0.1.93 checks an estimate against the page that it returns, but a full first page can only prove a lower limit for the total. An estimate that is too high is not challenged, so the picker showed extra pages with nothing on them.
The Add Existing picker now asks for an exact count. The picker always lists a small table that a person browses, so the exact count costs little. Large collection views keep the estimated count.
Fixed — a failed delete showed a success message
Who is affected: Anyone who deletes files or folders in Platform Studio.
The Files page and the file detail page did not check the delete response. Two examples:
- A folder that still held files answered
400 FOLDER_NOT_EMPTY. - A delete without permission answered
403.
Both pages still showed “deleted successfully”. The detail page then left the file, so the user believed that the file was gone.
Both pages now show the error from the server. They show success only when the delete succeeds.