Add Autosave to an AI-Built App Form Without Losing Data
Autosave looks like a ten-line feature, and the first version an AI writes usually is. The data loss hides in the cases it did not think about.
Autosave means saving what the user has typed without a Save button: wait for a short pause in typing, send the draft to the server, show a clear status, and make one last attempt when the tab is hidden. An AI builder will produce the first half in seconds. The part that protects data, the last-chance save and the ordering of requests, is usually missing, so ask for it by name.
What the first AI draft usually gets wrong
Ask any assistant for "autosave on this form" and you tend to get a setTimeout that fires a request after the user stops typing. That is a fair start. These are the gaps worth checking in whatever it wrote:
Saving on every keystroke. Fine in a demo, expensive and flaky on a real connection.
Out-of-order responses. Two saves in flight, the older one lands last, and stale text overwrites newer text on the server.
No error state. The request fails, the interface still says Saved, and the user closes the tab.
Relying on a page-unload event. The browser may never fire it, so the final edits are lost.
No sense of what was saved. The app cannot tell whether the last confirmed save includes the last edit.
None of these show up when you click through the form once on fast wifi. They show up on a train.
Pick the triggers on purpose
Trigger | What it covers | Notes |
|---|---|---|
Pause in typing (about 1 to 2 seconds) | Normal editing | Debounce, so a burst of typing makes one request |
Field blur | Tabbing between fields | Optional, the debounce usually covers it |
Tab hidden ( | Switching apps, closing the tab, locking a phone | Your last chance to get unsaved text out |
Retry after failure | Flaky connections | Back off, and never silently give up |
On that third row, the reasoning matters. MDN says the beforeunload event is "not reliably fired, especially on mobile platforms", gives the example of a mobile user switching to another app and later closing the browser from the app manager, and recommends visibilitychange as the more reliable signal for saving app state. See MDN on beforeunload. If your AI-built app has a beforeunload handler doing the saving, that is the first thing to replace.
The working version
Here is a compact pattern with the pieces above. It uses an edit counter so the app always knows whether the server has seen the latest text, and it saves only one request at a time.
const form = document.querySelector("#draft-form");
const url = "/api/drafts/123";
let edits = 0; // bumps on every change in this tab
let saved = 0; // highest edit number the server has confirmed
let timer = null;
let inFlight = false;
const payload = () => JSON.stringify({ seq: edits, title: form.title.value, body: form.body.value });
form.addEventListener("input", () => {
edits += 1;
setStatus("Unsaved changes");
clearTimeout(timer);
timer = setTimeout(save, 1500); // wait for a pause in typing
});
async function save() {
timer = null;
if (inFlight || edits === saved) return;
inFlight = true;
const sent = edits;
try {
const res = await fetch(url, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: payload(),
});
if (!res.ok) throw new Error("HTTP " + res.status);
saved = sent;
setStatus(edits === saved ? "Saved" : "Unsaved changes");
} catch (err) {
setStatus("Could not save, retrying");
timer = setTimeout(save, 5000);
} finally {
inFlight = false;
if (edits !== saved && !timer) timer = setTimeout(save, 1500);
}
}
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden" && edits !== saved) {
navigator.sendBeacon(url + "/beacon", payload()); // POST, under 64 KiB
}
});
function setStatus(text) { document.querySelector("#save-status").textContent = text; }Three details carry the weight:
editsandsavedare the whole trick. If they match, nothing is pending. If they differ after a save returns, the user kept typing and another save is queued.Only one request is in flight at a time, so responses cannot arrive out of order from this tab.
On
visibilitychange,navigator.sendBeaconqueues a POST that the browser delivers without delaying the page. MDN notes the data is limited to 64 KiB of queued data and recommendsfetchwithkeepalivewhen you need another method or the response. See MDN on sendBeacon. A long document draft can exceed that, so check your largest realistic draft.
The server half your AI may not mention
The code above sends a seq number with every save. Make the server enforce it: store the highest seq seen for that draft and ignore any save that carries a lower one. That closes the stale-overwrite gap even when two tabs or a retry race each other.
If two people can edit the same record, a counter in one tab is not enough. You need a version check on the server and a visible message when someone else changed it. For a solo draft, the sequence rule is enough.
The prompt to give your AI builder
Add autosave to the draft form in this app.
Requirements:
- Debounce 1.5s after the last keystroke, one request at a time.
- Track an edit counter and the last confirmed counter. Show Saved
only when they match.
- Show an error state and retry with a delay if a save fails.
- On visibilitychange to hidden with unsaved edits, send the draft
with navigator.sendBeacon to a POST endpoint.
- Do not use beforeunload or unload for saving.
- The server must ignore a save whose seq is lower than the stored one.
Change only the draft form and its endpoint.The last line matters, because assistants love to refactor nearby code. Pin the scope before you read the diff.
Test it like a user on a bad day
Run these five checks before you call it done:
Type, wait two seconds, reload. The text should be there.
Type, then immediately switch to another browser tab. Reload later. The text should be there.
Go offline in DevTools, type, go back online. The status should move from error to Saved.
Throttle the network to a slow profile and type continuously for ten seconds. The saved text should end up equal to the final text, not an earlier version.
Open the draft in a second tab, edit both, and see which wins. Decide that on purpose.
If any check fails, the cause is usually one of the gaps listed at the top, and the techniques in debugging AI-generated code apply. For forms that must work with no connection at all, offline support in an AI-built app is the next layer. And if you are earlier in the journey, the overall picture is in how to build an app with AI.
Saving a draft is not the same as accepting a submission, so pair this with server-side form validation in an AI-built app.
Questions people ask
How often should a form autosave?
A pause of one to two seconds after the last keystroke is a common starting point. Saving on every keystroke sends far more requests than needed, and long gaps risk losing a lot of text.
Why not just save when the user leaves the page?
Page-unload events are not reliably fired, particularly on mobile. Saving when the page becomes hidden is the more dependable signal.
What if the autosave request fails?
Show an error state, keep the unsaved text in the form, and retry after a delay. The worst outcome is a Saved label sitting over text the server never received.
Does autosave replace a Save button?
Not always. For forms that submit something final, such as an order, keep an explicit submit. Autosave suits drafts, notes and settings that are safe to store half-finished.
How did this land?
About the author

Developer Advocate
Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.


