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.
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
- In your Shopify admin go to Online Store, then Themes.
- Click the three dots next to your theme, then Edit code.
- Under the Sections folder click Add a new section.
- Name it
custom-contact-form, delete the placeholder content Shopify generates, and paste the code below.
{% 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".
- Add a Custom dropdown block and label it
What is this about?, keeping the default options. - Add a Custom text field block and label it
Order number. - In that text block, under Show only when, type
What is this about?in the first box andProblem with an orderin 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
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.