splitforms.com
← Back to the journal

Add a Contact Form to Ghost (No Plugin, 2026)

Add a contact form to a Ghost blog with one HTML card — no plugin or backend. Submissions stored, emailed, and spam-filtered free for 500/month.

Start free — 500 submissions/moSee pricing →No credit card. Paid plans from $5/mo.

Why Ghost doesn't have a native contact form

Ghost is built for publishing and paid memberships, not lead capture. The closest thing to a "form" in stock Ghost is the members/subscription Portal — the widget that lets visitors sign up as free or paid members and get your posts by email. It has no field for a one-off message, no way to route an advertising inquiry, and no submit button that just says "send."

Unlike WordPress, Ghost also has no plugin marketplace. There's nothing to install called "Contact Form 7 for Ghost." Ghost's extensibility model is theme code and integrations, not plugins — which is exactly why an embedded HTML form is the standard fix, on both Ghost(Pro) and self-hosted Ghost. Because the form is just markup living inside a page or post, it works the same way regardless of how your Ghost instance is hosted.

Step 1: Get a free splitforms access key (1 minute)

Open splitforms.com/login, enter your email, and paste the 6-digit code you receive. The dashboard generates your access key automatically on first load — copy it.

The Free plan covers 500 submissions/month, unlimited forms, spam filtering, the dashboard, and email notifications to your inbox — free on every plan, no credit card. If your Ghost blog grows past that, Pro is $5/month for 5,000 submissions plus signed webhooks and integrations, Pro is $5/month for 5,000, and a 3-Year plan locks in the price for $59 one time. Full breakdown at /pricing.

Step 2: Create or open your Contact page in Ghost Admin

Log in to Ghost Admin (usually yoursite.com/ghost) and go to Pages in the left sidebar. If you already have a Contact page, open it. If not:

  1. Click New page.
  2. Give it a title — "Contact" is fine.
  3. Leave the page unpublished for now; you'll publish once the form is in and tested.

Nothing here differs between Ghost(Pro) and a self-hosted install — the Admin editor is identical either way.

Step 3: Add an HTML card

In the page editor, click on an empty line to bring up the + button (or type / to search cards directly).

  1. Click the + icon, or type /html.
  2. Select HTML from the card picker.
  3. An empty code box appears — this is where the form snippet goes.

The HTML card renders whatever you paste into it exactly as written, inside your theme's content column. No Ghost theme file needs editing.

Step 4: Paste the complete form snippet

Paste this into the HTML card and replace YOUR_ACCESS_KEY with the key you copied in Step 1. It includes minimal inline CSS so it looks intentional immediately, plus a hidden honeypot field for spam protection.

<form action="https://splitforms.com/api/submit" method="POST" class="sf-ghost-form">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
  <input type="hidden" name="subject" value="New message from your Ghost site" />
  <input type="hidden" name="form_loaded_at" id="form_loaded_at" />

  <label for="sf-name">Name</label>
  <input type="text" id="sf-name" name="name" required autocomplete="name" />

  <label for="sf-email">Email</label>
  <input type="email" id="sf-email" name="email" required autocomplete="email" />

  <label for="sf-message">Message</label>
  <textarea id="sf-message" name="message" rows="5" required></textarea>

  <!-- Honeypot: bots fill this, real readers never see it -->
  <input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off" />

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

<script>
  document.getElementById('form_loaded_at').value = Date.now();
</script>

<style>
  /* Minimal styling — everything else inherits your Ghost theme's fonts and colors */
  .sf-ghost-form { max-width: 520px; margin: 0 auto; }
  .sf-ghost-form label { display: block; margin: 14px 0 6px; font-weight: 600; }
  .sf-ghost-form input[type="text"],
  .sf-ghost-form input[type="email"],
  .sf-ghost-form textarea {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid #ddd;
    border-radius: 8px;
    font: inherit; /* inherits Ghost's theme body font */
    box-sizing: border-box;
  }
  .sf-ghost-form button {
    margin-top: 16px;
    padding: 12px 24px;
    background: #15171a; /* swap for your theme's accent color */
    color: #fff;
    border: 0;
    border-radius: 8px;
    font-weight: 700;
    cursor: pointer;
  }
</style>

Three things to notice: the action URL is always https://splitforms.com/api/submit regardless of plan; the hidden access_key field tells splitforms which dashboard to deliver to; and the botcheck honeypot plus form_loaded_at timestamp are the two spam traps — keep both in place even though they're invisible to readers. More on how they work in honeypot vs reCAPTCHA.

Want the same building blocks without the Ghost-specific card steps? See the plain HTML contact form reference or grab a ready-made free contact form template.

Step 5: Publish, then test the live form

  1. Click Preview in the top-right of the editor to check the form renders and inherits your theme correctly.
  2. Click PublishPublish, update, or send → confirm.
  3. Open the live page in an incognito window (avoids the Ghost Admin toolbar interfering).
  4. Fill out the form with a real-looking test message using a real email address you control.
  5. Click submit. You should see a success response, or land on a redirect URL if you added a hidden redirect field.

If the form renders as plain text instead of an actual form, double-check the card type is HTML, not Markdown — Ghost's Markdown card escapes angle brackets instead of rendering them.

Step 6: Where submissions land

Two places, immediately:

  • Your splitforms dashboard. Every submission appears in the submissions view the moment it arrives, with the visitor's IP, timestamp, and a spam-likelihood score.
  • Your inbox. Email notifications are free on every plan, including Free — you don't need to upgrade just to get emailed. You'll get the message content within seconds of the visitor clicking submit.

Nothing about the delivery path changes based on whether your Ghost site is on Ghost(Pro) or self-hosted — the form posted directly from the browser to splitforms, bypassing Ghost entirely.

Step 7 (optional): Autoresponders and webhooks on Starter

The Free plan is a complete, working contact form. If you want more automation, Pro ($5/month) adds:

  • Autoresponders. Automatically email the person who submitted the form a confirmation — "Thanks, I'll get back to you soon" — with no extra code.
  • Signed webhooks. Forward every submission to Slack, Discord, or your own endpoint. Webhooks fire once with an 8-second timeout and an HMAC-SHA256 signature (a sha256= prefix on the header) — there's no automatic retry, so make sure the receiving endpoint responds fast.
  • 13 native integrations. Notion, Airtable, Google Sheets, Mailchimp, and more — connect once in the dashboard and every submission becomes a row or a message automatically.

None of this is required to get a working Ghost contact form — it's there if you outgrow plain email notifications.

Matching your Ghost theme's styling

Because the form sits inside an HTML card, it inherits your theme's body font, base text color, and line-height by default — that's why the snippet above avoids setting font-family anywhere. The inline <style> block only touches spacing, borders, and the button color, which keeps the form visually consistent without fighting your theme's CSS.

To match your theme's accent color exactly, open your Ghost theme's design settings (or check your theme's --ghost-accent-color custom property if it uses one) and swap the hex value in the .sf-ghost-form button background rule. If your theme sets a max content width narrower than 520px, the form will simply respect it since it's rendered inline with the rest of the page content.

Contact form vs. members/newsletter — different jobs

It's worth being precise about this, because the two get confused often. Ghost's built-in Portal handles subscriptions: a visitor becomes a free or paid member and starts receiving your posts by email. It's a newsletter and paywall system, not a messaging system — there's no field for a visitor to type "I'd like to advertise on your blog" or "Found a broken link on page 3."

A contact form is a separate need entirely: it lets a reader, advertiser, or collaborator send you a one-off message without becoming a member of anything. You can run both side by side on the same Ghost site with zero conflict — Portal keeps handling subscribers, and your HTML-card form keeps handling everyone who just wants to say something to you directly.

Alternatives, briefly

A few other approaches show up in Ghost forums. Here's how they compare:

  • Formspree or Getform embeds. These work the same way — paste an HTML form into an HTML card, point the action URL at their endpoint. Watch the free-tier limits closely: Formspree's free plan caps out much lower per month than splitforms' 500, so a moderately busy Contact page can hit the wall fast.
  • Google Form iframe. Technically works — paste an <iframe> pointed at a published Google Form. But it renders Google's own styling inside a box that clashes with Ghost's clean, typography-first theme design, and it can't inherit your theme's fonts or colors at all.
  • Mailto links. The simplest option and the worst one. A mailto: link opens the visitor's local email client (if they have one configured), exposes your raw email address to every spam scraper crawling your site, and gives you zero delivery tracking or spam filtering.

An HTML card with a hosted form backend gets you theme-matched styling, spam filtering, and a dashboard — without any of those trade-offs.

Troubleshooting Ghost-specific gotchas

  • Form HTML renders as visible text instead of a form. The card is set to Markdown, not HTML. Delete the card, type /html to insert a fresh HTML card, and re-paste the snippet.
  • splitforms returns 401 Unauthorized. Usually a typo or trailing space in YOUR_ACCESS_KEY. Re-copy the key from your dashboard and paste it exactly, or remove any domain restriction on the key while testing.
  • Email never arrives but the dashboard shows the submission. Check spam/junk first. If it's still missing, open the dashboard's deliverability page and send a test email — full diagnostic in contact form not working.
  • The form looks unstyled or oversized. Some Ghost themes apply strong default spacing to raw <input> and <button> elements inside content. The inline .sf-ghost-form styles in the snippet above should override that, but if not, add !important to the conflicting rule or scope your theme's override with a more specific selector.
  • The honeypot is blocking real visitors. A password manager auto-filled the hidden botcheck field. The snippet already includes autocomplete="off" and tabindex="-1", which stops most managers from touching it — if it still happens, rename the field and update the honeypot config in your dashboard.

Still stuck? /docs has the full request contract, and /faq covers plan and security questions.

Next steps

FAQ

Does Ghost have a built-in contact form?

No. Ghost ships a members/subscription portal for paid and free sign-ups, but nothing for one-off reader messages, advertising inquiries, or bug reports. There's also no plugin marketplace like WordPress to install one from. The standard fix is pasting an HTML form into an HTML card — no theme edit, no code repository access required.

Does this work on Ghost(Pro) and self-hosted Ghost the same way?

Yes, identically. The form is plain HTML sitting inside an HTML card in your page or post content — it posts straight from the visitor's browser to splitforms.com, so it never touches Ghost's server, theme files, or config. Whether Ghost(Pro) hosts your site or you self-host on your own server, the HTML card behaves the same way.

How do I stop spam on a Ghost contact form?

The snippet below includes two built-in traps: a botcheck honeypot checkbox that must stay empty (bots fill every field; real visitors never see it, since it's hidden with CSS) and a form_loaded_at timestamp that flags submissions sent faster than a human could type. splitforms also rate-limits and runs server-side spam heuristics on every plan, so most Ghost sites never need a CAPTCHA.

Will the form match my Ghost theme's styling?

Yes, by default. An HTML card renders inside your theme's content area, so an unstyled input, label, or button inherits your theme's body font, text color, and spacing automatically. The snippet below adds a handful of minimal inline styles (padding, border, border-radius) so the form looks intentional without fighting your theme's CSS.

Is a contact form for Ghost really free?

Yes. splitforms' Free plan covers unlimited forms, 500 submissions/month, spam filtering, the dashboard, and instant email notifications — no credit card. If your Ghost site outgrows that, Pro is $5/month for 5,000 submissions plus signed webhooks and 13 integrations, Pro is $5/month for 5,000, and a 3-Year plan is a one-time $59.

What's the difference between this contact form and Ghost's members/newsletter signup?

They do different jobs. Ghost's Portal handles subscriptions — visitors become free or paid members and receive your posts by email. A contact form lets a reader, advertiser, or collaborator send you a one-off message without becoming a member. You can run both side by side; neither replaces the other.

Can I add the same form to more than one Ghost page?

Yes. Paste the same HTML card into your Contact page, your About page, or a footer snippet block — every instance posts to the same access key, so all submissions land in one dashboard. If you want separate stats per page, generate a second access key in the dashboard and swap it into the second form's hidden field.

Will an HTML-card form survive a Ghost theme change or version upgrade?

Yes. Because the form is content — stored in the page or post body, not the theme — switching themes or updating Ghost core doesn't touch it. Compare that to a custom theme partial or a modified template file, which a theme switch would wipe out entirely.

Related articles

More practical guidance from tutorials.

Browse the journal →
Tutorials

FormData in JavaScript: Submit Forms with fetch (2026 Guide)

The complete FormData guide: reading form fields, appending files, sending multipart and url

9 min readRead →
Tutorials

Custom Auto-Responder Emails for HTML Forms (HTML + CSS, 2026)

Design fully branded auto-responder emails for your HTML forms: paste your own HTML/CSS temp

9 min readRead →
Tutorials

Send Form Emails From Your Own Domain (Custom SMTP, 2026)

Send form notification and auto-responder emails from your own domain with custom SMTP: encr

10 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 submissions a month for free.

Create free accountRead the docs →
Secure checkoutSSL encryptionPrivacyProtected
VISAAMERICANEXPRESSstripe