splitforms.com
← Back to the journal

Portfolio contact form and contact page: options compared, plus HTML

Add a contact form to your portfolio in 2 minutes: free options compared on limits, uploads and embeds, plus copy-paste HTML for a contact me section.

Start free — 500 submissionsSee pricing →No card, no time limit. Pro is $5 per month.
Short answer
A portfolio contact form works best as plain HTML posting to a form backend, so no widget or badge loads. Web3Forms gives 250 free submissions a month, Basin and Forminit include free uploads, Tally suits longer intake forms, and FormSubmit needs no signup. splitforms, which publishes this guide, gives 500 free submissions in total with no card or expiry.
Try it on your own form

Point any HTML form at one endpoint. 500 free submissions — no card, no time limit — and unlimited forms.

Create free account

Disclosure: splitforms publishes this guide and is one of the options in it. There is no overall ranking: options are listed alphabetically and each use case names the tool whose published plan fits it best. Competitor figures were read from each vendor's own pricing or docs page between September 27 and October 10, 2026 (sources at the end), and plans change, so confirm before you sign up.

What does a portfolio contact form need?

Portfolio sites (designers, developers, illustrators, photographers, small studios) share the same five constraints, and few popular form tools meet all five at once. Here's the brief:

  1. Minimal visual footprint. Your portfolio is the brand. The form should disappear into your design, with no badge, no iframe outline, no third-party fonts and no "powered by" line under the button.
  2. Little JavaScript overhead. You spent time getting your hero animation to feel right. A form widget that ships its own scripts and fonts can undo that work on the Lighthouse audit a prospective client runs out of curiosity.
  3. Free at very low volume. Many portfolios get a modest number of submissions a year, so paying a monthly fee for a feature you rarely use is hard to justify.
  4. File uploads for briefs. Clients who are serious may want to attach a moodboard, PDF brief or reference deck before the first call.
  5. Doesn't expose your email. mailto: links and plaintext addresses get scraped. A hosted form keeps your inbox address private and rotatable.

What goes on a portfolio contact page? (checklist)

Whether it's a separate /contact page or a "contact me" section at the bottom of a one-pager, the same short list covers it:

  1. A short form. Name, email, message. Add an optional file field for briefs if you take client work (on splitforms, uploads need a paid plan). The copy-paste HTML is further down.
  2. A reply-time promise. One line such as "I reply within 48 hours on weekdays" sets expectations and cuts follow-up emails.
  3. Availability status. "Taking on two new projects this quarter" or "Open to full-time roles" tells visitors whether to write at all.
  4. A booking link. Cal.com or Calendly for people who would rather pick a call slot than write a message.
  5. Social and GitHub links. LinkedIn, GitHub, Dribbble or Behance, whichever fits your work. They double as proof of work and a fallback channel.
  6. Your timezone. "Based in Lisbon (UTC+0)" answers "when can we talk?" before anyone asks.
  7. A one-line privacy note. Say you'll only use the details to reply. If you work with EU clients, the splitforms GDPR page covers how the data you collect is handled.

On a dedicated contact page, keep the form above the fold. On a one-page portfolio, make contact the last section, link to it from your navigation, and repeat a Contact button in the hero:

<nav>
  <a href="#work">Work</a>
  <a href="#about">About</a>
  <a href="#contact">Contact</a>
</nav>

<!-- ...hero, work and about sections... -->

<section id="contact">
  <h2>Let's work together</h2>
  <p>I reply within 48 hours. Taking on two projects this quarter. Based in Lisbon (UTC+0).</p>
  <!-- paste the contact form from the copy-paste section below -->
</section>

<style>
  /* keeps a sticky header from covering the heading when #contact is linked */
  #contact { scroll-margin-top: 80px; }
</style>

How do the contact form options compare for a portfolio?

Listed alphabetically. Prices and limits are from each vendor's own pricing page; the free tiers are monthly unless marked otherwise.

ToolFree tierHow it embedsFile uploads on freeBest for
Basin50/month, 1 formNative HTMLYes, 100 MBSpam controls plus free uploads
Forminit (formerly Getform)100/month, 1 formNative HTMLYes, 100 MB storageDevelopers who want framework guides
Formspree50/monthNative HTMLNo (storage starts on Personal)Quick setup if 50/month is enough
FormSubmitUnlimited, no account, email onlyNative HTMLEmailed, up to 10 MB total, not storedNo-signup email forwarding
splitforms500 total, one-time, no cardNative HTMLNo (paid plans, Pro $5/month)A form in your own HTML with a large one-time allowance
TallyUnlimited (fair use)Link or iframe; "Made with Tally" on FreeYes, 10 MB per fileMulti-step intake forms
Typeform10 responses/monthLink or iframe; Typeform branding on FreeNo (Basic and up)Conversational, quiz-style intake
Web3Forms250/monthNative HTMLNo (Pro and up)Largest metered monthly free tier

Native HTML options post straight to an API endpoint, so the form takes your CSS and adds no widget. Tally and Typeform paint the form themselves through a link or embed. For a plain portfolio contact form, native HTML gives the most design control.

Which option is best for each use case?

Each row names the tool whose published plan fits that use case best, based on the figures above.

Your situationPickWhy
Uploads on a free plan, with the form in your own HTMLBasin or ForminitBasin Free includes 100 MB of uploads on 50 submissions a month; Forminit Free includes 100 MB of storage on 100 a month.
The largest monthly free tierWeb3Forms250 submissions a month, unlimited forms and domains.
The largest one-time free allowancesplitforms500 submissions in total, no card, no expiry.
No account at all, just forward to my emailFormSubmitPut your email in the action URL and confirm once; free and unlimited, with no dashboard.
A longer client-intake questionnaire on a sub-pageTallyMulti-step forms with conditional logic, free and unlimited under fair use.
A conversational, quiz-style flowTypeformFree is 10 responses a month; Basic starts at $39/month ($28 billed yearly).
Auto-reply and signed webhooks on a plain HTML form for one low pricesplitforms Pro$5/month for 5,000 submissions a month; both are paid features.
You already use Formspree and get only a few submissionsFormspree50 a month free; no need to switch until you hit the cap or want webhooks.

The options in detail, alphabetically

Basin: spam controls and uploads on Free

Basin is a form backend with native HTML posting. Free is 50 submissions a month on 1 form, with 30-day retention and 100 MB of file uploads. Its spam toolkit covers reCAPTCHA, hCaptcha, Cloudflare Turnstile, domain restriction and a custom honeypot. Starter is $12.5/month billed yearly for 250 submissions a month. splitforms vs Basin.

Forminit (formerly Getform): developer-leaning

Getform now runs as Forminit. Free is 100 submissions a month on 1 form, with 100 MB of file storage; webhooks start on Pro. It publishes setup guides for common frameworks. splitforms vs Getform.

Formspree: the long-standing form backend

Formspree posts native HTML. Free is 50 submissions a month with 30 days of history, file storage starts on the Personal plan, and webhooks start on Professional ($30/month, $20 billed yearly). It's a fit for a low-traffic personal site where 50 a month is enough. Side-by-side: splitforms vs Formspree. Switching takes about 5 minutes; see migrate from Formspree.

FormSubmit: free, no signup, email only

No account and no API key: set action="https://formsubmit.co/your@email.com" and confirm the first submission from the activation email. It's free and unlimited. The trade-offs are that your address sits in the form action unless you use its random-string alias, there is no dashboard, submissions are kept for 30 days, and attachments are emailed, not stored. A reasonable choice for a placeholder or side project; check whether it covers your needs before you use it for a career portfolio.

splitforms: a form backend with a one-time free allowance

splitforms publishes this guide. Free is 500 submissions in total (one-time, no card, no expiry) with unlimited forms. Pro is $5/month for 5,000 submissions a month and adds file uploads (5 files of 5 MB each per submission), auto-reply and HMAC-signed webhooks; Annual is $49/year for 15,000 a month. A hand-written HTML form adds no widget, fonts or badge, and takes your CSS. Dashboard-generated Free code includes a small attribution link that paid plans remove. See pricing.

Setup takes a couple of minutes: sign in with your email, copy the access key, and paste the snippet below into your portfolio. The 60-second guide covers every framework, and the free contact form template page has copy-paste HTML pre-wired to splitforms.

Tally: free hosted builder for longer forms

Tally offers multi-step forms and conditional logic on an unlimited free plan under fair use, including 10 MB uploads. For a plain "contact me" form that can be more than you need, and the free plan shows a "Made with Tally" badge (Pro, $29/month, or $24 billed yearly, removes it). The embed is an iframe, so it may not match a minimal design. It fits a longer client-intake questionnaire on a sub-page. splitforms vs Tally.

Typeform: conversational forms

Typeform's one-question-at-a-time format is distinctive. Free is 10 responses a month with Typeform branding, and file uploads start on Basic. The embed loads Typeform's own resources. It fits a dedicated intake questionnaire on a sub-page more than a primary contact form. splitforms vs Typeform, or migrate from Typeform in 5 minutes.

Web3Forms: the largest metered free tier

Web3Forms is an access-key endpoint for static sites. Free is 250 submissions a month with unlimited forms and domains, hCaptcha spam protection and 30 days of history. File uploads start on the Pro plan ($149/year). The dashboard is a simple list of submissions. See splitforms vs Web3Forms.

When is splitforms not the right choice?

  • You need uploads on a free plan. On splitforms uploads are paid (Pro, $5/month). Basin Free includes 100 MB, Forminit Free 100 MB and Tally Free 10 MB per file.
  • You want a free allowance that resets monthly. splitforms Free is 500 submissions once, not per month. Web3Forms gives 250 a month and Formspree 50.
  • You want a visual form builder. splitforms takes a pasted HTML form. Tally and Typeform are visual builders that host the form for you.
  • You have no site to put the form on. splitforms is a backend for HTML you host yourself. A Tally or Typeform link works without a site.
  • You need EU data residency. splitforms hosts data in the US under Standard Contractual Clauses (see the DPA). Tally stores data in Europe.
  • You want webhooks or an auto-reply for free. Both are paid splitforms features. Tally Free includes webhooks.

How do I add a contact form to my portfolio? (copy-paste HTML)

Here's a minimal "Get in touch" form: three fields, an optional file upload, a honeypot and a thank-you redirect. Paste it into any portfolio page (Webflow, Astro, Next.js, raw HTML) and replace YOUR_ACCESS_KEY with the key from your dashboard. It works as-is on static hosts like GitHub Pages, Netlify and Vercel; the GitHub Pages contact form walkthrough goes step by step.

Example: a portfolio contact section, rendered

Let's work together

Tell me about your project and I'll get back to you by email.

Preview only: this copy posts to a placeholder YOUR_ACCESS_KEY, so sending is switched off. Add your own key and it goes live on your portfolio. Download the template (.html)

<form action="https://splitforms.com/api/submit" method="POST" enctype="multipart/form-data">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
  <input type="hidden" name="subject"    value="New portfolio inquiry" />
  <input type="hidden" name="redirect"   value="https://yoursite.com/thanks" />

  <label>
    Name
    <input type="text" name="name" required />
  </label>

  <label>
    Email
    <input type="email" name="email" required />
  </label>

  <label>
    Tell me about your project
    <textarea name="message" rows="5" required></textarea>
  </label>

  <label>
    Optional: attach a brief (PDF, images)
    <input type="file" name="brief" />
  </label>

  <!-- Honeypot: humans skip, bots fill -->
  <input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off" />

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

Two notes. The redirect value must be a path on your site (like /thanks) or a page on the same domain as the form; anything else is ignored and splitforms shows its standard success page. It works on every plan, including Free. The file field needs a paid plan (Pro, $5/month); on Free, the message still arrives and the file is dropped, so remove the field if you're staying on Free.

That's it: no script tags, no third-party CSS, no fonts. Style it with whatever your portfolio already uses, because it's a regular <form> with regular <input> elements. For framework-specific versions, see Next.js, Astro, React, Svelte and Vue.

For developers: inline submit without leaving the page

The template above uses a full-page redirect, which is bulletproof and needs no JavaScript. If you want the form to stay on the page with an inline success state (nicer on a single-page portfolio), submit it with fetch() and read the JSON response instead of setting a redirect field:

const form = document.querySelector("form");
form.addEventListener("submit", async (e) => {
  e.preventDefault();
  const res = await fetch(form.action, {
    method: "POST",
    body: new FormData(form),
    headers: { Accept: "application/json" },
  });
  const data = await res.json();
  if (data.success) {
    form.replaceWith(Object.assign(document.createElement("p"),
      { textContent: "Thanks — I'll reply within 48 hours." }));
  } else {
    // data.message contains the validation or spam reason
    console.error(data.message);
  }
});

Send Accept: application/json and splitforms returns a structured { success, message } object instead of a redirect, so you own the success and error UX. In a component framework, the same idea applies: bind the handler and render state from the JSON. Copy-paste starters for Next.js, React, Svelte, Vue and Astro each show the AJAX and no-JS variant. splitforms also exposes an MCP server if you want an AI agent to read your submissions.

How does a form backend fit the full portfolio setup?

Here is one way to set up a portfolio contact flow with a form backend like splitforms:

  1. The form itself. Sign in at splitforms.com/login, copy the access key, paste the snippet above.
  2. A custom thank-you page at /thanks on your portfolio. Use the redirect hidden field (it works on every plan) to send people there, with your booking link (Cal.com, Calendly) and a "reply within 48 hours" expectation.
  3. Honeypot plus automatic spam filtering, so there's no captcha and the form stays clean visually. See the honeypot vs reCAPTCHA breakdown.
  4. Allowed Domains lock in the splitforms dashboard so the key only works from your portfolio domain. If someone copies your access key off the page source, it won't work from their domain.
  5. Optional webhook to Discord or Slack so you get a ping on your phone when a real client submits. Webhooks are on paid plans (Pro, $5/month). See Discord or Slack setup.
  6. Optional auto-reply to the submitter so they immediately get a "thanks, I got it" email, with replies going to your inbox. Auto-replies are on paid plans (Pro, $5/month).

Steps 1-4 cost nothing on the Free plan (500 submissions total); the webhook ping and auto-reply in steps 5-6 need a paid plan.

What are the most common portfolio contact form mistakes?

  • Using mailto: as the "contact form". It does nothing on devices without a configured mail app and loses everyone who bounces at the compose window. Replace it with a real form, even a three-field one.
  • Showing your raw email in the page text. Scrapers pick it up fast and it ends up on spam lists. Route through a hosted form.
  • An iframe form on the hero page. If you use Typeform or Tally on a free plan, the badge can undermine a carefully designed page. Either pay to remove it or use native HTML.
  • Asking for too much information. Name, email, message, maybe an optional upload. Every extra required field can cut conversion; save qualifying questions for the discovery call.
  • No thank-you page. Sending users back to the home page after submit is a missed step. Use the redirect field to land them somewhere with a booking link and next steps.
  • Skipping the honeypot. Add the hidden botcheck field. It costs one line of HTML and stops many simple bots without any UX cost. Full spam playbook: stop contact form spam.
  • Ignoring the cap on a free plan. One side project, one big mention or an accidental form on another page can use up a small monthly cap. Know your tool's limit and what happens when you pass it.

Which one should you pick? (decision guide)

  • You want a free allowance that resets monthly → Web3Forms (250/month) or Formspree (50/month).
  • You want a large one-time allowance on your own HTML form → splitforms (500 total, no expiry).
  • You need free uploads on an HTML form → Basin or Forminit.
  • You're already on Formspree free and only get a few submissions → no need to switch; if you hit 50/month or want webhooks, see migrate from Formspree.
  • You need a multi-step client intake questionnaire on a separate page → Tally on its free plan works.
  • You just want a throwaway form for a side project → FormSubmit, no signup.
  • You need project intake or shoot-booking fields rather than a plain contact form → see the best contact form for freelancers or the best contact form for photographers.

Browse other splitforms guides, the cheapest form-to-email comparison, or jump to the FAQ for plan-specific questions.

Contact form guides for other industries

FAQ

Is this guide biased toward splitforms?

splitforms publishes this guide and is one of the options in it, so read it as vendor-written. To keep it checkable there is no overall ranking: tools are listed alphabetically, each use case names the option whose published plan fits it best (often not splitforms), competitor figures come from each vendor's own pricing page, and a section lists when splitforms is not the right choice.

What should a portfolio contact page include?

A short form (name, email, message, optionally a file for a brief), a line saying how fast you reply, your availability, a booking link, your social and GitHub links and your timezone. Keep the form above the fold.

Where should the contact section go on a one-page portfolio?

Make it the last section with an id='contact' anchor, add a Contact link to your sticky navigation, and repeat a Contact button in the hero.

How do I add a contact form to a GitHub Pages, Netlify or Vercel portfolio?

A static portfolio has no server code of its own to receive the form, so point the form's action at a hosted endpoint and add your access_key. The form above works as-is on all three, with no build step or serverless function.

Is it worth reaching out through a developer's portfolio contact form?

Yes. The message goes straight to the owner's inbox. Include the project, budget and timeline to get a faster reply, and use their email or LinkedIn as a fallback if you don't hear back.

How do I show a thank-you message without leaving the page?

Submit with fetch and an Accept: application/json header, then show your message inline when the JSON says success. If you'd rather send people to a separate thank-you page, keep the hidden redirect field from the template instead; it works on every plan, including Free.

Do I need a privacy notice or consent checkbox on a portfolio form?

Add a one-line privacy note saying how you'll use the details. Only add a consent checkbox if you'll add people to a mailing list. This isn't legal advice.

Do I really need a hosted form for a portfolio? Why not just mailto:?

A mailto: link does nothing on devices with no mail app set up, exposes your address to scrapers, and loses visitors who never finish the email. A form backend handles delivery, spam filtering and storage, so you paste a short HTML snippet and move on. Free tiers differ: splitforms Free is 500 submissions in total, Web3Forms gives 250 a month and Formspree 50 a month.

Will the embed slow down my portfolio's Lighthouse score?

A native HTML form posting to an API adds no JavaScript and nothing render-blocking, which is how splitforms, Web3Forms, Basin, FormSubmit and Formspree work. Iframe or script embeds such as Typeform and Tally load extra resources, so run Lighthouse on your page with the embed in place if Core Web Vitals matter to you.

How many submissions will a typical portfolio actually get?

It varies a lot. Many portfolios get a modest number of messages a year, while designers and developers with steady inbound (newsletter, talks, podcasts) can see far more. splitforms' Free plan is 500 submissions total, one time, so at 100 messages a year it lasts about five years. Web3Forms' 250 a month resets every month. You'd only need a paid splitforms plan for file uploads, webhooks or an auto-reply, or if you run client lead-gen sites from the same account.

Can I add file uploads for clients to send briefs and references?

Yes. Add <input type='file' name='brief' /> and set enctype='multipart/form-data' on the form. On splitforms, uploads are a paid-plan feature: Pro ($5/month) includes 500 MB of storage (5 GB on Annual), and each submission can carry up to 5 files of 5 MB each. On the Free plan the text fields still arrive and the files are dropped. Free uploads exist elsewhere: Basin Free includes 100 MB, Forminit Free 100 MB of storage and Tally Free 10 MB per file. Check each tool's current plan before you rely on it.

How do I prevent the contact form from getting scraped and spammed?

Use three layers. (1) A honeypot: a hidden <input name='botcheck' /> field that humans skip and bots fill. (2) splitforms' rule-based smart spam filtering, plus an optional time-trap that flags forms submitted too fast. (3) Allowed Domains in the dashboard, so your access key only works from yourportfolio.com; rate limiting is built in. With these on, a portfolio form rarely needs a CAPTCHA. The full comparison is in /blog/honeypot-vs-recaptcha.

Does the form expose my email address publicly?

Not with a form backend. Your email address lives in your splitforms account, never in the HTML the browser downloads. Scrapers see only the access key, which is rate-limited and can be locked to your domain with Allowed Domains. A mailto: link or a plain-text address, by contrast, gets harvested quickly. FormSubmit puts your address in the form action unless you use its random-string alias. If a splitforms key ever leaks, regenerate it in the dashboard and update one line of HTML.

What if my design language is super minimal — will the form fit?

Native HTML forms render with whatever CSS you already ship. splitforms doesn't inject styles or classes into a hand-written form, so a form with one input and a button can match the rest of your portfolio exactly, with no iframe outline or extra fonts. Code generated from the dashboard on the Free plan includes a small attribution link; paid plans remove it. The template in this article uses no framework, just a few fields and a submit button styled with your own CSS variables.

Can I redirect to a custom thank-you page after submission?

Yes. Add <input type='hidden' name='redirect' value='https://yoursite.com/thanks' /> and splitforms sends the visitor to that page after a successful submit, on every plan including Free. Use a path on your own site (like /thanks) or a page on the domain the form is on; any other address is ignored and the standard success page shows instead. The thank-you page is where you set expectations: how fast you reply, plus your booking link in the meantime.

Related articles

More practical guidance from guides.

Browse the journal →
Guides

Gravity Forms Not Sending Email? 5 Fixes That Work (2026)

Gravity Forms notifications not arriving? Find the wp_mail cause in 2 minutes and fix it wit…

7 min readRead →
Guides

Form Backend Migration Checklist: Fields, Files and Email

Move an existing form to SplitForms with a practical checklist for field mapping, redirects,…

6 min readRead →
Guides

Best MCP Servers for Building Forms with AI Agents (2026)

Ranked MCP servers that let Claude Code, Cursor, and other agents create forms and read subm…

12 min readRead →

Explore this topic

Start with the overview, then move into focused guides.

OverviewContact Forms for Every FrameworkStart here →GuideContact Form for Next.jsRead →GuideContact Form for ReactRead →GuideContact Form for VueRead →ReferenceContact form guideOpen →

Building forms with ChatGPT, Claude, Cursor, or v0? Connect the native MCP server and give your agent a production form backend.

Explore the MCP server →

Give your form a production backend.

One endpoint adds delivery, spam filtering, storage, and integrations. Start with 500 free submissions — no card, no time limit.

Create free accountRead the docs →
Secure checkoutSSL encryptionPrivacyProtected
VISAAMERICANEXPRESSstripe