payloadcms / payloadcms/payload

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

Open
#16,481 1 comment 0 reactions 1 assignee View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
44.8k
Forks
4.2k
Avg merge
2d 21h
Merged PRs (30d)
53

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.