splitforms.com
HR · CONTACT FORM TEMPLATE

Job Application Form (Careers Page)

Most career-page application forms hide behind Greenhouse / Lever, which charge $200+/month per role. For a startup hiring 1-3 roles a year, a self-hosted form with resume upload does the job.

1,000/mo free·no card·works on any host
form.htmlhtml34 lines
01<form action="https://splitforms.com/api/submit" method="POST">
02 <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
03 <input type="hidden" name="subject" value="New job application">
04
05 <label for="name">Full name *</label>
06 <input id="name" type="text" name="name" placeholder="Jane Builder" required>
07 <label for="email">Email *</label>
08 <input id="email" type="email" name="email" placeholder="jane@example.com" required>
09 <label for="role">Position you're applying for *</label>
10 <select id="role" name="role" required>
11 <option value="">Choose…</option>
12 <option>Engineering</option>
13 <option>Design</option>
14 <option>Marketing</option>
15 <option>Sales</option>
16 <option>Operations</option>
17 <option>Other</option>
18 </select>
19 <label for="linkedin">LinkedIn / portfolio URL</label>
20 <input id="linkedin" type="url" name="linkedin" placeholder="https://linkedin.com/in/janebuilder">
21 <label for="resume_url">Resume URL</label>
22 <input id="resume_url" type="url" name="resume_url" placeholder="https://...">
23 <label for="cover_letter">Cover letter *</label>
24 <textarea id="cover_letter" name="cover_letter" placeholder="Why this role, why us." required></textarea>
25
26 <!-- honeypot — bots fill every field -->
27 <input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off">
28
29 <button type="submit">Send</button>
30</form>
31
32<p style="margin-top:12px;font-size:11px;color:#888;text-align:right">
33 Powered by <a href="https://splitforms.com" style="color:#888;text-decoration:none" target="_blank" rel="noopener">splitforms</a>
34</p>
1,000
submissions / mo, free
6
fields, ready to ship
5
code outputs
60s
from copy to inbox
Job Application Form (Careers Page) — example splitforms template with submissions inbox
§ 01Why it mattersthe qualifying-fields argument

ATS subscriptions (Greenhouse, Lever, Workable) start at $6-15k/year for a small team — a lot of money for a startup that hires 2 engineers a year. A self-hosted application form on the careers page handles the inbound flow. The form captures name, email, role applying for, resume (PDF upload, often required), LinkedIn / portfolio URL, and a brief 'why us' textarea. The submission lands in the hiring manager's inbox or webhooks into Notion / Airtable / a Slack channel for triage. As soon as you're past 5 simultaneous open roles, an ATS pays off — until then, this works.

Resume upload (Pro) · webhooks into Notion / Airtable / Greenhouse.
✦ at a glance
  • Job application · 6 fields
  • HTML, JS, React, PHP, cURL outputs
  • One POST endpoint, no SDK
  • Honeypot + classifier, no CAPTCHA
§ 02Live previewinteractive · sandboxed · no key required

See exactly what your visitors see — and you’ll receive.

Left: the rendered form, fully interactive in a sandboxed iframe. Right: the email and dashboard view that lands the moment a visitor submits.

preview · job-application-formlocalhost:3000
✦ what you’ll see in your inbox

Every submission becomes an email plus a dashboard row. The fields below are the exact payload your form will send. Reply-to is wired to the visitor’s email so hitting reply goes back to them.

dashboard · new submission14ms · 200 OK
SUBJECT · New job application
Full name
Maya Iyer
Email
maya@studio71.co
Position you're applying for
Engineering
LinkedIn / portfolio URL
Resume URL
Cover letter

Iframe is sandboxed — submit doesn’t actually fire. Get your access key to wire it up live.

§ 03Three steps3 steps · ~60 seconds

Generate, embed, receive.

Three actions stand between you and your first lead. None of them require a backend, a database, or a CAPTCHA library.

STEP 01GENERATE

Capture role and basics

Required: name, email, role (dropdown of open positions), resume upload (PDF/DOCX). Optional: LinkedIn URL, portfolio URL, cover-letter textarea, location / timezone.

Create your form
key=sk_live_••••••••
STEP 02EMBED

Receive resume as attachment

Pro file upload — the resume PDF arrives as an actual email attachment, not a CDN link. Hiring manager opens it from inbox. Attachments cap at 25MB; resume PDFs are 100-500KB so headroom is huge.

snippethtml
<form action="https://splitforms.com/api/submit" method="POST">
  …
</form>
STEP 03RECEIVE

Webhook to Notion / Airtable

Push the application as a row in a Notion / Airtable database (one per role). Hiring team triages from there — moves to interview stage, archives obvious passes, comments inline. Cheaper than Greenhouse for low-volume hiring.

inbox · 1 newjust now
FROM contact@yoursite.com
New job application
Maya Iyer maya@studio71.co
Loved your last open house in Hayes — looking for similar with parking. Pre-approved through Wells Fargo.
§ 04Copy & ship5 languages · same endpoint

Five outputs. One backend.

HTML by default. Click open the language you ship in — every variant POSTs to the same /api/submit endpoint.

01HTMLform.html34 lines
<form action="https://splitforms.com/api/submit" method="POST">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
  <input type="hidden" name="subject" value="New job application">

  <label for="name">Full name *</label>
  <input id="name" type="text" name="name" placeholder="Jane Builder" required>
  <label for="email">Email *</label>
  <input id="email" type="email" name="email" placeholder="jane@example.com" required>
  <label for="role">Position you're applying for *</label>
  <select id="role" name="role" required>
    <option value="">Choose…</option>
    <option>Engineering</option>
    <option>Design</option>
    <option>Marketing</option>
    <option>Sales</option>
    <option>Operations</option>
    <option>Other</option>
  </select>
  <label for="linkedin">LinkedIn / portfolio URL</label>
  <input id="linkedin" type="url" name="linkedin" placeholder="https://linkedin.com/in/janebuilder">
  <label for="resume_url">Resume URL</label>
  <input id="resume_url" type="url" name="resume_url" placeholder="https://...">
  <label for="cover_letter">Cover letter *</label>
  <textarea id="cover_letter" name="cover_letter" placeholder="Why this role, why us." required></textarea>

  <!-- honeypot — bots fill every field -->
  <input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off">

  <button type="submit">Send</button>
</form>

<p style="margin-top:12px;font-size:11px;color:#888;text-align:right">
  Powered by <a href="https://splitforms.com" style="color:#888;text-decoration:none" target="_blank" rel="noopener">splitforms</a>
</p>
02JavaScriptform.js50 lines
<form id="lf-form">
  <label for="name">Full name *</label>
  <input id="name" type="text" name="name" placeholder="Jane Builder" required>
  <label for="email">Email *</label>
  <input id="email" type="email" name="email" placeholder="jane@example.com" required>
  <label for="role">Position you're applying for *</label>
  <select id="role" name="role" required>
    <option value="">Choose…</option>
    <option>Engineering</option>
    <option>Design</option>
    <option>Marketing</option>
    <option>Sales</option>
    <option>Operations</option>
    <option>Other</option>
  </select>
  <label for="linkedin">LinkedIn / portfolio URL</label>
  <input id="linkedin" type="url" name="linkedin" placeholder="https://linkedin.com/in/janebuilder">
  <label for="resume_url">Resume URL</label>
  <input id="resume_url" type="url" name="resume_url" placeholder="https://...">
  <label for="cover_letter">Cover letter *</label>
  <textarea id="cover_letter" name="cover_letter" placeholder="Why this role, why us." required></textarea>
  <button type="submit">Send</button>
</form>

<p style="margin-top:12px;font-size:11px;color:#888;text-align:right">
  Powered by <a href="https://splitforms.com" style="color:#888;text-decoration:none" target="_blank" rel="noopener">splitforms</a>
</p>

<script>
  document.getElementById('lf-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const data = new FormData(e.target);
    data.set('access_key', 'YOUR_ACCESS_KEY');
    data.set('subject', 'New job application');

    const res = await fetch('https://splitforms.com/api/submit', {
      method: 'POST',
      body: data,
      headers: { Accept: 'application/json' },
    });

    const json = await res.json();
    if (json.success) {
      e.target.reset();
      alert('Sent!');
    } else {
      alert('Error: ' + (json.message || 'Try again'));
    }
  });
</script>
03React / Next.jsForm.tsx65 lines
'use client';

import { useState, type FormEvent } from 'react';

export default function JobForm() {
  const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle');

  async function onSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus('sending');

    const data = new FormData(e.currentTarget);
    data.set('access_key', 'YOUR_ACCESS_KEY');
    data.set('subject', 'New job application');

    const res = await fetch('https://splitforms.com/api/submit', {
      method: 'POST',
      body: data,
      headers: { Accept: 'application/json' },
    });

    const json = await res.json();
    setStatus(json.success ? 'sent' : 'error');
    if (json.success) e.currentTarget.reset();
  }

  if (status === 'sent') return <p>Thanks — we&rsquo;ll be in touch.</p>;

  return (
    <>
    <form onSubmit={onSubmit}>
      <label htmlFor="name">Full name *</label>
      <input id="name" type="text" name="name" placeholder="Jane Builder" required />
      <label htmlFor="email">Email *</label>
      <input id="email" type="email" name="email" placeholder="jane@example.com" required />
      <label htmlFor="role">Position you're applying for *</label>
      <select id="role" name="role" required>
        <option value="">Choose…</option>
        <option>Engineering</option>
        <option>Design</option>
        <option>Marketing</option>
        <option>Sales</option>
        <option>Operations</option>
        <option>Other</option>
      </select>
      <label htmlFor="linkedin">LinkedIn / portfolio URL</label>
      <input id="linkedin" type="url" name="linkedin" placeholder="https://linkedin.com/in/janebuilder" />
      <label htmlFor="resume_url">Resume URL</label>
      <input id="resume_url" type="url" name="resume_url" placeholder="https://..." />
      <label htmlFor="cover_letter">Cover letter *</label>
      <textarea id="cover_letter" name="cover_letter" placeholder="Why this role, why us." required />

      <button type="submit" disabled={status === 'sending'}>
        {status === 'sending' ? 'Sending…' : 'Send'}
      </button>

      {status === 'error' && <p>Something went wrong. Try again.</p>}
    </form>

      <p style={{ marginTop: 12, fontSize: 11, color: '#888', textAlign: 'right' }}>
        Powered by <a href="https://splitforms.com" target="_blank" rel="noopener" style={{ color: '#888', textDecoration: 'none' }}>splitforms</a>
      </p>
    </>
  );
}
04PHPsubmit.php28 lines
<?php
// Drop into a PHP page. Receives a form POST and proxies it to splitforms.com.
// Useful when you want to add server-side validation or rate limiting.

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $allowed = ['name', 'email', 'role', 'linkedin', 'resume_url', 'cover_letter'];
    $payload = ['access_key' => 'YOUR_ACCESS_KEY'];
    $payload['subject'] = 'New job application';

    foreach ($allowed as $f) {
        if (isset($_POST[$f])) $payload[$f] = $_POST[$f];
    }

    $ch = curl_init('https://splitforms.com/api/submit');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/json']);
    $response = curl_exec($ch);
    $status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    header('Content-Type: application/json');
    http_response_code($status);
    echo $response;
    exit;
}
?>
05cURLtest.sh10 lines
curl -X POST https://splitforms.com/api/submit \
  -H "Accept: application/json" \
  -d "access_key=YOUR_ACCESS_KEY" \
  -d "subject=New job application" \
  -d "name=Jane Builder" \
  -d "email=jane@example.com" \
  -d "role=Engineering" \
  -d "linkedin=https://example.com" \
  -d "resume_url=https://example.com" \
  -d "cover_letter=Hello from cURL" 

Replace YOUR_ACCESS_KEY with the key from your dashboard. That’s the only edit.

§ 06FAQ4 answered

Things people ask before they ship.

Direct answers, no marketing fluff. Missing one? Email hello@splitforms.com.

01Is this EEOC / OFCCP compliant?
If you're under the regulated thresholds (15 employees for most EEOC requirements; federal contractor for OFCCP), basic forms are fine. If you're regulated, you need to collect voluntary self-ID demographics on a separate page (race, gender, veteran, disability) with explicit non-discrimination language. Your employment lawyer should review the final flow.
02How do I avoid resume-spam from staffing agencies?
Hidden honeypot field plus splitforms' spam classifier catches most. For persistent agency spam, add a 'are you applying directly or through an agency?' radio — direct applications get the role-specific reply; agency applications get a 'we don't accept third-party submissions' auto-response.
03Should I require a cover letter?
For senior or specialised roles, yes — quality goes up with the friction. For volume hiring (support, ops, junior eng), no — required cover letters drop application count 60-80% and the cover-letter quality is mostly noise anyway. Tune by role.
04Can I integrate with Notion / Airtable / Greenhouse later?
Yes — webhook the JSON. Notion has a native database API, Airtable accepts via Zapier, Greenhouse accepts via Zapier or their Harvest API. As you scale into a real ATS, point the webhook at Greenhouse and decommission the Notion database. The form stays the same.
✻ ✻ ✻

Ship your job application form (careers page) in 60 seconds.

1,000 free submissions per month. No credit card. Copy the snippet, paste your access key, watch leads land in your inbox.

Get free access key →Browse all 60 templates →
v0.1 · founders pricing locked in · early access open