Available for new projects
Follow me:
About Services Work Cases Pricing Book a Call
No App Needed

Add Custom Fields to the Shopify Contact Form Without an App

One Liquid section gives you extra fields, dropdowns, checkboxes and conditional logic, all editable from the theme editor. No app, no monthly fee, and it works on Dawn, Horizon and any Online Store 2.0 theme.

Updated 14 September 2026 8 minute read Copy and paste, 584 lines

Why the default form runs out of road

Shopify's built in contact form collects a name, an email, a phone number and a message. That holds up right until enquiries actually start arriving, at which point every message turns into a guessing game. Someone writes "my order is wrong" and you burn three emails working out which order they mean. If you sell made to order or personalised products it gets worse, because the details you need in order to quote anything are exactly the details the form never asks for.

The usual response is to install a form builder app. Those apps work, and if you need file uploads or a searchable database of submissions you should probably pay for one. But if all you want is a few extra fields, a dropdown and some conditional logic, Shopify already does that. You would be paying a recurring fee for something the platform hands you for free.

The one behaviour this is built on

Here is the part most guides skip. Any input inside a Shopify contact form named contact[Anything You Want] is included in the notification email automatically. You do not register the field anywhere first. Shopify does not need to know it exists. Name it correctly and it turns up in your inbox.

That is it. That single behaviour is what every technique on this page rests on, and it is why you do not need an app to ask better questions.

Naming rules worth knowing

Whatever you put between the brackets becomes the label in your email, so write it the way you want to read it at 9am: contact[Order number], not contact[ord_no]. Keep labels unique inside one form, since two fields with the same name will collide. And remember contact[email] is special: Shopify uses it as the reply-to address on the message you receive, so never remove it.

What you get after pasting it once

This matters more than the code itself, because it is the difference between a snippet you regret and something your team can live with.

Every field is a block. You add, rename, reorder and delete fields from the theme editor exactly like any other section. Colours, padding, border width, corner radius, font sizes, button styling and field spacing all live in the settings panel. After the initial paste, nobody needs to open Liquid again to change the form.

There are seven block types:

  • Name, Email, Phone and Message, which map to Shopify's built in fields so submissions are attributed correctly
  • Custom text field, Custom dropdown and Custom checkbox, which automatically become contact[Your Label]

Every block has a label, a required toggle, and a half or full width setting. The three custom types also get a "Show only when" pair of settings, which is how you get conditional fields without touching JavaScript.

The code

  1. In your Shopify admin go to Online Store, then Themes.
  2. Click the three dots next to your theme, then Edit code.
  3. Under the Sections folder click Add a new section.
  4. Name it custom-contact-form, delete the placeholder content Shopify generates, and paste the code below.
sections/custom-contact-form.liquid 584 lines
{% comment %}
  Custom contact form
  Works on Dawn, Horizon and any Online Store 2.0 theme
{% endcomment %}

{%- liquid
  assign s = section.settings
  assign ccf_form_id = 'ccf-form-' | append: section.id

  assign c_section_bg = s.section_bg | default: 'transparent'
  assign c_form_bg = s.form_bg | default: 'transparent'
  assign c_form_border = s.form_border_color | default: 'transparent'
  assign c_heading = s.heading_color | default: 'inherit'
  assign c_label = s.label_color | default: 'inherit'
  assign c_field_bg = s.field_bg | default: 'transparent'
  assign c_field_text = s.field_text | default: 'inherit'
  assign c_placeholder = s.field_placeholder_color | default: 'inherit'
  assign c_field_border = s.field_border_color | default: '#d9d9d9'
  assign c_field_focus = s.field_border_focus | default: '#121212'
  assign c_btn_bg = s.button_bg | default: '#121212'
  assign c_btn_text = s.button_text | default: '#ffffff'
  assign c_btn_bg_hover = s.button_bg_hover | default: c_btn_bg
  assign c_btn_text_hover = s.button_text_hover | default: c_btn_text
  assign c_success_text = s.success_text | default: 'inherit'
  assign c_success_bg = s.success_bg | default: 'transparent'
  assign c_error_text = s.error_text | default: '#b00020'
  assign c_error_bg = s.error_bg | default: 'transparent'
-%}

<div
  class="ccf"
  id="ccf-{{ section.id }}"
  style="background: {{ c_section_bg }}; padding: {{ s.padding_top }}px {{ s.padding_horizontal }}px {{ s.padding_bottom }}px;">

  <div class="ccf__inner">

    {%- if s.heading != blank -%}
      <h2 class="ccf__heading">{{ s.heading }}</h2>
    {%- endif -%}

    {% form 'contact', id: ccf_form_id %}

      {%- if form.posted_successfully? -%}
        <p class="ccf__success">{{ s.success_message }}</p>
      {%- endif -%}

      {%- if form.errors -%}
        <ul class="ccf__errors">
          {%- for field in form.errors -%}
            <li>{{ form.errors.translated_fields[field] }} {{ form.errors.messages[field] }}</li>
          {%- endfor -%}
        </ul>
      {%- endif -%}

      <div class="ccf__grid">
        {%- for block in section.blocks -%}

          {%- liquid
            assign label = block.settings.label | default: 'Field'
            assign uid = 'ccf-' | append: section.id | append: '-' | append: block.id

            case block.type
              when 'name'
                assign field_name = 'contact[name]'
              when 'email'
                assign field_name = 'contact[email]'
              when 'phone'
                assign field_name = 'contact[phone]'
              when 'message'
                assign field_name = 'contact[body]'
              else
                assign field_name = 'contact[' | append: label | append: ']'
            endcase

            assign is_conditional = false
            if block.settings.show_when_field != blank and block.settings.show_when_value != blank
              assign is_conditional = true
            endif

            assign cond_attr = ''
            if is_conditional
              assign cond_attr = 'disabled'
            endif

            assign width_class = block.settings.width | default: 'full'
          -%}

          <div
            class="ccf__field ccf__field--{{ width_class }}"
            {% if is_conditional %}data-ccf-cond data-ccf-parent="{{ block.settings.show_when_field | escape }}" data-ccf-value="{{ block.settings.show_when_value | escape }}" hidden{% endif %}
            {{ block.shopify_attributes }}
          >

            {%- case block.type -%}

              {%- when 'message' -%}
                {%- if s.show_labels -%}<label for="{{ uid }}">{{ label }}</label>{%- endif -%}
                <textarea
                  id="{{ uid }}"
                  name="{{ field_name }}"
                  rows="{{ block.settings.rows }}"
                  placeholder="{{ block.settings.placeholder | escape }}"
                  data-ccf-name="{{ label | escape }}"
                  {{ cond_attr }}
                  {% if block.settings.required %}required{% endif %}>{{ form.body }}</textarea>

              {%- when 'select' -%}
                {%- if s.show_labels -%}<label for="{{ uid }}">{{ label }}</label>{%- endif -%}
                <select
                  id="{{ uid }}"
                  name="{{ field_name }}"
                  data-ccf-name="{{ label | escape }}"
                  {{ cond_attr }}
                  {% if block.settings.required %}required{% endif %}>
                  {%- if block.settings.placeholder != blank -%}
                    <option value="">{{ block.settings.placeholder }}</option>
                  {%- endif -%}
                  {%- assign ccf_options = block.settings.options | newline_to_br | split: '<br />' -%}
                  {%- for opt in ccf_options -%}
                    {%- assign opt_clean = opt | strip -%}
                    {%- if opt_clean != blank -%}
                      <option value="{{ opt_clean | escape }}">{{ opt_clean }}</option>
                    {%- endif -%}
                  {%- endfor -%}
                </select>

              {%- when 'checkbox' -%}
                <label class="ccf__checkbox" for="{{ uid }}">
                  <input
                    type="checkbox"
                    id="{{ uid }}"
                    name="{{ field_name }}"
                    value="{{ block.settings.checked_value | default: 'Yes' | escape }}"
                    data-ccf-name="{{ label | escape }}"
                    {{ cond_attr }}
                    {% if block.settings.required %}required{% endif %}>
                  <span>{{ label }}</span>
                </label>

              {%- else -%}
                {%- liquid
                  case block.type
                    when 'email'
                      assign input_type = 'email'
                    when 'phone'
                      assign input_type = 'tel'
                    else
                      assign input_type = 'text'
                  endcase

                  assign prefill = ''
                  if block.type == 'name'
                    assign prefill = form.name | default: customer.name
                  elsif block.type == 'email'
                    assign prefill = form.email | default: customer.email
                  elsif block.type == 'phone'
                    assign prefill = form.phone | default: customer.phone
                  endif
                -%}
                {%- if s.show_labels -%}<label for="{{ uid }}">{{ label }}</label>{%- endif -%}
                <input
                  type="{{ input_type }}"
                  id="{{ uid }}"
                  name="{{ field_name }}"
                  value="{{ prefill }}"
                  placeholder="{{ block.settings.placeholder | escape }}"
                  data-ccf-name="{{ label | escape }}"
                  {% if block.type == 'email' %}autocomplete="email"{% endif %}
                  {{ cond_attr }}
                  {% if block.settings.required %}required{% endif %}>

            {%- endcase -%}

          </div>

        {%- endfor -%}
      </div>

      <div class="ccf__actions">
        <button type="submit" class="ccf__submit">{{ s.button_label }}</button>
      </div>

    {% endform %}

  </div>
</div>

<style>
  #ccf-{{ section.id }} {
    --ccf-gap: {{ s.field_gap }}px;

    --ccf-heading-size: {{ s.heading_size }}px;
    --ccf-heading-color: {{ c_heading }};
    --ccf-heading-margin: {{ s.heading_margin }}px;

    --ccf-label-size: {{ s.label_size }}px;
    --ccf-label-color: {{ c_label }};
    --ccf-label-weight: {{ s.label_weight }};
    --ccf-label-margin: {{ s.label_margin }}px;

    --ccf-field-bg: {{ c_field_bg }};
    --ccf-field-text: {{ c_field_text }};
    --ccf-field-placeholder: {{ c_placeholder }};
    --ccf-field-border: {{ c_field_border }};
    --ccf-field-focus: {{ c_field_focus }};
    --ccf-field-border-width: {{ s.field_border_width }}px;
    --ccf-field-radius: {{ s.field_radius }}px;
    --ccf-field-padding-y: {{ s.field_padding_y }}px;
    --ccf-field-padding-x: {{ s.field_padding_x }}px;
    --ccf-field-size: {{ s.field_font_size }}px;

    --ccf-btn-bg: {{ c_btn_bg }};
    --ccf-btn-text: {{ c_btn_text }};
    --ccf-btn-bg-hover: {{ c_btn_bg_hover }};
    --ccf-btn-text-hover: {{ c_btn_text_hover }};
    --ccf-btn-radius: {{ s.button_radius }}px;
    --ccf-btn-padding-y: {{ s.button_padding_y }}px;
    --ccf-btn-padding-x: {{ s.button_padding_x }}px;
    --ccf-btn-size: {{ s.button_font_size }}px;

    --ccf-msg-radius: {{ s.message_radius }}px;
  }

  #ccf-{{ section.id }} .ccf__inner {
    max-width: {{ s.form_width }}px;
    margin: {% if s.form_align == 'center' %}0 auto{% else %}0{% endif %};
    background: {{ c_form_bg }};
    border: {{ s.form_border_width }}px solid {{ c_form_border }};
    border-radius: {{ s.form_radius }}px;
    padding: {{ s.form_padding }}px;
  }

  #ccf-{{ section.id }} .ccf__heading {
    font-size: var(--ccf-heading-size);
    color: var(--ccf-heading-color);
    text-align: {{ s.heading_align }};
    margin: 0 0 var(--ccf-heading-margin);
  }

  #ccf-{{ section.id }} .ccf__grid {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ccf-gap);
    margin-bottom: var(--ccf-gap);
  }

  #ccf-{{ section.id }} .ccf__field--full { flex: 0 0 100%; }
  #ccf-{{ section.id }} .ccf__field--half { flex: 1 1 calc(50% - (var(--ccf-gap) / 2)); min-width: 220px; }

  #ccf-{{ section.id }} .ccf__field > label {
    display: block;
    font-size: var(--ccf-label-size);
    color: var(--ccf-label-color);
    font-weight: var(--ccf-label-weight);
    margin-bottom: var(--ccf-label-margin);
  }

  #ccf-{{ section.id }} .ccf__field input[type="text"],
  #ccf-{{ section.id }} .ccf__field input[type="email"],
  #ccf-{{ section.id }} .ccf__field input[type="tel"],
  #ccf-{{ section.id }} .ccf__field select,
  #ccf-{{ section.id }} .ccf__field textarea {
    width: 100%;
    box-sizing: border-box;
    padding: var(--ccf-field-padding-y) var(--ccf-field-padding-x);
    background: var(--ccf-field-bg);
    color: var(--ccf-field-text);
    border: var(--ccf-field-border-width) solid var(--ccf-field-border);
    border-radius: var(--ccf-field-radius);
    font-size: var(--ccf-field-size);
    font-family: inherit;
    transition: border-color 0.15s ease;
  }

  #ccf-{{ section.id }} .ccf__field input::placeholder,
  #ccf-{{ section.id }} .ccf__field textarea::placeholder {
    color: var(--ccf-field-placeholder);
    opacity: 0.7;
  }

  #ccf-{{ section.id }} .ccf__field input:focus,
  #ccf-{{ section.id }} .ccf__field select:focus,
  #ccf-{{ section.id }} .ccf__field textarea:focus {
    outline: none;
    border-color: var(--ccf-field-focus);
  }

  #ccf-{{ section.id }} .ccf__checkbox {
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    font-size: var(--ccf-label-size);
    color: var(--ccf-label-color);
  }
  #ccf-{{ section.id }} .ccf__checkbox input { width: auto; margin: 0; }

  #ccf-{{ section.id }} .ccf__actions { text-align: {{ s.button_align }}; }

  #ccf-{{ section.id }} .ccf__submit {
    {% if s.button_full_width %}width: 100%;{% endif %}
    padding: var(--ccf-btn-padding-y) var(--ccf-btn-padding-x);
    background: var(--ccf-btn-bg);
    color: var(--ccf-btn-text);
    border: none;
    border-radius: var(--ccf-btn-radius);
    font-size: var(--ccf-btn-size);
    font-family: inherit;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
  }

  #ccf-{{ section.id }} .ccf__submit:hover {
    background: var(--ccf-btn-bg-hover);
    color: var(--ccf-btn-text-hover);
  }

  #ccf-{{ section.id }} .ccf__success {
    padding: 12px;
    margin: 0 0 var(--ccf-gap);
    color: {{ c_success_text }};
    background: {{ c_success_bg }};
    border-radius: var(--ccf-msg-radius);
  }

  #ccf-{{ section.id }} .ccf__errors {
    padding: 12px 12px 12px 32px;
    margin: 0 0 var(--ccf-gap);
    color: {{ c_error_text }};
    background: {{ c_error_bg }};
    border-radius: var(--ccf-msg-radius);
  }

  #ccf-{{ section.id }} [hidden] { display: none !important; }
</style>

<script>
  (function () {
    var root = document.getElementById('ccf-{{ section.id }}');
    if (!root) return;

    var conds = root.querySelectorAll('[data-ccf-cond]');
    if (!conds.length) return;

    function currentValue(name) {
      var el = root.querySelector('[data-ccf-name="' + name + '"]');
      if (!el) return null;
      if (el.type === 'checkbox') return el.checked ? el.value : '';
      return el.value;
    }

    function apply() {
      Array.prototype.forEach.call(conds, function (wrap) {
        var parent = wrap.getAttribute('data-ccf-parent');
        var wants = (wrap.getAttribute('data-ccf-value') || '').split(',').map(function (v) {
          return v.trim();
        });
        var now = currentValue(parent);
        var show = now !== null && wants.indexOf(now) !== -1;

        wrap.hidden = !show;

        Array.prototype.forEach.call(wrap.querySelectorAll('input, select, textarea'), function (el) {
          el.disabled = !show;
          if (!show) {
            if (el.type === 'checkbox') {
              el.checked = false;
            } else {
              el.value = '';
            }
          }
        });
      });
    }

    root.addEventListener('change', apply);
    apply();
  })();
</script>

{% schema %}
{
  "name": "Custom contact form",
  "max_blocks": 20,
  "settings": [
    { "type": "header", "content": "Layout" },
    { "type": "range", "id": "form_width", "min": 320, "max": 1200, "step": 20, "unit": "px", "label": "Form width", "default": 640 },
    {
      "type": "select", "id": "form_align", "label": "Form position", "default": "center",
      "options": [{ "value": "left", "label": "Left" }, { "value": "center", "label": "Centre" }]
    },
    { "type": "range", "id": "field_gap", "min": 0, "max": 40, "step": 2, "unit": "px", "label": "Space between fields", "default": 16 },
    { "type": "range", "id": "padding_top", "min": 0, "max": 120, "step": 4, "unit": "px", "label": "Padding top", "default": 40 },
    { "type": "range", "id": "padding_bottom", "min": 0, "max": 120, "step": 4, "unit": "px", "label": "Padding bottom", "default": 40 },
    { "type": "range", "id": "padding_horizontal", "min": 0, "max": 80, "step": 4, "unit": "px", "label": "Padding left and right", "default": 20 },

    { "type": "header", "content": "Background" },
    { "type": "color", "id": "section_bg", "label": "Section background" },
    { "type": "color", "id": "form_bg", "label": "Form card background" },
    { "type": "range", "id": "form_padding", "min": 0, "max": 60, "step": 2, "unit": "px", "label": "Form card padding", "default": 0 },
    { "type": "color", "id": "form_border_color", "label": "Form card border" },
    { "type": "range", "id": "form_border_width", "min": 0, "max": 6, "step": 1, "unit": "px", "label": "Form card border width", "default": 0 },
    { "type": "range", "id": "form_radius", "min": 0, "max": 40, "step": 2, "unit": "px", "label": "Form card corner radius", "default": 0 },

    { "type": "header", "content": "Heading" },
    { "type": "text", "id": "heading", "label": "Heading", "default": "Get in touch" },
    { "type": "range", "id": "heading_size", "min": 16, "max": 56, "step": 1, "unit": "px", "label": "Heading size", "default": 28 },
    { "type": "color", "id": "heading_color", "label": "Heading colour" },
    {
      "type": "select", "id": "heading_align", "label": "Heading alignment", "default": "left",
      "options": [{ "value": "left", "label": "Left" }, { "value": "center", "label": "Centre" }, { "value": "right", "label": "Right" }]
    },
    { "type": "range", "id": "heading_margin", "min": 0, "max": 60, "step": 2, "unit": "px", "label": "Space below heading", "default": 20 },

    { "type": "header", "content": "Labels" },
    { "type": "checkbox", "id": "show_labels", "label": "Show field labels", "default": true },
    { "type": "range", "id": "label_size", "min": 10, "max": 22, "step": 1, "unit": "px", "label": "Label size", "default": 14 },
    { "type": "color", "id": "label_color", "label": "Label colour" },
    {
      "type": "select", "id": "label_weight", "label": "Label weight", "default": "500",
      "options": [{ "value": "400", "label": "Regular" }, { "value": "500", "label": "Medium" }, { "value": "600", "label": "Semibold" }, { "value": "700", "label": "Bold" }]
    },
    { "type": "range", "id": "label_margin", "min": 0, "max": 20, "step": 1, "unit": "px", "label": "Space below label", "default": 6 },

    { "type": "header", "content": "Fields" },
    { "type": "color", "id": "field_bg", "label": "Field background" },
    { "type": "color", "id": "field_text", "label": "Field text colour" },
    { "type": "color", "id": "field_placeholder_color", "label": "Placeholder colour" },
    { "type": "color", "id": "field_border_color", "label": "Field border", "default": "#d9d9d9" },
    { "type": "color", "id": "field_border_focus", "label": "Field border when focused", "default": "#121212" },
    { "type": "range", "id": "field_border_width", "min": 0, "max": 4, "step": 1, "unit": "px", "label": "Field border width", "default": 1 },
    { "type": "range", "id": "field_radius", "min": 0, "max": 40, "step": 2, "unit": "px", "label": "Field corner radius", "default": 4 },
    { "type": "range", "id": "field_padding_y", "min": 4, "max": 28, "step": 1, "unit": "px", "label": "Field padding top and bottom", "default": 12 },
    { "type": "range", "id": "field_padding_x", "min": 4, "max": 28, "step": 1, "unit": "px", "label": "Field padding left and right", "default": 12 },
    { "type": "range", "id": "field_font_size", "min": 12, "max": 20, "step": 1, "unit": "px", "label": "Field text size", "default": 16 },

    { "type": "header", "content": "Button" },
    { "type": "text", "id": "button_label", "label": "Button label", "default": "Send message" },
    { "type": "color", "id": "button_bg", "label": "Button background", "default": "#121212" },
    { "type": "color", "id": "button_text", "label": "Button text", "default": "#ffffff" },
    { "type": "color", "id": "button_bg_hover", "label": "Button background on hover" },
    { "type": "color", "id": "button_text_hover", "label": "Button text on hover" },
    { "type": "range", "id": "button_radius", "min": 0, "max": 50, "step": 2, "unit": "px", "label": "Button corner radius", "default": 4 },
    { "type": "range", "id": "button_padding_y", "min": 6, "max": 30, "step": 1, "unit": "px", "label": "Button padding top and bottom", "default": 13 },
    { "type": "range", "id": "button_padding_x", "min": 10, "max": 80, "step": 2, "unit": "px", "label": "Button padding left and right", "default": 28 },
    { "type": "range", "id": "button_font_size", "min": 12, "max": 22, "step": 1, "unit": "px", "label": "Button text size", "default": 16 },
    { "type": "checkbox", "id": "button_full_width", "label": "Full width button", "default": false },
    {
      "type": "select", "id": "button_align", "label": "Button alignment", "default": "left",
      "options": [{ "value": "left", "label": "Left" }, { "value": "center", "label": "Centre" }, { "value": "right", "label": "Right" }]
    },

    { "type": "header", "content": "Messages" },
    { "type": "text", "id": "success_message", "label": "Success message", "default": "Thanks, we got your message. We will get back to you shortly." },
    { "type": "color", "id": "success_text", "label": "Success text colour" },
    { "type": "color", "id": "success_bg", "label": "Success background" },
    { "type": "color", "id": "error_text", "label": "Error text colour", "default": "#b00020" },
    { "type": "color", "id": "error_bg", "label": "Error background" },
    { "type": "range", "id": "message_radius", "min": 0, "max": 30, "step": 2, "unit": "px", "label": "Message corner radius", "default": 4 }
  ],
  "blocks": [
    {
      "type": "name",
      "name": "Name field",
      "limit": 1,
      "settings": [
        { "type": "text", "id": "label", "label": "Label", "default": "Name" },
        { "type": "text", "id": "placeholder", "label": "Placeholder" },
        { "type": "checkbox", "id": "required", "label": "Required", "default": true },
        {
          "type": "select", "id": "width", "label": "Width", "default": "half",
          "options": [{ "value": "half", "label": "Half" }, { "value": "full", "label": "Full" }]
        }
      ]
    },
    {
      "type": "email",
      "name": "Email field",
      "limit": 1,
      "settings": [
        { "type": "paragraph", "content": "Keep this one. Shopify uses it as the reply-to address on the email you receive." },
        { "type": "text", "id": "label", "label": "Label", "default": "Email" },
        { "type": "text", "id": "placeholder", "label": "Placeholder" },
        { "type": "checkbox", "id": "required", "label": "Required", "default": true },
        {
          "type": "select", "id": "width", "label": "Width", "default": "half",
          "options": [{ "value": "half", "label": "Half" }, { "value": "full", "label": "Full" }]
        }
      ]
    },
    {
      "type": "phone",
      "name": "Phone field",
      "limit": 1,
      "settings": [
        { "type": "text", "id": "label", "label": "Label", "default": "Phone" },
        { "type": "text", "id": "placeholder", "label": "Placeholder" },
        { "type": "checkbox", "id": "required", "label": "Required", "default": false },
        {
          "type": "select", "id": "width", "label": "Width", "default": "half",
          "options": [{ "value": "half", "label": "Half" }, { "value": "full", "label": "Full" }]
        }
      ]
    },
    {
      "type": "message",
      "name": "Message field",
      "limit": 1,
      "settings": [
        { "type": "text", "id": "label", "label": "Label", "default": "Message" },
        { "type": "text", "id": "placeholder", "label": "Placeholder" },
        { "type": "range", "id": "rows", "min": 3, "max": 12, "step": 1, "label": "Rows", "default": 6 },
        { "type": "checkbox", "id": "required", "label": "Required", "default": true },
        {
          "type": "select", "id": "width", "label": "Width", "default": "full",
          "options": [{ "value": "half", "label": "Half" }, { "value": "full", "label": "Full" }]
        }
      ]
    },
    {
      "type": "text",
      "name": "Custom text field",
      "settings": [
        { "type": "paragraph", "content": "The label is what shows up in your notification email, so name it something you will recognise." },
        { "type": "text", "id": "label", "label": "Label", "default": "Order number" },
        { "type": "text", "id": "placeholder", "label": "Placeholder" },
        { "type": "checkbox", "id": "required", "label": "Required", "default": false },
        {
          "type": "select", "id": "width", "label": "Width", "default": "full",
          "options": [{ "value": "half", "label": "Half" }, { "value": "full", "label": "Full" }]
        },
        { "type": "header", "content": "Show only when" },
        { "type": "text", "id": "show_when_field", "label": "Label of the controlling field", "info": "Leave blank to always show. Type the exact label of a dropdown or checkbox in this same form." },
        { "type": "text", "id": "show_when_value", "label": "Value that reveals this field", "info": "Separate several values with commas." }
      ]
    },
    {
      "type": "select",
      "name": "Custom dropdown",
      "settings": [
        { "type": "text", "id": "label", "label": "Label", "default": "What is this about?" },
        { "type": "text", "id": "placeholder", "label": "First empty option", "default": "Please choose" },
        { "type": "textarea", "id": "options", "label": "Options", "info": "One per line.", "default": "Problem with an order\nQuestion before buying\nReturns or exchange\nWholesale enquiry\nSomething else" },
        { "type": "checkbox", "id": "required", "label": "Required", "default": true },
        {
          "type": "select", "id": "width", "label": "Width", "default": "full",
          "options": [{ "value": "half", "label": "Half" }, { "value": "full", "label": "Full" }]
        },
        { "type": "header", "content": "Show only when" },
        { "type": "text", "id": "show_when_field", "label": "Label of the controlling field" },
        { "type": "text", "id": "show_when_value", "label": "Value that reveals this field" }
      ]
    },
    {
      "type": "checkbox",
      "name": "Custom checkbox",
      "settings": [
        { "type": "text", "id": "label", "label": "Label", "default": "Sign me up for the newsletter" },
        { "type": "text", "id": "checked_value", "label": "Value when ticked", "default": "Yes" },
        { "type": "checkbox", "id": "required", "label": "Required", "default": false },
        {
          "type": "select", "id": "width", "label": "Width", "default": "full",
          "options": [{ "value": "half", "label": "Half" }, { "value": "full", "label": "Full" }]
        },
        { "type": "header", "content": "Show only when" },
        { "type": "text", "id": "show_when_field", "label": "Label of the controlling field" },
        { "type": "text", "id": "show_when_value", "label": "Value that reveals this field" }
      ]
    }
  ],
  "presets": [
    {
      "name": "Custom contact form",
      "blocks": [
        { "type": "name" },
        { "type": "email" },
        { "type": "select" },
        { "type": "message" }
      ]
    }
  ]
}
{% endschema %}

It is a long file, but you paste it once. Everything after this is done from the theme editor.

Add it to a page

Save the file, then go to Customize, open the page you want the form on, click Add section and pick Custom contact form. It drops in preconfigured with a name field, an email field, a dropdown and a message box, so you can see it working before you change anything.

Where submissions actually go

This trips up more people than the code does. Submissions are sent to your Sender email, found under Settings then Notifications. That is the only field that controls it, and no amount of Liquid will override it.

The Store contact email under Store details is a completely different setting. People check that one, see the correct address sitting there, and cannot work out why nothing is arriving. You may also find older forum answers pointing at a "Recipient email" field under Notifications. That field does not exist.

Conditional fields in twenty seconds

Say you only want to ask for an order number when someone selects "Problem with an order".

  1. Add a Custom dropdown block and label it What is this about?, keeping the default options.
  2. Add a Custom text field block and label it Order number.
  3. In that text block, under Show only when, type What is this about? in the first box and Problem with an order in the second.

For multiple triggers, separate the values with commas. The matching runs against the label text, so if you rename the controlling dropdown later, remember to update this too. Hidden fields are also disabled, which means they are left out of the submission entirely rather than arriving empty.

Why file uploads cannot work

This one wastes an afternoon

You cannot add file uploads to the native Shopify contact form. Not with clever Liquid, not with JavaScript. You will find posts suggesting you add enctype="multipart/form-data" and a file input. It appears to work: the form submits, the success message shows, and the file simply never arrives.

Shopify's backend has nowhere to store an attachment so it discards it silently, which is the cruel part. Merchants ship it and find out weeks later that customers have been uploading artwork into nothing.

If you genuinely need files, there are three real options, in the order they are usually right:

  • A form builder app. The monthly fee is essentially you renting their backend, which is a fair trade when you need one.
  • A plain HTML form without the Liquid form tag, pointed at your own endpoint that stores the file and emails you a link.
  • A text field asking for a Drive or Dropbox link. It sounds lazy. In practice it works better than you would expect, and it costs nothing.

When an app is still the right call

To be straight about the limits of this approach, install an app if you need file uploads, a searchable database of past submissions, multi step forms with saved progress, payment collection inside the form, or automatic CRM syncing.

This section covers the case where you just need better questions asked at the point of enquiry, which is most stores.

If something looks wrong

  • The section does not appear in the theme editor. The file has to sit in the Sections folder and end in .liquid. If Shopify rejected the save, the schema at the bottom usually has a stray character in it.
  • Nothing arrives in your inbox. Check the Sender email under Settings then Notifications, then check spam. Shopify sends from its own servers, so early submissions sometimes get filtered.
  • A custom field is missing from the email. It was probably left blank, or the field was hidden by a "Show only when" rule at the moment of submitting. Blank fields are not included.
  • A conditional field never appears. The controlling label has to match exactly, including capitals and the question mark. Renaming the dropdown breaks the link until you update the rule.
  • The form looks unstyled. Everything is controlled in the section settings, which start deliberately neutral so the form inherits your theme rather than fighting it. Set the field and button colours there.

Questions people ask

Does this work on Dawn and Horizon?

Yes. It is a standard section file, so it works on Dawn, Horizon and any other Online Store 2.0 theme that supports sections.

Will it survive a theme update?

The file is yours, not part of the theme, so updating the theme does not touch it. If you install a brand new copy of a theme rather than updating in place, you will need to add the section file to that copy and rebuild the blocks in the editor.

Can I use it on more than one page?

Yes. Add the section to as many pages as you like, each with its own fields. Every instance is scoped by its section ID, so the styling and conditional logic of one form cannot affect another.

Do submissions get stored anywhere in Shopify?

No. The native form emails you and keeps nothing. If you need a record you can search later, that is one of the clearest reasons to pay for a form app.

I first wrote this up as a thread on the Shopify Community after answering the same question in a dozen separate posts. If you want to ask something about your specific theme, reply on the original thread and I will take a look.

If your form needs go past what this covers, or you would rather not touch theme code at all, I build custom Liquid sections for exactly this sort of thing.

On this page
  1. Why the default form runs out of road
  2. The one behaviour this is built on
  3. What you get after pasting it once
  4. The code
  5. Add it to a page
  6. Where submissions actually go
  7. Conditional fields in twenty seconds
  8. Why file uploads cannot work
  9. When an app is still the right call
  10. If something looks wrong
  11. Questions people ask

Need a Form That Does More Than This?

Multi step logic, custom endpoints, CRM syncing, or a form built around how your store actually sells. Tell me what it needs to do.

Get a Quote