Skip to content

Application forms · 8 fields

Job application form template

A single-role application form: contact details, the position, experience and a CV. It comes in two versions, one with a CV upload and one with a CV link that works on every plan.

Get the code

Job application

Live demo: try the fields. Submitting sends nothing.

Why these fields

  • Full name

    text · required

    Required so you can address the candidate. Ask for nothing about age, nationality or a photo; you do not need them to assess an application.

  • Email

    email · required

    Required. It is how you reply, and it is the address a candidate will use if they ask you to delete their application.

  • Phone

    tel · optional

    Optional. Many recruiters prefer email for the first contact; keep phone only if you actually call candidates before an interview.

  • Position applied for

    text · required

    Required when one form serves several openings, so applications can be filtered by role. On a form for a single role you can make it a hidden field.

  • Years of experience

    select · optional

    Optional bands instead of a free number keep answers comparable when you screen many applications.

  • LinkedIn / portfolio URL

    text · optional

    Optional. In the CV link version this becomes the required CV field, which removes the need for uploads altogether.

  • Cover letter

    textarea · optional

    Optional. Some candidates write a lot, some write nothing; do not make it a gate unless you read every one.

  • Resume / CV

    file · required

    Required in this version and the reason it needs Professional: uploads are stored in the EU with the submission and deleted with it. On other plans the upload is rejected, so use the CV link version.

When to use it

Use it on a careers page or a job ad where each opening gets its own form, or one form with the position typed in. For speculative applications with no specific role, the open application hosted template fits better.

Decide on the CV first. File uploads need the Professional plan. On Free or Personal, use the CV link version below: candidates paste a link to their CV, LinkedIn profile or portfolio and nothing is uploaded.

Privacy and GDPR

  • Applications are personal data, and keeping them indefinitely is hard to justify. Tell candidates how long you keep them, and set the form's data retention to match.
  • On Free, submissions are deleted after 30 days automatically. On paid plans you choose the retention period per form, and uploaded files are deleted together with the submission.
  • If you want to keep candidates on file for future roles, ask for that separately with its own checkbox, as the open application template does.
  • Turn on link-only notifications in the form settings so the email you receive contains a link to the dashboard and no applicant details or CV.

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" enctype="multipart/form-data">
  <label for="name">Full 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="phone">Phone</label>
  <input type="tel" name="phone" id="phone" />
  <label for="position">Position applied for</label>
  <input type="text" name="position" id="position" required />
  <label for="experience">Years of experience</label>
  <select name="experience" id="experience">
    <option value="0-1">0-1</option>
    <option value="2-4">2-4</option>
    <option value="5-9">5-9</option>
    <option value="10+">10+</option>
  </select>
  <label for="linkedin">LinkedIn / portfolio URL</label>
  <input type="text" name="linkedin" id="linkedin" />
  <label for="cover_letter">Cover letter</label>
  <textarea name="cover_letter" id="cover_letter"></textarea>
  <label for="resume">Resume / CV</label>
  <input type="file" name="resume" id="resume" accept=".pdf,.doc,.docx" required />
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px" aria-hidden="true" />
  <button type="submit">Send</button>
</form>

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.

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 JobApplicationForm() {
  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">Full 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="phone">Phone</label>
      <input type="tel" name="phone" id="phone" />
      <label htmlFor="position">Position applied for</label>
      <input type="text" name="position" id="position" required />
      <label htmlFor="experience">Years of experience</label>
      <select name="experience" id="experience">
        <option value="0-1">0-1</option>
        <option value="2-4">2-4</option>
        <option value="5-9">5-9</option>
        <option value="10+">10+</option>
      </select>
      <label htmlFor="linkedin">LinkedIn / portfolio URL</label>
      <input type="text" name="linkedin" id="linkedin" />
      <label htmlFor="cover_letter">Cover letter</label>
      <textarea name="cover_letter" id="cover_letter" />
      <label htmlFor="resume">Resume / CV</label>
      <input type="file" name="resume" id="resume" accept=".pdf,.doc,.docx" required />
      <input
        type="text"
        name="_gotcha"
        tabIndex={-1}
        autoComplete="off"
        style={{ position: "absolute", left: "-9999px" }}
        aria-hidden="true"
      />
      <button type="submit">Send</button>
    </form>
  );
}

CV link version, works on every plan

The same form without a file upload. The CV becomes a required link, and a required consent checkbox carries the retention notice. Change 6 months to the period you actually use, and set the form's retention to match.

HTML

<form action="https://forms.formward.eu/f/your-form-id" method="POST">
  <label for="name">Full 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="phone">Phone</label>
  <input type="tel" name="phone" id="phone" />
  <label for="position">Position applied for</label>
  <input type="text" name="position" id="position" required />
  <label for="cv_link">Link to your CV, LinkedIn profile or portfolio</label>
  <small id="cv_link-help">Share only what you are comfortable with us storing.</small>
  <input type="text" name="cv_link" id="cv_link" placeholder="https://" aria-describedby="cv_link-help" required />
  <label for="cover_letter">Cover letter</label>
  <textarea name="cover_letter" id="cover_letter"></textarea>
  <label>
    <input type="checkbox" name="consent" id="consent" aria-describedby="consent-help" required /> I agree that my application details are stored and used to assess my application for this role
  </label>
  <small id="consent-help">We keep applications for up to 6 months after the role is filled and then delete them. You can ask us to delete yours sooner at any time.</small>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px" aria-hidden="true" />
  <button type="submit">Send</button>
</form>

Next.js

"use client";

import { useState } from "react";

export function JobApplicationCvLinkForm() {
  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">Full 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="phone">Phone</label>
      <input type="tel" name="phone" id="phone" />
      <label htmlFor="position">Position applied for</label>
      <input type="text" name="position" id="position" required />
      <label htmlFor="cv_link">Link to your CV, LinkedIn profile or portfolio</label>
      <small id="cv_link-help">Share only what you are comfortable with us storing.</small>
      <input type="text" name="cv_link" id="cv_link" placeholder="https://" aria-describedby="cv_link-help" required />
      <label htmlFor="cover_letter">Cover letter</label>
      <textarea name="cover_letter" id="cover_letter" />
      <label>
        <input type="checkbox" name="consent" id="consent" aria-describedby="consent-help" required /> I agree that my application details are stored and used to assess my application for this role
      </label>
      <small id="consent-help">We keep applications for up to 6 months after the role is filled and then delete them. You can ask us to delete yours sooner at any time.</small>
      <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

  • You get an email for each application on every plan. With link-only notifications on, it carries no personal data, only a link to the submission.
  • An automatic "we received your application" reply needs Professional. Webhooks into an applicant tracking system also start at Professional.
  • CSV export of all applications starts at Personal.

Questions

Can candidates upload a CV on the free plan?
No. File uploads start at Professional, and a submission carrying files to a form on a lower plan is rejected. Use the CV link version, which asks for a link to a CV, LinkedIn profile or portfolio instead.
How long should I keep job applications?
Only as long as you need them for the recruitment, and state the period on the form. Many EU employers keep them for a few months after the role is filled. Set the form's retention to the same period so deletion happens automatically.

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.

Use this template →

No website? Use a hosted form