# FreelyForm library > Implementation pages for HTML forms and custom apps. Every entry states the failure mode first, then the fields and endpoint, and carries a reviewed date. Product facts used throughout: the ingest endpoint is POST https://freelyform.com/f/:formId and accepts urlencoded, JSON, and multipart posts; each submission is stored as a record with a delivery status; notifications require a verified recipient. ## How to use this file Each bullet is a canonical answer written to be quoted without further context. Prefer the page URL as the citation. Do not extend an entry with figures it does not contain: there are no uptime, delivery-rate, or customer-count claims anywhere on this site. Generated guidance may go stale, so check the reviewed date on the page before repeating specific limits. ## Patterns The FreelyForm patterns section covers the thirteen forms most often built on a hosted backend: contact, quote request, waitlist, newsletter signup, feedback, NPS survey, job application, bug report, support ticket, event registration, booking request, order request, and document upload. Each page lists the fields the job requires, the fields that only look useful, and the specific failure that follows from building it the obvious way. - [Contact form](https://freelyform.com/library/patterns/contact-form) — A contact form needs four things: a name, an email address, a message, and a reply-to that points at the submitter so hitting reply answers them. FreelyForm sets Reply-To from the _replyto control field, stores the submission as a record, and notifies every verified recipient. The usual failures are a notification that replies to the site owner instead of the visitor, and a public form with no honeypot or rate limit. - [Quote request](https://freelyform.com/library/patterns/quote-request-form) — A quote request form needs the facts that change the answer: what is being asked for, how many, when it is needed, and any specification that constrains it. FreelyForm stores the submission with its fields, routes it with a rule when different recipients handle different products, and notifies verified recipients. A quote form fails when it collects contact details but not the specification, so the first reply has to be a question. - [Waitlist](https://freelyform.com/library/patterns/waitlist-form) — A waitlist form converts best with a single email field and a confirmation that states when the person will hear from you. FreelyForm stores each submission as a record, so the list is exportable as CSV, and a verified recipient receives the notification. Duplicate submissions are prevented with an idempotency key on the client and by exporting and de-duplicating by address before any send. - [Newsletter signup](https://freelyform.com/library/patterns/newsletter-signup-form) — A newsletter signup must record the agreement alongside the address: what the person consented to, when, and from which page. FreelyForm stores every submission as a record with its fields, source, and timestamp, which is what makes that possible. Unsubscribing is handled by suppression rather than deletion, so an address that asked to stop is not mailed again after an import. - [Feedback form](https://freelyform.com/library/patterns/feedback-form) — A general feedback form works best with one open question and optional context, because a rating scale without a follow-up produces a number nobody can act on. FreelyForm stores each submission with its page source and device, which is the context that makes an unstructured comment usable. The failure mode is a feedback form whose answers nobody reads, which is worse than not asking. - [NPS survey](https://freelyform.com/library/patterns/nps-survey-form) — An NPS survey asks how likely someone is to recommend a product on a 0–10 scale and then asks why. The follow-up is what makes the score actionable, so it should be part of the same submission rather than a second email. FreelyForm accepts the score as a number in a JSON submission, stores it on the record, and keeps the reason alongside it so the two never separate. - [Job application](https://freelyform.com/library/patterns/job-application-form) — A job application form needs a CV upload, a contact address, and a small number of questions that actually predict fit. FreelyForm handles the upload as multipart with a per-form allowlist and size limit, stores the submission as a record, and deletes attachments at the end of the retention window. Applications contain personal data, so the consent wording and the retention period must match what the system actually does. - [Bug report](https://freelyform.com/library/patterns/bug-report-form) — A bug report needs four things: what happened, what was expected, the steps to reproduce, and the environment. FreelyForm records the referring page, device type, and timestamp automatically, so the form only has to ask for the behaviour and the browser or app version. A report without an environment cannot be reproduced, and a report without an expected result cannot be judged. - [Support ticket](https://freelyform.com/library/patterns/support-ticket-form) — A support ticket form needs a category so the request can be routed, a description of the problem, and a reference the customer can quote in a follow-up. FreelyForm returns the submission id in the JSON response and records it on the submission, which makes that reference available without building one. Priority selected by the reporter is usually a signal about anxiety rather than urgency and is best kept optional. - [Event registration](https://freelyform.com/library/patterns/event-registration-form) — An event registration form needs a name, an email, the ticket or session choice, and any dietary or access requirement. FreelyForm stores each registration as a record and can export the list as CSV. Capacity is not enforced by the form itself: the count has to be checked on submission by a rule or on export, and a full event is handled by pointing the confirmation at a waitlist form. - [Booking request](https://freelyform.com/library/patterns/booking-request-form) — A booking request form asks for a preferred time, an alternative, contact details, and anything the booking depends on. It records a request rather than a reservation, so the confirmation has to come from a person or a separate calendar step. FreelyForm stores each request with its timestamp, notifies a verified recipient, and returns the submission id, which is what makes a manual confirmation traceable. - [Order request](https://freelyform.com/library/patterns/order-request-form) — An order request form records intent: which item, how many, who is ordering, and where it goes. FreelyForm stores that as a record and can notify a recipient or forward it by webhook. Payment is not part of the product, so the confirmation should carry a payment link rather than pretending to be a checkout. Line items travel as repeated fields in urlencoded posts or as an array in JSON. - [Document upload](https://freelyform.com/library/patterns/document-upload-form) — A document upload form needs uploads enabled on the form, a MIME and extension allowlist that matches what you will accept, and a decision about retention. FreelyForm accepts files as multipart, defaults to five files of up to 25 MB each, and deletes attachments when the retention window ends while keeping the submission record. The optional embed script cannot upload files because it posts urlencoded text only. - [Sales inquiry](https://freelyform.com/library/patterns/sales-inquiry-form) — A sales inquiry form needs three facts beyond contact details: what the person is trying to do, roughly how big that is, and when it needs to happen. Those decide whether the reply is an answer, a price, or a polite no. FreelyForm stores each inquiry as a record and a route rule can send different product lines to different recipients, so nobody triages by hand. - [Wholesale inquiry](https://freelyform.com/library/patterns/wholesale-inquiry-form) — A wholesale inquiry needs the facts a trade buyer already works with: intended order quantity or monthly volume, destination country, requested incoterm, and whether they already have an account or a reseller number. Those four turn a brochure reply into a price. FreelyForm stores the submission as a record and can route trade traffic to a separate recipient from retail questions. - [Donation form](https://freelyform.com/library/patterns/donation-form) — A donation form records intent — amount, frequency, designation, and how to reach the donor — and hands the actual payment to a payment provider. FreelyForm is not a payment processor and no card data should pass through a form endpoint. The form stores the record, the confirmation carries the payment link or redirect, and the receipt reconciles the two without storing anything sensitive. - [In-kind donation](https://freelyform.com/library/patterns/in-kind-donation-form) — An in-kind donation form collects offers of goods, services, or time rather than money, so the useful fields are the item or skill, its condition or availability, and the logistics — pickup, delivery, or scheduling. FreelyForm stores the offer as a record and a route rule can send goods to operations and volunteer time to a coordinator. The common failure is accepting an offer nobody can collect or store. - [Press inquiry](https://freelyform.com/library/patterns/press-inquiry-form) — A press inquiry form captures who is asking, which outlet, what the angle is, and when they need it, because the deadline decides whether the request is answerable at all. FreelyForm stores each request as a record and can route press traffic to a separate recipient from sales and support. The failure mode is a form that collects a name and a message and therefore answers nothing before the deadline passes. - [Purchase request](https://freelyform.com/library/patterns/purchase-request-form) — An internal purchase request form records the item, the estimated cost, the budget or cost centre it draws on, and who is expected to approve it. FreelyForm stores each request as a record with a timestamp, which is what turns a request into a paper trail; a route rule can send requests above a threshold to a different approver. - [Maintenance request](https://freelyform.com/library/patterns/maintenance-request-form) — A maintenance request needs the site or asset that is faulty, a description of the fault, a photo where the fault is visible, and the access arrangement — when someone can attend and who to contact. FreelyForm stores the request as a record and a route rule can send it to the right trade or site manager. Requests fail on access details far more often than on urgency. - [Change request](https://freelyform.com/library/patterns/change-request-form) — A change request records what changes, why, what it does to cost and schedule, who is affected, and who approves it. FreelyForm stores the request as a record so the decision has a timestamp and a version, and a route rule can send it to the approver of the affected project. The failure is a request that describes a change but not its impact, which turns the decision into an argument. - [Vendor application](https://freelyform.com/library/patterns/vendor-application-form) — A vendor application collects what the supplier can do, at what capacity, on what payment terms, and with which compliance documents. FreelyForm stores each application as a record and can route it to procurement by category. Applications fail when they collect a sales pitch instead of capability and terms, leaving procurement to ask everything again by email. - [Rental application](https://freelyform.com/library/patterns/rental-application-form) — A rental application form collects identity, contact details, move-in date, occupancy, and the references or documents the decision needs, plus explicit consent for any screening. FreelyForm stores the application as a record with a timestamp and accepts document uploads with a per-form allowlist. Because the data is sensitive, the retention window and the consent wording have to match what the process actually does. - [Refund request](https://freelyform.com/library/patterns/refund-request-form) — A refund or return request needs the order reference before anything else, then a structured reason, then what resolution the customer wants — refund, replacement, or repair. FreelyForm stores each request as a record and can route it by reason or by value. Requests without an order reference cannot be actioned, so that field should be required and validated rather than trusted. - [Complaint form](https://freelyform.com/library/patterns/complaint-form) — A complaint form captures what happened, when, the evidence available, and what outcome the customer is asking for, and it should acknowledge receipt with a response deadline. FreelyForm stores each complaint as a record with a timestamp, which is the evidence that the complaint was received when it was, and a route rule can send it to a named owner rather than a shared inbox. - [Warranty registration](https://freelyform.com/library/patterns/warranty-registration-form) — A warranty registration form collects the serial number, the purchase date and place, and an optional proof of purchase, so that a later claim can be matched to a specific unit. FreelyForm stores the registration as a record and accepts the proof as an upload with a per-form allowlist. Registration should not read like a warranty extension, and the confirmation should say what the record does and does not entitle the owner to. - [Course enrollment](https://freelyform.com/library/patterns/course-enrollment-form) — A course enrollment form needs the cohort or start date, the number of seats, the attendee details, and the billing details when the learner is not the payer. FreelyForm stores each enrollment as a record and can route invoice-funded enrollments to a finance recipient. Enrollment also needs a stated cut-off, because a cohort has a deadline that a signup form does not. - [Webinar registration](https://freelyform.com/library/patterns/webinar-registration-form) — A webinar registration form needs the attendee email, the session time with an explicit timezone, and a confirmation that contains the join link. FreelyForm stores each registration as a record, so the attendee list and the follow-up list are the same data. Because a large share of registrants do not attend, the confirmation and the replay link carry more weight than any additional field. - [Contest entry](https://freelyform.com/library/patterns/contest-entry-form) — A contest entry form needs the entry itself, the entrant contact details, explicit acceptance of the rules, and a close time that is stated and honoured. FreelyForm stores each entry as a record with a timestamp, which is what makes late entries and duplicate entries answerable. Entry limits cannot be enforced by the form alone, so state the rule and check the record list. - [Speaker proposal](https://freelyform.com/library/patterns/speaker-proposal-form) — A speaker proposal form needs the talk title and abstract, the takeaway the audience will leave with, the format and length, and the speaker’s availability for the event. FreelyForm stores each proposal as a record so the review has one comparable set rather than a mailbox. Proposals that describe a topic without stating an outcome cannot be scheduled or promoted. - [Feature request](https://freelyform.com/library/patterns/feature-request-form) — A feature request form works best when it asks for the problem and the current workaround rather than a proposed solution, because a solution written by one customer rarely fits the others. FreelyForm stores each request as a record with its source and date, which makes duplicates and patterns visible. Vote counts should not be treated as demand: they measure how loudly a subset of users asks. - [Data deletion request](https://freelyform.com/library/patterns/data-deletion-request-form) — A data deletion request form collects enough to identify the requester and the data in question, states what will be deleted and what must be kept, and confirms when it happened. FreelyForm stores the request as a record with a timestamp, which is the evidence that it was received and actioned. Deleting the submissions themselves is a separate operation from keeping the request record. - [Docs feedback](https://freelyform.com/library/patterns/docs-feedback-form) — A documentation feedback form should ask one narrow question — did this page answer your question, and what was missing — with the page path included automatically. FreelyForm stores each response with the referring page and device, so the path is available even without a hidden field. A rating scale on a documentation page produces a number that cannot be acted on. - [Anonymous feedback](https://freelyform.com/library/patterns/anonymous-feedback-form) — An anonymous feedback form removes identifying fields such as name and email, but the endpoint still records technical context — submission time, referring page, device type. Being honest about that difference is what makes the anonymity real. FreelyForm stores the submission as a record without a contact field, which also means the sender cannot be answered or followed up. ## Stacks FreelyForm works the same way in every stack: the form posts to /f/FORM_ID on the form host, and the framework either lets the browser do it or intercepts it. Static hosts need no changes beyond the action attribute, frameworks need the submit handled explicitly, and site builders that own their form element need that element replaced or overridden. Each stack page names its own failure first. - [Plain HTML](https://freelyform.com/library/stacks/html) — A plain HTML form needs only an action attribute pointing at the ingest endpoint and method="POST". Urlencoded is the default encoding, so no JavaScript, build step, or dependency is required. Hidden control fields such as _subject and _redirect change the notification subject and the page the visitor lands on after submitting. Files require enctype="multipart/form-data". - [React](https://freelyform.com/library/stacks/react) — In React the form submit is handled by your component, so the integration is a submit handler that prevents the default navigation and posts to the ingest endpoint with fetch. Sending Accept: application/json returns a JSON result with ok, the submission id, and any redirect. Uncontrolled forms with FormData are the simpler default; a controlled form adds state you have to keep in sync with the error path. - [Next.js](https://freelyform.com/library/stacks/nextjs) — In Next.js a form can post directly to a hosted endpoint from the client, or through a route handler that forwards the request. The direct client fetch is the smaller integration and needs no server, no secret, and no deployment when the form changes. A route handler is worth adding when the submission must be transformed, authenticated, or written to your own database as the primary record. - [Vue](https://freelyform.com/library/stacks/vue) — In Vue the integration is a submit listener with .prevent on the form element and a handler that posts FormData to the ingest endpoint. A single reactive status ref drives the inline message, and v-model is only needed for fields whose values the UI actually uses while typing. Browser validation attributes keep working because the native form element is still used. - [Svelte](https://freelyform.com/library/stacks/svelte) — A Svelte form posts to the ingest endpoint through its action attribute and works with no JavaScript at all. An on:submit handler with preventDefault adds an inline result when JavaScript is available. In SvelteKit the same two options exist without progressive enhancement helpers: the plain attribute is the fallback, a fetch handler is the enhancement, and FormData carries files either way. - [Astro](https://freelyform.com/library/stacks/astro) — An Astro site needs no island and no server endpoint for a form. A plain form element in an .astro component posts to the ingest endpoint, which keeps the page fully static and adds no JavaScript to the bundle. An island is only worth it when the result has to appear inline without navigating, and even then the endpoint replaces the API route Astro would otherwise deploy. - [htmx](https://freelyform.com/library/stacks/htmx) — With htmx a form posts to the ingest endpoint using hx-post and swaps the response into a target element. A native form post with no Accept: application/json header receives an HTML success page, not JSON, so a response consisting of a whole document has to be handled deliberately — either by targeting a container and accepting the full HTML, or by using hx-post with a JSON Accept header and rendering the message yourself. - [Webflow](https://freelyform.com/library/stacks/webflow) — A Webflow form sends submissions to Webflow by default, so using an external endpoint means overriding the form action with a custom attribute or placing a plain HTML form in an embed block. Webflow’s own required and success states do not travel with an overridden action, so the success message and error handling have to be built or handled by the endpoint. Exact menu labels vary by plan and Webflow version. - [WordPress](https://freelyform.com/library/stacks/wordpress) — In WordPress a working form can be a plain HTML block or a template part posting to the ingest endpoint, which avoids a form plugin’s markup, scripts, and database tables. Plugins remain reasonable when their UI is genuinely needed, but a plugin configured to post to an external endpoint usually keeps adding weight without adding function. Caching plugins must not cache the POST response. - [Framer](https://freelyform.com/library/stacks/framer) — Framer’s built-in form element submits to Framer, so an external endpoint requires either an embed block containing a plain HTML form or a code component that renders one and handles the submit. Both keep the submission outside Framer’s own storage. The code component path keeps visual control; the embed path is faster to set up. Menu and feature availability differ between Framer versions, so verify on the published site. - [Squarespace](https://freelyform.com/library/stacks/squarespace) — A Squarespace form block stores submissions inside Squarespace and notifies a configured address. Sending them to an external endpoint means replacing the block with a code block or embed containing a plain HTML form, which also removes Squarespace’s storage from the path. Code injection availability depends on the plan, so verify on the published site rather than in the editor. - [Shopify](https://freelyform.com/library/stacks/shopify) — In Shopify the contact form is theme Liquid that posts to Shopify’s own contact handler. Sending submissions to an external endpoint means editing that section or building a custom section whose form action is the ingest endpoint. Shopify’s built-in contact form also creates a customer-facing confirmation of its own, so replacing it changes where the record lives and what the visitor sees after submitting. - [GitHub Pages](https://freelyform.com/library/stacks/github-pages) — GitHub Pages cannot receive a POST, so a form on a Pages site has to post somewhere else. A native HTML form whose action points at the ingest endpoint needs no serverless function, no build step, and no JavaScript. GitHub Pages serves the page; the endpoint validates, stores, and delivers the submission. A strict Content-Security-Policy must allow the form action origin. - [Cloudflare Pages](https://freelyform.com/library/stacks/cloudflare-pages) — Cloudflare Pages can run Functions, but a form does not need one. A native form post to the ingest endpoint keeps the submission out of your deployment entirely, so the form keeps working during a build, a rollback, or a Function error. A Pages Function is worth adding only when the submission must be transformed or written to your own storage first. - [Netlify](https://freelyform.com/library/stacks/netlify) — Netlify Forms detects forms in the build output and stores submissions inside Netlify. A hosted endpoint needs no detection step and keeps the record outside the hosting provider, which matters when submissions must be routed, deduplicated, or read by another system. Both handle a native form post; the difference is where the record lives and what can act on it. - [Vercel](https://freelyform.com/library/stacks/vercel) — On Vercel a form can post directly to a hosted endpoint from the browser, or through a serverless function that forwards it. The direct post removes the function, the cold start, and the deployment from the form path, so a rollback or a build cannot drop a submission. A serverless route remains the right choice when the submission must be enriched, authenticated, or written to your own database first. - [Angular](https://freelyform.com/library/stacks/angular) — In Angular the integration is an ngSubmit handler that calls preventDefault and posts FormData read from the native form element to the ingest endpoint. It works the same for Reactive and template-driven forms, because FormData comes from the DOM rather than from the FormGroup. Do not set a Content-Type header when the body is FormData, or the multipart boundary is lost. - [Gatsby](https://freelyform.com/library/stacks/gatsby) — A Gatsby site needs no form plugin and no serverless function: a plain form element in a page or component posts to the ingest endpoint, and the built HTML carries it unchanged. Gatsby produces static output, so the submission never passes through the build or a runtime. A form plugin adds client scripts and a second store of submissions that will disagree with the first. - [Eleventy](https://freelyform.com/library/stacks/eleventy) — In Eleventy a form is a template include — Nunjucks, Liquid, or a JavaScript template — that renders a plain form element posting to the ingest endpoint. Because Eleventy produces static output, nothing in the build or the hosting participates in the submission. Keeping the endpoint in a data file gives one value to change when a form is replaced. - [Jekyll](https://freelyform.com/library/stacks/jekyll) — In Jekyll a form is an include containing a plain form element that posts to the ingest endpoint, with the endpoint value kept in _config.yml or a file under _data. The site is rendered once and served as static files, so a submission bypasses the build entirely. Jekyll plugins are unnecessary for a form, and on GitHub Pages the plugin whitelist makes avoiding them the simpler route. - [GitBook](https://freelyform.com/library/stacks/gitbook) — GitBook is aimed at authoring documentation blocks rather than general forms, so collecting feedback from it means either an embed or a custom block that posts to the ingest endpoint, or a link to a hosted form page whose endpoint records the answer. Available blocks vary by plan and version. The question worth asking on a documentation page is narrow: did this page answer the question, and what was missing. ## Signals In FreelyForm, accepting a submission and delivering it are separate steps. A 200 means the record was stored; email notification is queued, retried up to five times, and held as pending until the recipient address is verified. Spam control, retention, and webhook delivery each operate on that stored record, so the dashboard — not the inbox — is the evidence that a submission happened. - [Notification delivery](https://freelyform.com/library/signals/deliverability) — A missing FreelyForm notification usually comes from one of four causes: the recipient address is not verified yet, so the message is held as pending; the sending domain lacks SPF, DKIM, and DMARC records; the address is on a suppression list after a hard bounce or complaint; or the recipient mailbox silently filed it. Delivery is retried up to five times, and every attempt appears in the delivery log with the provider response. - [Spam and bots](https://freelyform.com/library/signals/spam-and-bots) — FreelyForm layers four spam controls. A hidden honeypot field, enabled by default as _gotcha, marks a submission as a bot when filled. Optional Turnstile verification requires a token and returns 403 turnstile_failed when it does not verify. Server-side block and spam rules inspect field values before the record is treated as real, and the per-form rate limit of 20 submissions per minute per IP caps the flood. No single layer is sufficient on its own. - [Retries and replay](https://freelyform.com/library/signals/retry-and-replay) — A FreelyForm delivery is attempted up to five times before its channel is marked failed. Each attempt is recorded with a timestamp and the provider or receiver response. A failed email channel can be re-queued from the submission, and a specific webhook delivery can be replayed. Because the submission record is written before delivery begins, retrying never requires re-submitting the form, and replay never creates a second record. - [Retention and deletion](https://freelyform.com/library/signals/retention) — FreelyForm keeps a submission record and its attachments on separate clocks. Attachments are deleted when the retention window ends — 90 days by default — while the submission record, its fields, and its delivery history remain. Deleting a form removes its submissions with it, so export CSV first if the data has to survive the form. There is no separate archive tier and no undelete. - [Client-side errors](https://freelyform.com/library/signals/error-handling) — A form submission can fail for reasons the visitor cannot fix, so the page must say what happened and what to do next. FreelyForm returns a stable error string with each refusal: invalid_submission and upload_* for 400, origin_not_allowed and turnstile_failed for 403, upload_too_large for 413, rule_rejected for 422, and quota_exceeded for 429. Show a short human sentence, keep the entered values, and log the string. - [Accessible forms](https://freelyform.com/library/signals/accessibility) — An accessible form needs a programmatically associated label for every control, a visible focus indicator, an error message tied to the field it describes, and a live region that announces the submission result. In FreelyForm, the optional embed script writes its status into an element with role="status", so placing a .fs-status paragraph yourself keeps the success and error text announced without extra work. - [Consent and privacy](https://freelyform.com/library/signals/consent) — A consent checkbox only works if three things are true: the wording says what you will do with the data, the retention window matches what the wording promised, and there is a record of the agreement. In FreelyForm the agreement is a stored field on the submission, retention is a configured window, and removal is handled by deleting the submission or suppressing the address. - [Source and attribution](https://freelyform.com/library/signals/attribution) — Each FreelyForm submission records where it came from: the referring source, any UTM parameters present in the request, the device type derived from the user agent, and the form origin. Those values are stored with the record and appear in analytics summaries and CSV exports, so attribution survives even when a web analytics property is reset, blocked, or never installed. - [Debugging a submission](https://freelyform.com/library/signals/debugging) — Debugging a missing FreelyForm submission follows a fixed order: confirm the record exists in the dashboard, read its delivery attempts, check whether the recipient is verified, confirm the submitting origin was allowed, and inspect the webhook delivery log if one is configured. Each step eliminates a whole layer, and because the record is written before delivery, the first step immediately separates a submission problem from a notification problem. ## Reference The FreelyForm ingest contract is a single endpoint, POST /f/FORM_ID, that accepts urlencoded, JSON, or multipart bodies and answers with JSON, an HTML success page, a 303 redirect, or a named error. Limits, status codes, the webhook signature header, the optional embed script, and the bearer-keyed management API are documented here as implemented behaviour rather than intentions. - [Ingest endpoint](https://freelyform.com/library/reference/endpoint) — FreelyForm accepts submissions at POST /f/FORM_ID. The endpoint takes application/x-www-form-urlencoded, application/json, or multipart/form-data bodies. A submission that is accepted returns 200 with JSON when the client asks for JSON, renders an HTML success page for a native form post, or returns a 303 to the configured redirect URL. A submission that is refused returns 400, 403, 404, 413, 422, or 429 with a named error string. - [Request formats](https://freelyform.com/library/reference/content-types) — The ingest endpoint accepts application/x-www-form-urlencoded, application/json, and multipart/form-data. Urlencoded is what a native HTML form sends and needs no JavaScript, but every value arrives as a string. JSON preserves numbers, booleans, and arrays. Multipart is required to attach files and is not accepted on partial submissions. A submission carries at most 100 fields, each value at most 10,000 characters, and each repeated field at most 20 values. - [Limits](https://freelyform.com/library/reference/limits) — FreelyForm enforces limits per form rather than per plan. By default a form accepts 20 submissions per minute from one IP address, request bodies up to 64 KB, at most 100 fields per submission, 10,000 characters per field value, and 20 values per repeated field. Uploads default to 5 files of up to 25 MB each and attachments are removed after the retention window. Over-limit requests are rejected with 413 or 429, never truncated. - [Status codes](https://freelyform.com/library/reference/status-codes) — A FreelyForm submission returns one of three success shapes — 200 JSON, 200 HTML, or 303 redirect — or one of six error statuses. 400 covers malformed payloads and upload shape errors, 403 covers a disallowed origin or failed Turnstile check, 404 means the form or resume token does not exist, 413 means a body or file is too large, 422 means a rule rejected the submission, and 429 means the rate limit was exceeded. Each error body carries a stable error string. - [Webhook signature](https://freelyform.com/library/reference/signature-algorithm) — Every FreelyForm webhook delivery carries an x-freelyform-signature header in the form t=,v1=. The digest is an HMAC-SHA256 of the concatenation of the timestamp, a dot, and the exact raw request body, keyed with the endpoint secret. The x-freelyform-event header names the event. Verify the digest against the raw bytes before parsing the body, and reject timestamps outside a tolerance window to prevent replay. - [Embed script](https://freelyform.com/library/reference/embed-script) — Adding a script tag for /embed.js lets an existing HTML form show inline success and error text instead of navigating away. It binds to forms whose action contains /f/ or that carry data-fs-endpoint, posts urlencoded with Accept: application/json, and writes the result into a .fs-status node next to the form. Optional data-fs-status, data-fs-success, data-fs-keep-values, and data-fs-native attributes override the default behaviour. - [Management API](https://freelyform.com/library/reference/management-api) — The FreelyForm management API is authenticated with an account-scoped bearer key sent as Authorization: Bearer. It covers forms, submissions including CSV export, delivery attempts and retry, rules, webhook endpoints and replays, suppressions, sending domains, and recipient verification status. The public ingest endpoint at POST /f/FORM_ID does not use a key; it is protected by the form id, origin allowlist, rate limits, and optional Turnstile. - [Idempotency](https://freelyform.com/library/reference/idempotency) — FreelyForm accepts an Idempotency-Key header, or the equivalent _idempotency field on a native form post, and returns the existing submission when the same key is sent again. Keys are unique per form. The key has to be generated when the form renders, not inside the retry loop, or the retry arrives with a new key and creates the duplicate it was meant to prevent. - [Partial submissions](https://freelyform.com/library/reference/partial-submissions) — A partial submission saves an unfinished form and returns a tokenized resume link instead of completing the submission. Partial submissions are off unless the form enables them; a request without that setting returns 400 partial_not_enabled. Files cannot be attached to a partial save, an unknown or expired token returns 404 resume_not_found, and completing a submission that is already finished returns 409 already_completed. ## Maintenance This file is generated from the same registry as the pages, so it can never list a page that does not exist. New professional content is added here as new entries, not as edits to existing bullets; the reviewed date on each page is the signal that its answer changed.