Comparison

Shopify contact form Liquid code

Two sections for any Online Store 2.0 theme (Dawn, Refresh, Sense and the rest). Once the file is in your theme, you add it in the theme editor like a built-in section and type the access key into its settings. No app needed.

Short answer: In Shopify admin open Online Store › Themes › … › Edit code, add a new section called formgong-contact, paste the code, then add the section to a page in the theme editor and paste your access key into its settings.

Why not Shopify's own contact form?

Shopify's {% form 'contact' %} sends each message to the store's sender email. "Shopify contact form not receiving email" is one of Google's top suggestions for this search, which says how often that goes wrong. A Formgong section stores every message in an inbox, emails it, and can also send it to Telegram, Slack or a Google Sheet. Keep Shopify's form if it works for you. These sections are for when you want the messages somewhere else, or want a product question form that says which product it came from.

Contact form section

In the code editor, under Sections, choose "Add a new section", name it formgong-contact, replace the generated code with this and save. The section uses your theme's button classes and colour variables, so it matches the store.

sections/formgong-contact.liquid
<style>
  .formgong-form { position: relative; display: grid; gap: 1.2rem; max-width: 60rem; margin: 0 auto; }
  .formgong-form label { display: grid; gap: 0.4rem; }
  .formgong-form input, .formgong-form textarea { font: inherit; padding: 1rem 1.2rem; border: 1px solid rgba(var(--color-foreground), 0.55); border-radius: var(--inputs-radius, 0); background: rgb(var(--color-background)); color: rgb(var(--color-foreground)); }
  .formgong-form button { justify-self: start; }
</style>

<div class="page-width section-{{ section.id }}-padding">
  {%- if section.settings.heading != blank -%}
    <h2 class="h2">{{ section.settings.heading }}</h2>
  {%- endif -%}
  <form id="FormgongForm-{{ section.id }}" class="formgong-form" action="https://formgong.com/submit" method="POST">
    <input type="hidden" name="access_key" value="{{ section.settings.access_key }}">
    <input type="hidden" name="_subject" value="Contact form: {{ shop.name }}">
    <label>Name <input type="text" name="name" autocomplete="name" required></label>
    <label>Email <input type="email" name="email" autocomplete="email" required></label>
    <label>Message <textarea name="message" rows="5" required></textarea></label>
    <div aria-hidden="true" style="position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
      <input type="text" name="botcheck" tabindex="-1" autocomplete="off">
    </div>
    <button type="submit" class="button">{{ section.settings.button_label }}</button>
    <p data-status role="status"></p>
  </form>
</div>

<script>
  document.getElementById("FormgongForm-{{ section.id }}").addEventListener("submit", async (event) => {
    event.preventDefault();
    const form = event.currentTarget;
    const button = form.querySelector("button[type=submit]");
    const status = form.querySelector("[data-status]");
    button.disabled = true;
    status.textContent = "";
    try {
      const response = await fetch(form.action, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(form),
      });
      const result = await response.json();
      if (result.success === true) {
        form.reset();
        status.textContent = {{ section.settings.success_text | json }};
      } else {
        status.textContent = result.message || "Not sent. Please try again.";
      }
    } catch {
      status.textContent = "Not sent: check your connection and try again.";
    }
    button.disabled = false;
  });
</script>

{% schema %}
{
  "name": "Formgong contact form",
  "tag": "section",
  "class": "section",
  "settings": [
    { "type": "text", "id": "access_key", "label": "Formgong access key", "info": "Starts with fk_. Copy it from your form in Formgong." },
    { "type": "text", "id": "heading", "label": "Heading", "default": "Contact us" },
    { "type": "text", "id": "button_label", "label": "Button label", "default": "Send" },
    { "type": "text", "id": "success_text", "label": "Message after sending", "default": "Thanks! Your message has been sent." },
    { "type": "range", "id": "padding_top", "label": "Top padding", "min": 0, "max": 100, "step": 4, "unit": "px", "default": 36 },
    { "type": "range", "id": "padding_bottom", "label": "Bottom padding", "min": 0, "max": 100, "step": 4, "unit": "px", "default": 36 }
  ],
  "presets": [{ "name": "Formgong contact form" }]
}
{% endschema %}

Product question section

This one only appears on product templates. It sends the product title and URL along with the question, so you know what the customer was looking at. It sits in a <details> element and stays closed until someone needs it.

sections/formgong-product-question.liquid
<style>
  .formgong-form { position: relative; display: grid; gap: 1.2rem; max-width: 60rem; margin: 0 auto; }
  .formgong-form label { display: grid; gap: 0.4rem; }
  .formgong-form input, .formgong-form textarea { font: inherit; padding: 1rem 1.2rem; border: 1px solid rgba(var(--color-foreground), 0.55); border-radius: var(--inputs-radius, 0); background: rgb(var(--color-background)); color: rgb(var(--color-foreground)); }
  .formgong-form button { justify-self: start; }
</style>

<div class="page-width section-{{ section.id }}-padding">
  <details class="formgong-question">
    <summary class="h4">{{ section.settings.heading }}</summary>
    <form id="FormgongForm-{{ section.id }}" class="formgong-form" action="https://formgong.com/submit" method="POST">
      <input type="hidden" name="access_key" value="{{ section.settings.access_key }}">
      <input type="hidden" name="_subject" value="Question about {{ product.title | escape }}">
      <input type="hidden" name="product" value="{{ product.title | escape }}">
      <input type="hidden" name="product_url" value="{{ shop.url }}{{ product.url }}">
      <label>Your question <textarea name="message" rows="4" required></textarea></label>
      <label>Email for the answer <input type="email" name="email" autocomplete="email" required></label>
      <div aria-hidden="true" style="position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
        <input type="text" name="botcheck" tabindex="-1" autocomplete="off">
      </div>
      <button type="submit" class="button button--secondary">Send question</button>
      <p data-status role="status"></p>
    </form>
  </details>
</div>

<script>
  document.getElementById("FormgongForm-{{ section.id }}").addEventListener("submit", async (event) => {
    event.preventDefault();
    const form = event.currentTarget;
    const button = form.querySelector("button[type=submit]");
    const status = form.querySelector("[data-status]");
    button.disabled = true;
    status.textContent = "";
    try {
      const response = await fetch(form.action, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(form),
      });
      const result = await response.json();
      if (result.success === true) {
        form.reset();
        status.textContent = {{ section.settings.success_text | json }};
      } else {
        status.textContent = result.message || "Not sent. Please try again.";
      }
    } catch {
      status.textContent = "Not sent: check your connection and try again.";
    }
    button.disabled = false;
  });
</script>

{% schema %}
{
  "name": "Formgong product question",
  "tag": "section",
  "class": "section",
  "enabled_on": { "templates": ["product"] },
  "settings": [
    { "type": "text", "id": "access_key", "label": "Formgong access key", "info": "Starts with fk_. Copy it from your form in Formgong." },
    { "type": "text", "id": "heading", "label": "Heading", "default": "Ask a question about this product" },
    { "type": "text", "id": "success_text", "label": "Message after sending", "default": "Thanks! We'll answer by email." },
    { "type": "range", "id": "padding_top", "label": "Top padding", "min": 0, "max": 100, "step": 4, "unit": "px", "default": 20 },
    { "type": "range", "id": "padding_bottom", "label": "Bottom padding", "min": 0, "max": 100, "step": 4, "unit": "px", "default": 20 }
  ],
  "presets": [{ "name": "Formgong product question" }]
}
{% endschema %}

Add it to a page

  1. Open Online Store › Themes › Customize.
  2. Pick the page or the product template, then Add section › Formgong contact form (or Formgong product question).
  3. Paste your key, which starts with fk_, into "Formgong access key" and save.
  4. Send a test from the live store and check your Formgong inbox.

Formgong also has a Shopify app with theme blocks for the contact form and reviews, if you would rather not edit theme code.

How we tested this code

We pasted each snippet unchanged into a fresh project (the sections rendered with LiquidJS, which follows Shopify's Liquid, with the default settings from each schema), filled in a real access key, and submitted it in Chromium three times against a local copy of Formgong. With the right key the form showed its success text and exactly one submission was stored, not marked as spam. With a key Formgong does not know, the form showed Formgong's own error, "That access key does not exist.", and nothing was stored. With the request cut off, the form said it was not sent. None of the snippets says "sent" before Formgong answers success: true.

We did not run them inside a real Shopify store for this page, so test once on your own store after you add the section.

Questions

Is it safe to put the access key in front-end code?

Yes. The key only lets someone send a submission to your form, the same thing your page already does. It cannot read submissions or change settings. If you want it out of the page source anyway, add your domain under allowed domains in the form settings, or post through your own server.

Will this survive a theme update?

Custom sections are part of the theme's files. When you install a new theme version, copy the section file over and add it again in the editor.

Does it work on vintage (non-2.0) themes?

Sections with presets need Online Store 2.0. On an older theme, paste the form and script into a page template instead and type the key in place of the Liquid setting.

What is the hidden botcheck field?

A honeypot. It sits off screen, so people never fill it, while simple bots fill every field they find. Formgong marks a submission with it filled in as spam, so it does not reach your email. Keep the name botcheck.

Sources

Checked on 16.10.2026 against these public pages: Shopify: Section schema, Shopify Help Center: Editing theme code, LiquidJS.

← Formgong