Skip to content

[v3.84.1] Autosave silently does not fire on Safari (data loss — POST returns 200, DB stays empty) #16481

@oysi2025

Description

@oysi2025

Summary

On Safari (tested 17.5 and 26.4 on macOS), the admin edit view's autosave hook
emits zero PATCH /api/<collection>/:id?autosave=true requests while the
user types in localized fields. The form does emit Server-Action POST /admin/collections/<collection>/:id requests that return 200 with a
~800-byte body, so the UI shows a "last saved Xs ago" indicator and no error.
But the underlying _<collection>_v_locales.version_title (and other
localized fields) stay empty in the database. Editors believe their work is
saved; on reload, everything is gone.

The same Payload bundle, same user, same document, in Chrome works correctly:
PATCH ?autosave=true fires every ~1s and the draft persists.

Environment

  • payload@3.84.1
  • @payloadcms/next@3.84.1
  • @payloadcms/db-postgres@3.84.1
  • @payloadcms/richtext-lexical@3.84.1
  • Next.js 15.4.11, Node 22, Postgres 16
  • Reproduced on:
    • Safari 17.5 / macOS
    • Safari 26.4 / macOS (latest)
  • Working on: Chrome 147 / macOS

Collection config (relevant)

{
  slug: "posts",
  versions: { drafts: { autosave: { interval: 800 }, schedulePublish: true } },
  access: {
    read: ({ req: { user } }) => !!user || ({ status: { equals: "published" } } as never),
  },
  // localized fields: title, excerpt, content (lexical)
}
// localization: { locales: [fr, de], defaultLocale: "fr", fallback: true }

Reproduction

  1. Open the Payload admin in Safari (any version 17+), authenticated.
  2. Create a new post in a draft+autosave-enabled collection with localized
    fields. Stay on the default locale.
  3. Type a title. Wait the autosave debounce window (we use 800 ms).
  4. Watch the network tab and the database.

Expected: PATCH /api/posts/:id?autosave=true&...&locale=fr fires, DB
gets a new version row with the title.

Actual: zero PATCH requests. Only POST /admin/collections/posts/:id
requests fire (Server Action). Returns 200. DB row in _posts_v_locales
stays empty.

Evidence — same user, same document, two browsers

Server access log for the same authenticated user on the same post,
back-to-back sessions:

# Safari 26.4 (n=3 typing events)
POST /cms/admin/collections/posts/851 → 200  (body 832B)
POST /cms/admin/collections/posts/851 → 200  (body 814B)
POST /cms/admin/collections/posts/851 → 200  (body 822B)
# Zero PATCH /cms/api/posts/851?autosave=true

# Chrome 147 (same role, same doc, n=4 typing events)
POST  /cms/admin/collections/posts/850                       → 200
PATCH /cms/api/posts/850?autosave=true&...&locale=fr         → 200  ✓ persists
POST  /cms/admin/collections/posts/850                       → 200
PATCH /cms/api/posts/850?autosave=true&...&locale=fr         → 200  ✓ persists

DB after the Safari session:

SELECT v._locale, v.version_title FROM _posts_v_locales v
JOIN _posts_v p ON v._parent_id = p.id WHERE p.parent_id = 851 AND p.latest;
 _locale | version_title
---------+---------------
 fr      |
 de      |

DB after the Chrome session (same user role, same flow):

 _locale | version_title
---------+----------------------
 fr      | EDITOR FR TEST
 de      | Titre TEST admin v2

Ruled out

  • Role/access: tested with editor role in Chrome — autosave fires
    correctly and persists. Verified /api/<col>/access/:id returns
    {update: true, ...} for both admin and editor.
  • Custom auth strategy: same custom auth strategy (validating a
    session cookie via payload.auth({headers})) used in Chrome and Safari
    sessions; resolves the user identically.
  • CORS / WAF / upstream proxy: zero 403/blocked requests in our
    edge or origin gateway logs during the Safari session.
  • Document lock: payload_locked_documents table empty for the doc.
  • Collection access functions: defaults to Boolean(user) for
    update/create — no custom rule in play.

Hypothesis

Something in the autosave hook chain (useDebounce / useDeferredValue /
useEffectEvent / queueTask) or in requests.patch() (which is plain
fetch(url, {method: "PATCH", credentials: "include", body: formData}))
is silently no-op'ing on Safari/WebKit. The fact that the Server-Action
POST path still fires suggests the form itself is functional — only the
client-side autosave PATCH submit is dropped.

We have not yet identified whether the fetch call throws, the hook
never runs, or requests.patch resolves without a network round-trip.
Happy to attach Safari Web Inspector traces if useful — let me know what
would help triage.

Workaround

Hard banner at the top of every editable doc, detecting WebKit-non-Chromium
UA, telling editors to use Chrome/Firefox until a fix lands. Better than
silent data loss but obviously not a fix.

Impact

Two editors lost their work before we identified the cause; our prior
"fixes" (validated only in Chrome) gave false confidence. This is
effectively silent data corruption for any editor on Safari — a common
browser on macOS.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions