Request forms · 5 fields
Return (RMA) request form template
Collects what a returns desk needs to approve or refuse a return: who is asking, which order, and why. The reason list makes returns countable, so you can see which products come back and for what.
RMA request
Live demo: try the fields. Submitting sends nothing.
02 / Field guide
Why these fields
Name
text · requiredRequired to match the request to the customer record.
Email
email · requiredRequired. It is where the return label or refusal goes, and usually matches the order email.
Order number
text · requiredRequired. If your order numbers have a fixed format, add a pattern attribute such as pattern="[0-9]{4,10}" so typos are caught in the browser before the request reaches you.
Reason for return
select · optionalFixed choices make it possible to count returns by cause. Match them to how your returns policy treats each case, for example who pays for shipping.
Details
textarea · optionalOptional free text for anything the reason list does not cover. Defective items usually need it, so mention that in the help text.
When to use it
Use it for shops that handle returns by hand or through a helpdesk, including Shopify stores that want a returns page outside checkout. The Shopify code tab generates a section you can add to a theme.
Photos of a damaged item help, but uploads need Professional. On lower plans, ask customers to reply to your confirmation email with photos instead.
Privacy and GDPR
- Do not ask for an address or bank details here; you already have the address on the order, and refunds should go back through the original payment method.
- Returns data is only needed until the return is closed and any warranty or bookkeeping period has passed. Set a retention period that matches.
03 / Make it yours
Choose your stack. Copy your form.
Replace the example endpoint with your Formward endpoint, then adapt the fields to your needs.
<form action="https://forms.formward.eu/f/your-form-id" method="POST">
<label for="name">Name</label>
<input type="text" name="name" id="name" required />
<label for="email">Email</label>
<input type="email" name="email" id="email" required />
<label for="order_number">Order number</label>
<input type="text" name="order_number" id="order_number" required />
<label for="reason">Reason for return</label>
<select name="reason" id="reason">
<option value="Defective">Defective</option>
<option value="Wrong item">Wrong item</option>
<option value="No longer needed">No longer needed</option>
<option value="Other">Other</option>
</select>
<label for="details">Details</label>
<textarea name="details" id="details"></textarea>
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px" aria-hidden="true" />
<button type="submit">Send</button>
</form>Accessibility
Accessible by default, and how to keep it that way
- Every field in the code has a visible label tied to it with for and id, so screen readers announce what to enter and a click on the label focuses the field.
- Required fields carry the required attribute, so the browser explains a missing answer before anything is sent. Say “required” in words as well; an asterisk or a colour alone is easy to miss.
- The spam trap field (_gotcha) is hidden from screen readers with aria-hidden and skipped by the Tab key, so people never meet it.
- In the JavaScript versions, show the confirmation or error in an element with role="status" so the result is announced, and keep the entered answers when sending fails.
Changed the fields? Paste your version into the HTML form tester to see exactly what it sends, or run it through the GDPR form checker.
04 / Implementation
How the code talks to Formward
Each form you create gets its own endpoint in the form https://forms.formward.eu/f/your-form-id. The HTML version posts to it directly and the browser is redirected to a thank-you page, so it works without JavaScript.
The Next.js version below sends the same fields with fetch and an Accept: application/json header. Formward then answers with { ok: true, id }, or an error body with 402 when the plan limit is reached and 429 when the rate limit kicks in. See responses and status codes.
Next.js client component
"use client";
import { useState } from "react";
export function RmaForm() {
const [sent, setSent] = useState(false);
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const form = e.currentTarget;
const res = await fetch("https://forms.formward.eu/f/your-form-id", {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
if (res.ok) {
setSent(true);
form.reset();
}
}
if (sent) return <p>Thanks! Your message has been sent.</p>;
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<input type="text" name="name" id="name" required />
<label htmlFor="email">Email</label>
<input type="email" name="email" id="email" required />
<label htmlFor="order_number">Order number</label>
<input type="text" name="order_number" id="order_number" required />
<label htmlFor="reason">Reason for return</label>
<select name="reason" id="reason">
<option value="Defective">Defective</option>
<option value="Wrong item">Wrong item</option>
<option value="No longer needed">No longer needed</option>
<option value="Other">Other</option>
</select>
<label htmlFor="details">Details</label>
<textarea name="details" id="details" />
<input
type="text"
name="_gotcha"
tabIndex={-1}
autoComplete="off"
style={{ position: "absolute", left: "-9999px" }}
aria-hidden="true"
/>
<button type="submit">Send</button>
</form>
);
}
Spam protection
- The generated code already includes the hidden _gotcha honeypot. Leave it in: a bot that fills it gets a normal success response, the submission is filed as spam and no email is sent.
- Every form has a per-IP rate limit, and you can restrict it to your own domains with the allowed-origins list in the form settings.
- Cloudflare Turnstile is optional and works on every plan, including Free. Turn it on in the form settings, add the widget to your markup and Formward checks the token server-side.
- The form settings can also reject disposable email addresses, which is worth turning on when a fake address would waste your time.
What happens after submit
- Each request is emailed to your returns inbox and stored in the EU on every plan. Webhooks from Professional can open a ticket in your helpdesk automatically.
- Confirm receipt to the customer with the autoresponder (Professional), and tell them how long a decision usually takes.
Questions
- Can customers attach photos of a damaged item?
- Yes, with a file field and the Professional plan or above. Uploads are stored in the EU with the request and deleted with it.
- Does this replace a returns app?
- For a small shop, often yes: the form collects the request, and you approve and send the label yourself. It does not generate labels or refunds.
Related
- Shopify forms
Add a non-checkout form to a Shopify theme.
- Request templates
Refund, support, maintenance and shipping requests.
- Refund request template
When there is nothing to send back.
- Forms with file uploads
Accept photos as evidence on paid plans.
Customize before publishing
- Remove fields you do not need and mark only essential answers as required.
- Replace placeholder wording with terms your audience already understands.
- Add your privacy notice, retention details and expected response time.
- Test keyboard focus, validation errors and the confirmation state on mobile.
Use this template
Create a free account and the form is set up from this template. Copy your endpoint and replace https://forms.formward.eu/f/your-form-id in the code above.