SheetLink Forms

Developer docs

One endpoint per form. Five ways in: an HTML form action, JSON, the sl.js embed, platform webhooks, or email. Everything lands in your connected Google Sheet.

Quick start: plain HTML

Point any form at your endpoint. No JavaScript required.

<form action="https://sheetlinkforms.com/f/YOUR_TOKEN" method="POST"> <input type="text" name="name" placeholder="Name"> <input type="email" name="email" placeholder="Email"> <textarea name="message"></textarea> <input name="_slhp" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px"> <button type="submit">Send</button> </form>

The browser is 303-redirected to your form's redirect URL (or a hosted thank-you page). Field names that match your sheet's column headers map automatically - "Email", "email", and "E-mail" all match an "Email" column. The hidden _slhp input is the spam honeypot: keep it in, leave it empty.

JSON API

POST https://sheetlinkforms.com/f/YOUR_TOKEN Content-Type: application/json {"name": "Ada", "email": "ada@example.com", "message": "Hello"} -> 200 {"ok": true, "id": "..."}

Requests with Content-Type: application/json or Accept: application/json get a JSON response instead of a redirect. Multipart is accepted too (file parts are dropped in the current release). Bodies are capped at 256 KB. Errors: 404 unknown token, 410 paused form, 403 origin not allowed or challenge failed, 413 too large, 429 rate limited (per-form and per-IP), 400 unreadable body. Delivery to the sheet is asynchronous with automatic retries (5 min, 30 min, 2 h).

sl.js embed

<script src="https://sheetlinkforms.com/sl.js" data-token="YOUR_TOKEN"></script>

Add once per page. It binds any <form data-sheetlink> and upgrades any form whose action already points at your endpoint:

  • AJAX submit with inline success/error text - no page reload
  • Ad attribution: utm_*, gclid, wbraid, gbraid, fbclid, msclkid captured from the URL and persisted across pages - add matching columns to receive them
  • Page URL, referrer, and a bot-timing signal (stored as submission metadata)
  • Honeypot injected automatically
  • SPA-safe: late-rendered forms are bound via MutationObserver

Form attributes: data-sheetlink="slf_..." (bind + per-form token), data-redirect (navigate on success), data-success-message (inline text). Events: sheetlink:success and sheetlink:error bubble from the form with details in event.detail.

Webflow & Framer webhooks

Webflow: https://sheetlinkforms.com/w/webflow/YOUR_TOKEN Framer: https://sheetlinkforms.com/w/framer/YOUR_TOKEN

Webflow: Site settings -> Integrations -> Webhooks -> Add webhook, trigger type "Form submission", paste the URL - one webhook covers every form on the site, and the originating form name is stored with each submission. Framer: set the Form component's submission destination to Webhook and paste the URL. Both v2 and legacy Webflow payloads are handled.

Email-in

YOUR_TOKEN@sheetlinkforms.com

The catch-all: point any system's notification email at your form's address and lines shaped Label: value become fields; when nothing parses, the subject, sender, and body are stored so the lead is never lost.

For IT admins: Microsoft 365 approval

If your organization requires admin approval for new apps, an Entra admin can grant SheetLink org-wide consent in one step - after that, users connect their Microsoft account without an approval prompt:

https://sheetlinkforms.com/api/hosted/oauth/microsoft/admin-consent

What you are approving: app "SheetLink", application (client) ID 27b7f757-de78-4806-a92a-c4e50572a825. Delegated permissions only - Files.ReadWrite (write rows to the workbook the user picks), User.Read (show which account is connected), offline_access (keep delivering while the user is offline). No application-level (app-only) permissions, no admin data access. All SheetLink traffic to Microsoft originates from the static egress IP 69.197.187.14 if you allowlist outbound integrations. Refresh tokens are stored AES-256-GCM encrypted and are revocable at any time from your tenant's Enterprise applications blade or the SheetLink dashboard.

Two things worth knowing before you scale

Excel needs a work or school Microsoft account. Microsoft's Excel API does not support workbooks on personal (consumer) OneDrive - a Microsoft platform limit, not a SheetLink one. Connect a Microsoft 365 business account (OneDrive for Business or SharePoint) and Excel delivery works; a personal account will be flagged as unsupported at connect time rather than failing later. Google Sheets delivery works with any Google account.

At high volume, delivery pace is governed by your own provider quota. You connect with your own Google or Microsoft account, so appends run inside your account's API limits - which is why one customer's traffic can never slow another's. If you push very large bursts, Google or Microsoft may briefly throttle your connection; submissions are never dropped - they queue and deliver as your quota allows, and you can watch them drain on the submissions page.

Code snippets

fetch (browser or edge)

await fetch("https://sheetlinkforms.com/f/YOUR_TOKEN", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, email, message }) });

Node.js

const res = await fetch("https://sheetlinkforms.com/f/YOUR_TOKEN", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Ada", email: "ada@example.com" }) }); const { ok, id } = await res.json();

PHP

$ch = curl_init("https://sheetlinkforms.com/f/YOUR_TOKEN"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode(["name" => "Ada", "email" => "ada@example.com"]), CURLOPT_RETURNTRANSFER => true, ]); $response = curl_exec($ch); // {"ok":true,"id":"..."} // On WordPress? Skip the endpoint entirely - the SheetLink Forms plugin // captures your existing forms without code: sheetlinkwp.com

curl

curl -d "name=Ada" -d "email=ada@example.com" https://sheetlinkforms.com/f/YOUR_TOKEN

Spam handling & control fields

Every submission passes rate limits, an origin allowlist (when configured), the honeypot, and content heuristics tuned by your form's strictness dial. Suspicious submissions are quarantined for review in the dashboard, never silently dropped - approving one delivers it. That includes an empty or unrecognizable payload: it still answers {"ok":true} and is held rather than rejected, so if your integration gets success responses but no rows are arriving, check the quarantine first - an empty-body bug looks exactly like that. Reserved field names (stripped from your sheet, used by the pipeline):

FieldPurpose
_slhpHoneypot - must be present and empty; bots that fill it are marked spam
_sl_elapsed_mssl.js timing signal (load-to-submit)
_sl_pageSubmitting page URL (stored as metadata)
_sl_referrerPage referrer (stored as metadata)
cf-turnstile-responseCloudflare Turnstile token, when the challenge is enabled

SheetLink Forms - start free · privacy