Skip to Content

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 request14
total_count that the API reported665
Size of the whole daas_files table665

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.

  1. Call GET /api/schema/snapshot on the source environment.
  2. Send the snapshot to POST /api/schema/diff on the target environment. The response lists the changes and a hash of the target schema.
  3. Send that diff, unchanged, to POST /api/schema/apply on 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

ModeWhat it does
merge (default)Creates and updates only.
mirrorAlso 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.

  1. Make sure that NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are set.

  2. Run the script without arguments. It writes nothing, and reports the rows that it can repair:

    node scripts/repair-export-storage-bucket.mjs
  3. 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.