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.
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
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
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,msclkidcaptured 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: 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
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:
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)
Node.js
PHP
curl
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):
| Field | Purpose |
|---|---|
_slhp | Honeypot - must be present and empty; bots that fill it are marked spam |
_sl_elapsed_ms | sl.js timing signal (load-to-submit) |
_sl_page | Submitting page URL (stored as metadata) |
_sl_referrer | Page referrer (stored as metadata) |
cf-turnstile-response | Cloudflare Turnstile token, when the challenge is enabled |
SheetLink Forms - start free · privacy