Last updated 31 August 2026 · applies to version 0.1.0
Form For Later is built and published by Plai Faiar LLC, which is the data controller for everything described on this page.
The short parts below are for anyone. If you want to check a claim yourself, every section has a “Show the technical detail” toggle with the exact file and function behind it — so nothing here is just our word.
FormForLater keeps what you type into a form right there in your browser. There's no account, and nothing is sent anywhere unless you choose to share it — and even then, it's encrypted before it ever leaves your device.
We host one extra thing: the page someone opens to read a share link. It's a plain file, and it can't read your link either — the part of the address that carries your answers and the key is the part browsers never send to a server.
Passwords, card security codes and one-time codes are never written down. Not encrypted, not hidden — never saved, whatever your settings say.
If you sign up for the iPhone launch list, we save your email address, and use it for exactly one thing: telling you when the app is out. More under The iOS waitlist.
Everything you save lives in your browser's own storage, on your device. For each answer: what you typed, the question it answered, and the page it came from. There's also a random ID for your browser profile, so your records don't get mixed up with anyone else's on a shared computer — it isn't tied to you personally, and it never leaves your device.
Everything lives in chrome.storage.local, the
private storage Chrome gives this extension inside your browser
profile. Four kinds of thing are kept:
| Key | What it holds |
|---|---|
fk.bundle.<id> | a saved record: the answers you saved and how to find those fields again |
fk.index | a list of records for the sidebar: title, site, dates, counts |
fk.settings | your settings |
fk.deviceId | a random identifier for this browser profile |
The fk. prefix is left over from an earlier name
for this extension. It was kept on purpose: a build looking under
new key names would find no records, report none, and start
fresh, while everything already saved sat in the profile under the
old names — unreachable, with no error shown. The names are
invisible in use, and nothing is worth that risk.
Where: src/lib/storage.js
— the key names at the top of the file, and
putBundle.
For each answer you saved: the text you typed and its
human-readable form where those differ (the visible text of a
chosen dropdown option, for instance), the question's visible
label, the heading it sits under, the field's name,
id, type and autocomplete
attributes, several ways to find that field again on the page, a
sensitivity classification, and whether the field was hidden when
it was read.
For the page itself: its title, its address, the time you saved it, and a few notes about what was found there — including how many fields were skipped for being too sensitive to keep, and whether part of the page could not be read.
The record also carries a copy of the device identifier described below. It is removed from an export, and it is never in a share link.
Where: src/content/extract.js
— extractPage, the object pushed into
fields, and the page object it returns, including
notes. src/lib/storage.js —
saveCapture, which stamps the record with the device
identifier.
Alongside those, each saved page keeps a list of the
name attributes and visible labels of
every form field on it, filled in or not. This is
what lets a blank form recognise the record belonging to it.
Where: src/content/extract.js
— formShape and formCaptions in
extractPage.
A record includes the address of the page you saved it from. Only for pages where you pressed Save. FormForLater does not keep a history of pages you merely visit.
fk.deviceId is a random value generated in your
browser on install, using the browser's own cryptographic random
number generator. It is not derived from anything about you or
your machine, it is not an account, and it is never sent anywhere
— it does not appear in a share link.
Where: src/lib/storage.js
— getDeviceId; src/lib/util.js
— uid; src/background/service-worker.js
— the onInstalled handler. That it is absent
from shares can be checked in src/lib/share.js
— buildSharePayload, which lists exactly what
a share contains.
Passwords, one-time codes and card security codes are thrown out the moment the page is read, before anything gets written down. Not even the field's name is kept, so nothing left in storage says a password field was ever there.
One exception, so the extension can still be honest with you: it keeps a plain count of how many were skipped on a page, so it can tell you “2 passwords or codes skipped” instead of just going quiet. The count is a number and nothing else.
Fields classified critical are discarded during
reading, before anything is written. That covers:
type is password,
or whose autocomplete is
current-password, new-password or
cc-cscWhere: src/lib/sensitive.js
— the RULES, TYPE_LEVEL and
AUTOCOMPLETE_LEVEL tables;
src/content/extract.js — the
critical check in extractPage, which
stops before the field is added.
Nothing that identifies such a field is kept — not the
value, not its name, not its label. They are also
left out of the field lists described under
What's saved, so a raw dump of storage
contains no trace of them.
One exception, stated for accuracy: the record keeps a count of how many fields were skipped this way on each page. The count is a number and nothing else — no name, no label, no value, nothing that says which fields they were.
Where: src/content/extract.js
— the critical check sits before
shape.add, and increments
notes.skippedCritical.
If a field of this kind ever reaches a record — through an older record, or a classification rule that changed between versions — it still cannot be put into a share link, whatever the settings say and whatever you switch on for that field.
Where: src/lib/storage.js
— resolveField.
Almost nothing, on its own. FormForLater's code runs on the pages you visit so it's ready to read a form the moment you ask it to, but it doesn't look at anything or send anything until you click Save or Restore. In the whole codebase there's exactly one place that makes a network request, and it only runs if you've set up the optional relay yourself.
No analytics, no crash reports, no telemetry, and no code loaded from the internet while it runs — every script it ever executes ships inside the package you installed.
FormForLater's reading code is loaded into every page you open, including frames inside those pages. It defines functions and waits; it does not read your form, and sends nothing, until you click the toolbar icon or press the keyboard shortcut.
Where: manifest.json
— the content_scripts block;
src/content/main.js, which only registers a message
listener. Reading happens in
src/content/extract.js —
extractPage, which nothing calls until the popup or
the shortcut asks it to (ui/popup.js —
scan; src/background/service-worker.js
— saveTab).
The extension makes one outbound network request in its entire codebase, described under What happens when you share. There is no analytics, no telemetry, no error reporting, and no remote code: every file it runs ships inside the package.
Where: searching the packaged extension
for fetch(, XMLHttpRequest,
sendBeacon, WebSocket or
EventSource returns a single result —
src/lib/share.js, in createShare. The
build refuses to produce a package where that count is anything
other than one (scripts/pack.js), so this claim
cannot quietly stop being true.
If you sign up at formforlater.com/waitlist for the iPhone launch, we save your email address. That's the only thing collected there — no name, no other details, nothing about the browser or device you signed up from.
We use it for exactly one thing: telling you the day the iOS app is out. We won't use it for anything else, and we won't send you anything else. You can ask to be taken off the list at any time, and every email we send will include a way to do that.
To ask right now, email support@plaifair.ai from the address you signed up with, or use the support page. Your address is deleted outright, not marked or archived.
The waitlist form posts to a small Cloudflare Worker backed by a key-value store. The address itself is the key; the only value stored alongside it is the time you signed up. Nothing else about you is written down — not your IP address, not your browser, not where you came from.
Where:
server/site/waitlist.html — the submit
handler, which posts { email } and nothing more;
server/waitlist-worker.js — the
/join handler, which stores only the address and a
timestamp.
Two things run alongside that, purely to keep the list clean, and neither becomes part of your record: a hidden field that a real person never fills in (a honeypot — if it has anything in it, the submission is quietly ignored), and a short-lived count of recent attempts from the same IP address, used only to slow down obvious spam. That count expires on its own within the hour and is never linked to any email address.
Where:
server/waitlist-worker.js — the honeypot check
and the rl: rate-limit keys, both in the
/join handler.
Removing an address deletes it outright rather than marking it somehow — there's no record left that it was ever there.
Where:
server/waitlist-worker.js — the
/leave handler.
Two buttons in the record viewer take a record out of the extension on purpose, and they don't behave the same way.
Export file saves the whole record to your computer as a plain JSON file, ignoring your “don't share” marks — it's a full copy for you, not something built for someone else to read. Passwords and codes are still absent, because they were never stored to begin with. Your device ID is stripped out first, so an exported file can't be tied back to your browser.
Download PDF, next to Create share link, builds a document laid out for reading and filing — the record's name, the site, the date, then each page as a section with its questions and answers. It's built from the exact same choice a share link is: a field you've marked “don't share” is left out of the file entirely, the same way it's left out of a link, and passwords and codes are absent for the reason they always are. The document says on its own first page that it holds real answers and who it's safe to hand to.
Neither file is encrypted, and once one is on your disk, FormForLater isn't protecting it anymore — that's on whatever you do with it from there.
Where: ui/viewer.js —
the exportBtn handler, which removes
deviceId and serialises everything else.
The PDF is built from the same redacted object a share link is
built from — FFL.share.buildSharePayload
— so a field excluded from one is excluded from the other by
construction, not by a second check written to agree with the
first. The PDF itself has no library behind it:
src/lib/pdf.js writes the file's bytes directly,
using only the PDF format's own built-in Helvetica font, so
nothing is fetched and nothing is built. Every character is real,
selectable text; nothing is ever drawn as a box over a value,
because a black rectangle in a PDF routinely leaves the covered
text selectable underneath it.
Where: ui/viewer.js —
the pdfBtn handler; src/lib/pdf.js
— buildRecordPdf.
Removing the extension deletes everything it saved. That's just how Chrome handles extension storage, not something we built — there's no separate copy anywhere else to delete, because there never was one.
You can also delete records yourself any time: one at a time in the viewer, or all of them from Settings. Two things stick around on purpose: links you already sent (the data's inside the link itself, not on a server, so removing the extension doesn't touch it) and files you exported.
Where: src/lib/storage.js
— deleteBundle; ui/options.js
— the wipe handler.
Relay entries do expire on their own, whether or not the extension is still installed.
When something here changes because the extension changed, we update this page in the same release. The date and version at the top say which one it describes.