How to utilize the shopify form tag {% form 'customer' %} or {% form 'contact' %}

Topic summary

A developer encountered persistent 400 “invalid parameters” errors when attempting to modify customer information using Shopify’s {% form 'contact' %} Liquid tag. The goal was to update customer tags (specifically wishlist data) when users clicked a button, but even simple changes to first_name and last_name fields failed.

Attempted Solution:

  • Used hidden form inputs with customer data
  • Implemented JavaScript to handle form submission via fetch API
  • All requests reached Shopify but returned 400 errors

Root Cause Identified:
Another user suggested hCaptcha implementation on Shopify’s side could be blocking the requests, making this approach unstable.

Final Resolution:
The developer abandoned the custom implementation in favor of a third-party app called “SE Wishlist Engine” due to:

  • Instability of Shopify’s native update/getter methods
  • Customer tag limitations (250 entries maximum)
  • Better pricing ($5/month for unlimited wishlists vs. competitors charging $50 for limited entries)

Technical Note:
The app exposes wishlist product IDs via window.WISHLIST_PRODUCTS_IDS for developers needing programmatic access.

Summarized with AI on October 27. AI used: claude-sonnet-4-5-20250929.

Hi i have been struggling making a change on customer’s information as user clicks on a button. Basically the flow is

User click the button.

Button within the form submits the form.

Make a change in customer information.

And this is the actual code.

I managed to add an extra script to trigger the form submission action. The call successfully reaches to the Shopify and sends a call but it returns 400 every time. The error message is invalid parameters.

I was initially trying to change the customer[tags] but then i tried to change simpler things like first_name and last_name. But they all return 400 with invalid parameters.

What could be possibly wrong?

{% if customer %}
    {% assign wishlist_tag = 'wishlist:' | append: product.id %}
    {% unless customer.tags contains wishlist_tag %}
      {% form 'contact', id:"wishlist-form", class: 'wishlist-form', data-product-handle: product.handle, data-product-id: product.id %}
        <input type="hidden" name="contact[email]" value="{{ customer.email }}" id="email">
        <input type="hidden" name="contact[first_name]" value="hello" id="first_name">
        <input type="hidden" name="contact[last_name]" value="world" id="last_name">
        <button type="submit" class="m-tooltip m-button--icon {{ class }} {{ class_name | default: 'm-tooltip--left' }} m-tooltip--style-{{ style | default: '1' }}">
         ...SKIP_CONTENT...
        </button>
      {% endform %}       
        <script>
          document.addEventListener('DOMContentLoaded', function() {
            const wishlistForm = document.querySelector('.wishlist-form[data-product-handle="{{ product.handle }}"]');
            console.log(wishlistForm);
            if (wishlistForm) {
              const button = wishlistForm.querySelector('button[type="submit"]');
              console.log(button);
              // Debug button click
              button.addEventListener('click', function(e) {
                console.log('?️ Button clicked!');
                console.log('Button type:', button.type);
                console.log('Form action:', wishlistForm.action);
                console.log('Form method:', wishlistForm.method);
                console.log('Form fields:');
                
                const formData = new FormData(wishlistForm);
                for (let [key, value] of formData.entries()) {
                  console.log(`  ${key} = ${value}`);
                }
                
                // Submit via AJAX to prevent page refresh
                fetch(wishlistForm.action, {
                  method: wishlistForm.method,
                  body: formData
                })
                .then(response => {
                  console.log('Response status:', response.status);
                  console.log('Response URL:', response.url);
                  if (response.ok) {
                    console.log('✅ Wishlist updated successfully!');
                    button.innerHTML = '<span class="m-tooltip-icon m:block">❤️</span><span class="m-tooltip__content">Added to wishlist!</span>';
                    button.disabled = true;
                  } else {
                    console.error('❌ Error:', response.status);
                    return response.text().then(text => {
                      console.error('Error details:', text);
                    });
                  }
                })
                .catch(error => {
                  console.error('❌ Network error:', error);
                })
              });
              
              // Debug form submission
              wishlistForm.addEventListener('submit', function(e) {
                console.log('? FORM SUBMITTED!');
                console.log('Form is actually submitting - this should refresh the page');
                console.log('Form action:', this.action);
                // Let it submit naturally
              });
            }
          });
        </script>

Trying to recreate https://github.com/zakhardage/Shopify-Wish-List?tab=readme-ov-file ?

Captcha could be the reason, as suggested in readme…

1 Like

Thank you for your help, but I ended up using 3rd part app called SE Wishlist Engine for wishlist functionality.

Reason

  1. updater and getter methods are unstable. Recent hcaptcha implementation on shopify side made this logic more unstable.

  2. tag system is limited to 250 entries.

App

  1. Very flexible and applicable to many different scenarios. Can add as an icon, nav, module, … providing a separate page.

  2. Only requires $5 per month for unlimited wishlist unlike others who give you only 100~1000 and charge you $50.

1 Like

If you get to use this 3rd party app and if you also need a way to retrieve the list of wishlist product ids. You can access it through window.WISHLIST_PRODUCTS_IDS