{"product_id":"2026-04-27","title":"[Horizon] Temporary Fix for Missing Contact Form Success Message","description":"\u003cp\u003eA \u003cstrong\u003etemporary theme-side fix\u003c\/strong\u003e for the bug introduced in Horizon v3.3.1, where the contact form's success message no longer appears after submission. The proper fix should land in Horizon itself, but at the time of writing Shopify is not accepting external PRs against the \u003ca href=\"https:\/\/github.com\/Shopify\/horizon\" target=\"_blank\" rel=\"noopener\"\u003eHorizon repository\u003c\/a\u003e, so this serves as a stopgap until the upstream fix arrives.\u003c\/p\u003e\n\n\u003cp\u003e\u003cimg alt=\"The success message that no longer appears in Horizon v3.3.1 and later\" src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0733\/0381\/8491\/files\/2026-04-27_1_en.png?v=1777184401\" class=\"zoomable-image\" width=\"800\" height=\"600\" style=\"aspect-ratio: 1.33;\"\u003e\u003c\/p\u003e\n\n\u003cdiv class=\"divider\"\u003e\u003c\/div\u003e\n\n\u003ch3\u003eThe symptom\u003c\/h3\u003e\n\u003cp\u003eWith the contact form rendered via \u003ccode\u003eblocks\/contact-form.liquid\u003c\/code\u003e in Horizon v3.3.1 or later, submitting the form successfully shows \u003cstrong\u003eno confirmation message at all\u003c\/strong\u003e — the \"Thank you for your message. We'll be in touch as soon as we can.\" text never appears. The submission itself goes through (it lands in the Shopify Admin \"Inbox \/ Contacts\" area), but the customer is left wondering whether anything happened.\u003c\/p\u003e\n\u003cp\u003ev3.2.1 and earlier displayed the success message correctly. This is a regression introduced by changes shipped in v3.3.1 (details below).\u003c\/p\u003e\n\n\u003cdiv class=\"divider\"\u003e\u003c\/div\u003e\n\n\u003ch3\u003eRoot cause — the \u003ccode\u003eform.id == form_id\u003c\/code\u003e guard added in v3.3.1\u003c\/h3\u003e\n\u003cp\u003eThe diff between v3.2.1 and v3.3.1 introduces two changes to \u003ccode\u003eblocks\/contact-form.liquid\u003c\/code\u003e:\u003c\/p\u003e\n\u003col\u003e\n  \u003cli\u003eA hidden \u003ccode\u003econtact[id]\u003c\/code\u003e input is added so that multiple contact forms can coexist on the same page.\u003c\/li\u003e\n  \u003cli\u003eThe error \/ success branches are now gated by an extra condition that compares the block's own \u003ccode\u003eform_id\u003c\/code\u003e against \u003ccode\u003eform.id\u003c\/code\u003e returned by Shopify — only the matching form should render the message.\u003c\/li\u003e\n\u003c\/ol\u003e\n\u003cp\u003eThe intent makes sense, but in practice the Liquid \u003ccode\u003e{% form 'contact' %}\u003c\/code\u003e Drop's \u003ccode\u003eform.id\u003c\/code\u003e property does not return the value passed via \u003ccode\u003eid:\u003c\/code\u003e — it returns \u003cstrong\u003ean empty string\u003c\/strong\u003e. As a result, \u003ccode\u003eform.id == form_id\u003c\/code\u003e is never true, and the success message is permanently suppressed.\u003c\/p\u003e\n\n\u003ch3\u003eConfirmed empirically via debug output\u003c\/h3\u003e\n\u003cp\u003eOn a working branch I added a debug HTML comment to \u003ccode\u003econtact-form.liquid\u003c\/code\u003e and inspected it via DevTools after submitting the form. The values were:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;!-- SH-DEBUG-CONTACT-FORM\n  block.id                     = \"shopify:\/\/apps\/...\"\n  section.id                   = \"template--...\"\n  form_id                      = \"ContactForm-shopify:\/\/apps\/...\"\n  form.id                      = \"\"                ← expected: \"ContactForm-...\"\n  form.id == form_id           = false             ← always false\n  form.posted_successfully?    = true              ← submission itself succeeded\n  form.errors                  = null\n--\u0026gt;\u003c\/code\u003e\u003c\/pre\u003e\n\u003cp\u003e\u003ccode\u003eform.posted_successfully?\u003c\/code\u003e correctly returns \u003ccode\u003etrue\u003c\/code\u003e, meaning Shopify is handing the success state to the template. \u003cstrong\u003eThe information is reaching the template — it's just being thrown away by the broken \u003ccode\u003eform.id == form_id\u003c\/code\u003e guard.\u003c\/strong\u003e\u003c\/p\u003e\n\n\u003cdiv class=\"divider\"\u003e\u003c\/div\u003e\n\n\u003ch3\u003eThe temporary fix — drop \u003ccode\u003eand form.id == form_id\u003c\/code\u003e\u003c\/h3\u003e\n\u003cp\u003eThe change is confined to \u003cstrong\u003etwo\u003c\/strong\u003e guards inside \u003ccode\u003eblocks\/contact-form.liquid\u003c\/code\u003e. Remove \u003ccode\u003eand form.id == form_id\u003c\/code\u003e from each \u003ccode\u003e{%- if ... -%}\u003c\/code\u003e, restoring the simpler v3.2.1-era condition.\u003c\/p\u003e\n\n\u003ch4\u003eWhat to remove (struck-through portions)\u003c\/h4\u003e\n\u003cpre style=\"background:#f6f8fa;padding:1em;border-radius:6px;line-height:1.7;overflow-x:auto;\"\u003e\u003ccode\u003e{%- if form.errors \u003cspan style=\"color:#c00;text-decoration:line-through;background:#fde8e8;font-weight:bold;padding:0 4px;border-radius:3px;\"\u003eand form.id == form_id\u003c\/span\u003e -%}\n  \u0026lt;div class=\"contact-form__error\" tabindex=\"-1\" autofocus\u0026gt;\n    {{- 'icon-error.svg' | inline_asset_content -}}\n    {{ form.errors.translated_fields.email | capitalize }}\n    {{ form.errors.messages.email }}\n  \u0026lt;\/div\u0026gt;\n{%- endif -%}\n\n{%- if form.posted_successfully? \u003cspan style=\"color:#c00;text-decoration:line-through;background:#fde8e8;font-weight:bold;padding:0 4px;border-radius:3px;\"\u003eand form.id == form_id\u003c\/span\u003e -%}\n  \u0026lt;div class=\"contact-form__success\" tabindex=\"-1\" autofocus\u0026gt;\n    {{- 'icon-checkmark.svg' | inline_asset_content -}}\n    {{- 'blocks.contact_form.post_success' | t -}}\n  \u0026lt;\/div\u0026gt;\n{%- endif -%}\u003c\/code\u003e\u003c\/pre\u003e\n\u003cp style=\"color:#666;font-size:0.9em;margin-top:0.5em;\"\u003eThe struck-through \u003ccode\u003eand form.id == form_id\u003c\/code\u003e appears twice — delete both occurrences. Every other line stays untouched.\u003c\/p\u003e\n\n\u003ch4\u003eFinal form after the fix (copy-paste friendly)\u003c\/h4\u003e\n\u003cpre style=\"background:#f0f9f0;padding:1em;border-radius:6px;border-left:4px solid #0a7d2e;line-height:1.7;overflow-x:auto;\"\u003e\u003ccode\u003e{%- if form.errors -%}\n  \u0026lt;div class=\"contact-form__error\" tabindex=\"-1\" autofocus\u0026gt;\n    {{- 'icon-error.svg' | inline_asset_content -}}\n    {{ form.errors.translated_fields.email | capitalize }}\n    {{ form.errors.messages.email }}\n  \u0026lt;\/div\u0026gt;\n{%- endif -%}\n\n{%- if form.posted_successfully? -%}\n  \u0026lt;div class=\"contact-form__success\" tabindex=\"-1\" autofocus\u0026gt;\n    {{- 'icon-checkmark.svg' | inline_asset_content -}}\n    {{- 'blocks.contact_form.post_success' | t -}}\n  \u0026lt;\/div\u0026gt;\n{%- endif -%}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eFor the typical case of one contact form per page, this is enough to restore the success message.\u003c\/p\u003e\n\n\u003cdiv class=\"divider\"\u003e\u003c\/div\u003e\n\n\u003ch3\u003eCaveats — this is a temporary fix\u003c\/h3\u003e\n\u003cul\u003e\n  \u003cli\u003e\u003cstrong\u003eTreat this as temporary.\u003c\/strong\u003e The proper resolution is for Horizon itself (\u003ccode\u003eShopify\/horizon\u003c\/code\u003e) to either return the expected value from \u003ccode\u003eform.id\u003c\/code\u003e, or to revisit the guard condition altogether.\u003c\/li\u003e\n  \u003cli\u003eAs of April 2026, Shopify states explicitly on the \u003ca href=\"https:\/\/github.com\/Shopify\/horizon\" target=\"_blank\" rel=\"noopener\"\u003eHorizon repository\u003c\/a\u003e that \"We are not accepting contributions to Horizon at this time.\" Issues can be opened, but the timing of any upstream fix is at Shopify's discretion.\u003c\/li\u003e\n  \u003cli\u003eIf you actually need multiple contact forms on the same page, do \u003cstrong\u003enot\u003c\/strong\u003e apply this fix — instead consider a different disambiguation strategy (e.g. a custom hidden input you compare yourself in Liquid).\u003c\/li\u003e\n  \u003cli\u003eWhen Horizon ships a release that revisits this guard, \u003cstrong\u003eroll back this temporary fix promptly\u003c\/strong\u003e and follow upstream behaviour again.\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch3\u003eImpact on theme files\u003c\/h3\u003e\n\u003cp\u003eOnly \u003ccode\u003eblocks\/contact-form.liquid\u003c\/code\u003e is touched. Because that file is part of the upstream Horizon theme, Horizon updates will overwrite it — you'll either need to reapply this patch on every update, or drop the patch once upstream is fixed.\u003c\/p\u003e\n\n\u003ch3\u003eConclusion\u003c\/h3\u003e\n\u003cp\u003eThis is purely a \u003cstrong\u003etemporary fix until Horizon itself is corrected\u003c\/strong\u003e. We strip the broken guard from \u003ccode\u003eblocks\/contact-form.liquid\u003c\/code\u003e with the smallest possible change so that customers see the success message again. Once the upstream fix lands, this patch should be removed.\u003c\/p\u003e\n","brand":"STORE DOJO","offers":[{"title":"Default Title","offer_id":48804311040251,"sku":null,"price":0.0,"currency_code":"USD","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0733\/0381\/8491\/files\/2026-04-27.png?v=1777187044","url":"https:\/\/store-dojo.com\/en-us\/products\/2026-04-27","provider":"STORE DOJO","version":"1.0","type":"link"}