React forms to Excel
A fetch in your submit handler or one script tag that binds your forms automatically - either way, submissions land as rows in an Excel table on OneDrive.
React gives you complete control over form state and none over where submissions go - and if where they should go is an Excel workbook, the ecosystem mostly shrugs and points you at Zapier or Power Automate, both of which meter every submission as a billable task. SheetLink Forms takes a different shape - it is the only hosted form backend with a native Excel Online destination: a permanent endpoint at https://sheetlinkforms.com/f/{token} that accepts JSON, urlencoded, or multipart POSTs and inserts each accepted submission as a row in an Excel table on your OneDrive, via Microsoft Graph. These are live rows in the real workbook - open it in Excel on the web or the desktop app and they are already there. Google Sheets is equally supported if part of your team lives there instead.
Because this is a browser-first product, the parts that are usually annoying in a SPA are handled: the endpoint supports cross-origin fetch, the sl.js embed binds forms even when they mount after initial load (it watches the DOM with a MutationObserver), and ad attribution - utm_* plus gclid, wbraid, gbraid, fbclid, and msclkid - is captured from the URL and persisted in localStorage across client-side navigations.
SheetLink Forms is free during beta with an invite - join the waitlist - and you can poke a working form on the live demo.
The best path for React
Two paths work well in React. The least-code path is the sl.js embed: one script tag with your token, then mark any form with data-sheetlink. The script AJAX-submits it, shows inline success text, auto-injects the honeypot, adds a timing signal for spam screening, and captures ad attribution - and because it uses a MutationObserver, forms React renders after page load are bound too.
The full-control path is a fetch in your own submit handler, as in the snippet below. One detail matters: send an Accept: application/json header (or post a JSON body with Content-Type: application/json) so the endpoint answers {"ok":true,"id":"..."} instead of the 303 redirect intended for classic HTML forms. CORS is not a fight here - the endpoint is built for cross-origin browser posts, with an optional per-form origin allowlist when you want to pin submissions to your domains.
1. Connect Excel Online and get your endpoint
Connect a Microsoft account in the dashboard via OAuth - Microsoft 365 or a personal Microsoft account with OneDrive - and pick the workbook and table your rows should land in. The table's columns define the row shape, and SheetLink Forms syncs column names from the table, so the table needs to exist with its columns first (no header seeding on this destination, unlike Google Sheets). Your form gets a permanent endpoint at https://sheetlinkforms.com/f/{token}.
2. Easiest path: drop in sl.js
Add <script src="https://sheetlinkforms.com/sl.js" data-token="slf_yourtoken"></script> to your HTML shell (for example index.html in a Vite app) and put data-sheetlink on your form element. The script binds it - including forms that mount later, via MutationObserver - AJAX-submits, swaps in inline success text, injects the honeypot, and records the timing signal. It emits sheetlink:success and sheetlink:error events if you want to drive your own UI from them.
3. Full-control path: write the fetch yourself
Use the snippet above. Building the body with new FormData(e.currentTarget) keeps your inputs uncontrolled and simple; a JSON body with Content-Type: application/json works just as well. Either way, keep the request under the 256KB body cap and read result.ok for your UI state.
4. Keep the honeypot input in your JSX
When you handle the fetch yourself, render the _slhp input exactly as in the snippet - tabIndex={-1}, autoComplete="off", positioned off-screen with a style object. It is the one signal that marks spam outright; everything else suspicious (rate limits, content heuristics, timing) goes to a quarantine for one-click review and is never silently dropped. The docs cover the full screening stack, including optional Cloudflare Turnstile.
5. Name inputs after your table's columns
Field names that match the Excel table's column names map automatically, and matching is case and punctuation insensitive - Email, email, and E-mail all land in the same column. When a field and a column genuinely differ, set an explicit per-field mapping in the dashboard. Add a column to the table in Excel and it syncs back, so the row shape stays under your control, in the workbook.
6. Test and confirm in the delivery log
Submit a test from your dev server, then open the workbook - same OneDrive file, so the row is already visible in Excel on the web and the desktop app. The delivery log records every attempt, and the asynchronous worker retries at 5 minutes, 30 minutes, and 2 hours if Microsoft Graph hiccups. The whole pipeline is diagrammed on how it works.
FAQ
Should I use sl.js or my own fetch?
Use sl.js when you want the shortest path: binding, inline success, honeypot injection, timing signal, and ad attribution all come free, and its MutationObserver means React-rendered forms are picked up whenever they mount. Write your own fetch when you want submission state inside your component - loading spinners, optimistic UI, custom validation. Both hit the same endpoint and land the same rows.
Will a browser fetch hit CORS errors?
No - the endpoint is built for cross-origin browser posts. If you enable the per-form origin allowlist, posts from origins not on the list are refused with a 403, which is a feature: it stops other sites from spraying your form. Leave the allowlist empty and any origin may post.
How do my input names line up with the Excel table's columns?
Column names are read from the table you picked - the table is the source of truth, which is the reverse of the Google Sheets destination, where headers can be seeded for you. Matching against your input names is name-based and forgiving about case and punctuation, and the dashboard offers explicit per-field mapping when a form field and a column need to be paired by hand.
Can Power BI chart these submissions?
Yes, and it is one of the best reasons to pick the Excel destination: the rows live in a real workbook on OneDrive, so Power BI connects to it like any other workbook and your submission data feeds live dashboards - signups per day, campaign breakdowns, whatever you build - with no export step in between.
Does ad attribution survive client-side routing?
Yes. sl.js captures utm_source/medium/campaign/term/content and the gclid, wbraid, gbraid, fbclid, and msclkid click IDs when the visitor first lands, and persists them in localStorage - so the values are still attached when the form is submitted several routes later.
What does it cost?
Free during beta, invite required - the waitlist is on the homepage. Planned post-beta pricing (planned, not live): a free tier around 50 submissions/month and a paid plan around $15-19/mo, unmetered. No per-submission fees, ever - which is precisely the fee structure Zapier and Power Automate would attach to the same Excel workflow.
Also on: React · Next.js · Plain HTML · Framer