Prevent Redirect on Failed Login for Custom Login Page

“I’ve created a custom login page using Shopify’s default login form. Currently, when a user submits the form with correct credentials, they are logged in successfully. However, if they enter an incorrect email or password, they are redirected to the store’s default login page to see the error. I want to prevent this redirect and instead display the error directly on the custom login page.”

You’re getting that redirect because Shopify’s `customer_login` form, when submitted with invalid credentials, will always redirect to the `{{ routes.account_login_url }}` route to display errors. If your “custom login page” isn’t actually the `customers/login.liquid` template itself (or a custom template assigned to handle the `/account/login` route), then Shopify is just sending them to the default login page for error display.

To prevent the redirect and show errors on your custom page, you need to make sure your custom login page is the `customers/login.liquid` template. Then, within that template, you can access and display the `form.errors` object.

Here’s how you’d typically structure it in your `customers/login.liquid` file:

```

{% form ‘customer_login’ %}

{% if form.errors %}

<div class="login-errors">

  {{ form.errors | default_errors }}

</div>

{% endif %}

Email

Password

Sign In

{% endform %}

```

If your custom login page exists at a different URL (e.g., `/pages/my-custom-login`) and you’re just copying the form action to post to `/account/login`, Shopify will still redirect back to `/account/login` to show the errors. The only way around that for a truly separate page would be to implement an AJAX login, which is significantly more complex and not directly supported by the default `customer_login` form’s error handling. Stick to making `customers/login.liquid` your custom page.

P.S. I’m building a gamified discount app called Game Gophers. I’m giving it away for free to the first 5 users that DM me about it.

Hey,

Thanks for posting your Query on this forum. This is the issue because you’re using that in the conditional base. Like for the corrent entries it’s submit the form, but with the wrong entries it redirects to the actual login page. So, in that case you need to modify the code so that it doesn’t redirects to the login page.

The easiet way is to check the file for the login page and then you need to check for the redirect link. So, you just need to Edit this link with the error message instead.

Hope this helps.

Cheers :slight_smile:

Hey @youssefhe5,
Thanks for the explanation, that makes sense.
In my case, my custom login page exists at /pages/premium-login, and it’s using Shopify’s default {% form 'customer_login' %} to submit to /account/login.

Based on what you explained, am I correct in understanding that there’s no supported way to prevent the redirect back to**/account/login** when credentials are invalid, unless the page itself is the customers/login.liquid template?

Hey @saurabhv,

Yes, that’s right.

Because that specific form submission is handled by Shopify’s backend (which you can’t modify), the server sends a hard 302 Redirect response back to /account/login immediately upon authentication failure. The browser sees that header and leaves your custom page before you can intervene.

If keeping the URL as /pages/premium-login is strictly required, the only ‘supported’ way to bypass that redirect is to abandon the Liquid form entirely and use the Shopify Storefront API (specifically the customerAccessTokenCreate mutation) via JavaScript. This allows you to send credentials and get a success/fail response in the background (AJAX) without the page reloading or redirecting.

However, that adds a lot of complexity (managing access tokens, handling errors manually). If you can live with the URL being /account/login, styling that template is definitely the path of least resistance.

Hope that helps!

Hey @youssefhe5 , thanks for your guidance! As you suggested, I’m using the Shopify Storefront API and have created a custom login form. I’m using the following query for login:
mutation customerLogin($email: String!, $password: String!) {
customerAccessTokenCreate(input: {
email: $email,
password: $password
}) {
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors {
message
}
}
}
Could you please verify if this query is correct?

<section class="eq-login-pre" {{ customer }}>

  <div class="eq-container">

    <div class="eq-login-pre__wrapper">

      <div class="eq-login-pre__header">

        <h1 class="eq-title">Welcome Back!</h1>

        <p class="eq-info-text eq-highlight-text">

          Already have a Cupick.jp account? You can use the same login details here.

        </p>

      </div>

      <form id="custom-login-form" class="eq-form">

        <div class="eq-form__field">

          <label class="eq-form__label" for="login-email">Email</label>

          <input

            type="email"

            id="login-email"

            class="eq-form__input"

            placeholder="name@example.com"

            required

          />

        </div>

        <div class="eq-form__field">

          <label class="eq-form__label" for="login-password">Password</label>

          <input

            type="password"

            id="login-password"

            class="eq-form__input"

            placeholder="********"

            required

          />

        </div>

        <p id="login-error" class="eq-form__error" style="display:none;"></p>

        <div class="eq-form__cta">

          <button type="submit" class="eq-btn eq-btn--primary">

            Sign In

          </button>

        </div>

      </form>

    </div>

  </div>

</section>



<script>

  const STOREFRONT_TOKEN = "23453tdbvdfdf12423xv";

  const SHOP_URL = "";


  document.getElementById("custom-login-form")

    .addEventListener("submit", async function (e) {

      e.preventDefault();



      const email = document.getElementById("login-email").value;

      const password = document.getElementById("login-password").value;

      console.log('nbnbnbnb',email,password);

      await loginCustomer(email, password);

    });


  async function loginCustomer(email, password) {

    console.log('ashdfgbnfv',email,password);

    const errorEl = document.getElementById("login-error");

    errorEl.style.display = "none";


    const query = `

       mutation customerLogin($email: String!, $password: String!) {

        customerAccessTokenCreate(input: { email: $email, password: $password }) {

          customerAccessToken {

            accessToken

            expiresAt

          }

          customerUserErrors {

            message

          }

        }

      }

    `;

    const variables = { email, password };

    try {

      const res = await fetch(SHOP_URL, {

        method: "POST",

        headers: {

          "Content-Type": "application/json",

          "X-Shopify-Storefront-Access-Token": STOREFRONT_TOKEN

        },

        body: JSON.stringify({ query, variables })

      });


      const data = await res.json();

      console.log('data', data);

      if (

        !data.data ||

        data.data.customerAccessTokenCreate.customerUserErrors.length

      ) {

        errorEl.innerText = "Login failed."; // show an error on login fails

        errorEl.style.display = "block";

        return;

      }

      const token =

        data.data.customerAccessTokenCreate.customerAccessToken.accessToken;

      localStorage.setItem("customerToken", token);

      // Redirect to custom page

      window.location.href = "/account";

    } catch (err) {

      errorEl.innerText = "Network error. Please try again.";

      errorEl.style.display = "block";

    }

  }

</script>

This code implements a custom Shopify login page using a custom form and the Storefront API.

It sends a GraphQL mutation (customerAccessTokenCreate) to Shopify to log in the customer.

If the login fails, an error message is displayed on the same page. However, I’m not certain whether the customer is actually being logged in to Shopify successfully.

Hey @youssefhe5 ,
just wanted to say a huge thank you for sharing your solution for the custom login. I really appreciate the time and effort you took to explain everything so clearly.

I did notice one issue, though. The solution works perfectly when the credentials are incorrect, but when I enter valid credentials, Shopify throws a “missing hCaptcha token” error. To work around this, I ended up using the following code:


if (window.Shopify && window.Shopify.captcha && window.Shopify.captcha.protect) {
        await new Promise((resolve) => {
          window.Shopify.captcha.protect(document.getElementById("hidden-session-form"), resolve);

        });
}