Why make your customers suffer through 1-2 extra steps just to login or register on your Magento store? In this article I’ll show you how to build an AJAX Quick Login and Registration form that can be used in a dropdown or lightbox. We’ll be creating a new extension and overriding several controller actions in Magento.
Before we get started let’s run through all of the steps required:
- Set up our extension and override the customer account controller.
- Create a custom login and registration form block template.
- Update our frontend layout (local.xml) to append the login/registration form block inside the <default> handle to “header” reference.
- Update header.phtml to render the form block inside the store header.
- Use jQuery to make an AJAX post call after clicking “Login” or “Register”.
- Load the JavaScript via local.xml and add some style using CSS.
Create the Extension
First let’s build the folder structure for extension:
/app/code/local/FastDivision/QuickLogin
/app/code/local/FastDivision/QuickLogin/controllers
/app/code/local/FastDivision/QuickLogin/etc
Now we need to set up our config.xml file and load the extension with a module XML file inside /app/etc/modules.
/app/code/local/FastDivision/QuickLogin/etc/config.xml
1 2 3 4 5 6 7 8 | <?xml version="1.0"?> <config> <modules> <FastDivision_QuickLogin> <version>1.0.0</version> </FastDivision_QuickLogin> </modules> </config> |
For now we’re just getting a barebones extension loaded. We’ll return to this file later so keep it open.
/app/etc/modules/FastDivision_QuickLogin.xml
1 2 3 4 5 6 7 8 9 | <?xml version="1.0"?> <config> <modules> <FastDivision_QuickLogin> <active>true</active> <codePool>local</codePool> </FastDivision_QuickLogin> </modules> </config> |
This is really simple. We’re just telling Magento to load the QuickLogin extension inside the FastDivision package inside /app/code/local. Verify your new Magento extension is loading correctly by going to System > Configuration > Advanced. You may have to clear the cache and log in again:

Next we’re going to create a controller to override the login and create account post actions. We’ll be referencing the code and inheriting the Mage_Customer_AccountController class from /app/code/core/Mage/Customer/controllers/AccountController.php.
/app/code/local/FastDivision/QuickLogin/controllers/Customer/AccountController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | <?php // Require the core controller file that you're planning to override require_once('Mage/Customer/controllers/AccountController.php'); // The class name follows this format: // YOURPACKAGE_YOUREXTENSION_COREMODULEFOLDER_CONTROLLERFILENAME // We extend the original Mage_Customer_AccountController class to inherit unused actions and override specific actions class FastDivision_QuickLogin_Customer_AccountController extends Mage_Customer_AccountController { // Code referenced from AccountController.php public function loginPostAction() { if(!$this->getRequest()->isXmlHttpRequest()) { if ($this->_getSession()->isLoggedIn()) { $this->_redirect('*/*/'); return; } } $session = $this->_getSession(); if($this->getRequest()->isXmlHttpRequest()) { // Report exceptions via JSON $ajaxExceptions = array(); } if ($this->getRequest()->isPost()) { $login = $this->getRequest()->getPost('login'); if (!empty($login['username']) && !empty($login['password'])) { try { $session->login($login['username'], $login['password']); if ($session->getCustomer()->getIsJustConfirmed()) { $this->_welcomeCustomer($session->getCustomer(), true); } } catch (Mage_Core_Exception $e) { if($this->getRequest()->isXmlHttpRequest()) { $messages = array_unique(explode("\n", $e->getMessage())); foreach ($messages as $message) { $ajaxExceptions['exceptions'][] = $message; } } else { switch ($e->getCode()) { case Mage_Customer_Model_Customer::EXCEPTION_EMAIL_NOT_CONFIRMED: $value = Mage::helper('customer')->getEmailConfirmationUrl($login['username']); $message = Mage::helper('customer')->__('This account is not confirmed. <a href="%s">Click here</a> to resend confirmation email.', $value); break; case Mage_Customer_Model_Customer::EXCEPTION_INVALID_EMAIL_OR_PASSWORD: $message = $e->getMessage(); break; default: $message = $e->getMessage(); } $session->addError($message); } $session->setUsername($login['username']); } catch (Exception $e) { // Mage::logException($e); // PA DSS violation: this exception log can disclose customer password } } else { if($this->getRequest()->isXmlHttpRequest()) { $ajaxExceptions['exceptions'][] = 'Login and password are required.'; } else { $session->addError($this->__('Login and password are required.')); } } } if($this->getRequest()->isXmlHttpRequest()) { // If errors if(count($ajaxExceptions)) { echo json_encode($ajaxExceptions); } else { // No Errors echo json_encode(array('success' => 'success')); } } else { // Redirect for non-ajax $this->_loginPostRedirect(); } } // Create Account public function createPostAction() { if($this->getRequest()->isXmlHttpRequest()) { // Report exceptions via JSON $ajaxExceptions = array(); } $session = $this->_getSession(); if ($session->isLoggedIn()) { $this->_redirect('*/*/'); return; } $session->setEscapeMessages(true); // prevent XSS injection in user input if ($this->getRequest()->isPost()) { $errors = array(); if (!$customer = Mage::registry('current_customer')) { $customer = Mage::getModel('customer/customer')->setId(null); } /* @var $customerForm Mage_Customer_Model_Form */ $customerForm = Mage::getModel('customer/form'); $customerForm->setFormCode('customer_account_create') ->setEntity($customer); $customerData = $customerForm->extractData($this->getRequest()); if ($this->getRequest()->getParam('is_subscribed', false)) { $customer->setIsSubscribed(1); } /** * Initialize customer group id */ $customer->getGroupId(); if ($this->getRequest()->getPost('create_address')) { /* @var $address Mage_Customer_Model_Address */ $address = Mage::getModel('customer/address'); /* @var $addressForm Mage_Customer_Model_Form */ $addressForm = Mage::getModel('customer/form'); $addressForm->setFormCode('customer_register_address') ->setEntity($address); $addressData = $addressForm->extractData($this->getRequest(), 'address', false); $addressErrors = $addressForm->validateData($addressData); if ($addressErrors === true) { $address->setId(null) ->setIsDefaultBilling($this->getRequest()->getParam('default_billing', false)) ->setIsDefaultShipping($this->getRequest()->getParam('default_shipping', false)); $addressForm->compactData($addressData); $customer->addAddress($address); $addressErrors = $address->validate(); if (is_array($addressErrors)) { $errors = array_merge($errors, $addressErrors); } } else { $errors = array_merge($errors, $addressErrors); } } try { $customerErrors = $customerForm->validateData($customerData); if ($customerErrors !== true) { $errors = array_merge($customerErrors, $errors); } else { $customerForm->compactData($customerData); $customer->setPassword($this->getRequest()->getPost('password')); $customer->setConfirmation($this->getRequest()->getPost('confirmation')); $customerErrors = $customer->validate(); if (is_array($customerErrors)) { $errors = array_merge($customerErrors, $errors); } } $validationResult = count($errors) == 0; if (true === $validationResult) { $customer->save(); Mage::dispatchEvent('customer_register_success', array('account_controller' => $this, 'customer' => $customer) ); if ($customer->isConfirmationRequired()) { $customer->sendNewAccountEmail( 'confirmation', $session->getBeforeAuthUrl(), Mage::app()->getStore()->getId() ); $session->addSuccess($this->__('Account confirmation is required. Please, check your email for the confirmation link. To resend the confirmation email please <a href="%s">click here</a>.', Mage::helper('customer')->getEmailConfirmationUrl($customer->getEmail()))); if($this->getRequest()->isXmlHttpRequest()) { echo json_encode(array('success' => $this->__('Account confirmation is required. Please, check your email for the confirmation link. To resend the confirmation email please <a href="%s">click here</a>.', Mage::helper('customer')->getEmailConfirmationUrl($customer->getEmail())))); } else { $this->_redirectSuccess(Mage::getUrl('*/*/index', array('_secure'=>true))); } return; } else { $session->setCustomerAsLoggedIn($customer); $url = $this->_welcomeCustomer($customer); if($this->getRequest()->isXmlHttpRequest()) { echo json_encode(array('success' => 'success')); } else { $this->_redirectSuccess($url); } return; } } else { $session->setCustomerFormData($this->getRequest()->getPost()); if(!$this->getRequest()->isXmlHttpRequest()) { if (is_array($errors)) { foreach ($errors as $errorMessage) { $session->addError($errorMessage); } } else { $session->addError($this->__('Invalid customer data')); } } else { if (is_array($errors)) { foreach ($errors as $errorMessage) { $ajaxExceptions['exceptions'][] = $errorMessage; } } else { $ajaxExceptions['exceptions'][] = 'Invalid customer data'; } } } } catch (Mage_Core_Exception $e) { $session->setCustomerFormData($this->getRequest()->getPost()); if ($e->getCode() === Mage_Customer_Model_Customer::EXCEPTION_EMAIL_EXISTS) { $url = Mage::getUrl('customer/account/forgotpassword'); $message = $this->__('There is already an account with this email address. If you are sure that it is your email address, <a href="%s">click here</a> to get your password and access your account.', $url); $session->setEscapeMessages(false); } else { $message = $e->getMessage(); } if(!$this->getRequest()->isXmlHttpRequest()) { $session->addError($message); } else { $messages = array_unique(explode("\n", $e->getMessage())); foreach ($messages as $message) { $ajaxExceptions['exceptions'][] = $message; } } } catch (Exception $e) { if(!$this->getRequest()->isXmlHttpRequest()) { $session->setCustomerFormData($this->getRequest()->getPost()) ->addException($e, $this->__('Cannot save the customer.')); } else { $ajaxExceptions['exceptions'][] = 'Cannot save the customer.'; } } } if($this->getRequest()->isXmlHttpRequest()) { echo json_encode($ajaxExceptions); } else { $this->_redirectError(Mage::getUrl('*/*/create', array('_secure' => true))); } } } |
If you compare the AccountController.php in /app/code/core/Mage and the copy above you’ll notice some differences when it comes to error handling and redirects. The controller overrides two actions: loginPost and createPost which should be self-explanatory. createPost handles registration and extracts data from the registration form, validates, then saves it.
$this->getRequest()->isXmlHttpRequest() is used to check for AJAX requests. If the controller action is called via AJAX, we return any success or error messages as a JSON-encoded array. Redirects are removed.
The last thing we need to do in our new extension is update the config.xml file to specify the controller override:
/app/code/local/FastDivision/QuickLogin/etc/config.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?xml version="1.0"?> <config> <modules> <FastDivision_QuickLogin> <version>1.0.0</version> </FastDivision_QuickLogin> </modules> <frontend> <routers> <customer> <args> <modules> <FastDivision_QuickLogin before="Mage_Customer">FastDivision_QuickLogin_Customer</FastDivision_QuickLogin> </modules> </args> </customer> </routers> </frontend> </config> |
That’s it! Next we need to create the block template .phtml file, tie it to the store header via local.xml, and get it included in the header.phtml file using $this->getChildHtml().
Create the Login/Registration Form Block Template
Let’s turn our attention away from /app/code and focus on the visual side:
/app/design/frontend/YOUR_PACKAGE/YOUR_THEME/template/customer/quick_login.phtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | <div id="login-modal" class="modal-window"> <div id="login-modal-content"> <div id="signup-box"> <h1>Register</h1> <p><a href="#" id="already-registered-link">Already registered? Click here to sign in.</a></p> <form action="<?php echo Mage::getBaseUrl() ?>customer/account/createpost/" method="post" id="signup-form" class="site-form" onsubmit="return false"> <ul class="form-list"> <li class="fields"> <div class="field first-field"> <label for="firstname" class="required"><?php echo $this->__('First Name') ?><em>*</em></label> <div class="input-box"> <input type="text" name="firstname" id="firstname" class="input-text validate-email required-entry" /> </div> </div> <div class="field"> <label for="last_name" class="required"><?php echo $this->__('Last Name') ?><em>*</em></label> <div class="input-box"> <input type="text" name="lastname" id="lastname" class="input-text validate-email required-entry" /> </div> </div> <div class="field"> <label for="email_address" class="required"><?php echo $this->__('Email') ?><em>*</em></label> <div class="input-box"> <input type="text" name="email" id="email_address" class="input-text validate-email required-entry" /> </div> </div> <div class="field"> <label for="password" class="required"><?php echo $this->__('Password') ?><em>*</em></label> <div class="input-box"> <input type="password" name="password" id="password" title="<?php echo $this->__('Password') ?>" class="input-text required-entry validate-password" /> </div> </div> <div class="field"> <label for="confirmation" class="required"><?php echo $this->__('Confirm Password') ?><em>*</em></label> <div class="input-box"> <input type="password" name="confirmation" title="<?php echo $this->__('Confirm Password') ?>" id="confirmation" class="input-text required-entry validate-cpassword" /> </div> </div> </li> </ul> <button type="submit" title="Submit" class="action-button"><span>Create Account</span></button> </form> </div> <div id="login-box"> <h1>Login</h1> <p><?php echo $this->__('If you have an account with us, please log in.') ?></p> <p><a href="#" id="need-account-link">Need an account? Click here to register.</a></p> <form action="<?php echo Mage::getBaseUrl() ?>customer/account/loginPost/" method="post" id="login-form" class="site-form" onsubmit="return false"> <ul class="form-list"> <li class="field"> <label for="email" class="required"><?php echo $this->__('Email') ?><em>*</em></label> <div class="input-box"> <input type="text" name="login[username]" value="<?php echo $this->htmlEscape($this->getUsername()) ?>" id="email" class="input-text" title="<?php echo $this->__('Email Address') ?>" /> </div> </li> <li class="field"> <label for="pass" class="required"><?php echo $this->__('Password') ?><em>*</em></label> <div class="input-box"> <input type="password" name="login[password]" class="input-text" id="pass" title="<?php echo $this->__('Password') ?>" /> </div> </li> </ul> <button type="submit" class="action-button action-button-no-arrow" title="<?php echo $this->__('Login') ?>" name="send" id="send2"><span><?php echo $this->__('Login') ?></span></button> <a href="<?php echo $this->getForgotPasswordUrl() ?>" class="small"><?php echo $this->__('Forgot Your Password?') ?></a> </form> </div> </div> <a id="close_x" class="close" href="#">Close</a> <script type="text/javascript"> //<![CDATA[ var dataForm = new VarienForm('login-form', true); //]]> </script> </div> |
A suitable location for this file is the /customer directory. I’m using the stock forms from Magento and instantiated the VarienForm validation class to validate the forms. For this article I wrapped the forms around modal window <div>'s but you could also use dropdowns.
Update Your Magento Theme’s Local.xml
Now let’s attach quick_login.phtml to the header so we have the markup available everywhere in our store. Later on we’ll hide it with CSS so it isn’t visible unless dropped down or opened in a modal window.
Local.xml makes it easy to modify the layout XML in your store in an upgrade-friendly way. For an introduction to local.xml, click here.
/app/design/frontend/YOUR_PACKAGE/YOUR_THEME/layout/local.xml
1 2 3 4 5 6 7 8 | <?xml version="1.0"?> <layout version="0.1.0"> <default> <reference name="header"> <block type="customer/form_login" name="login.modal" as="login-modal" template="customer/quick_login.phtml"/> </reference> </default> </layout> |
We attach the customer/form_login block to the <default> layout handle to target every page. The reference hits the header block.
Update Your Magento Theme’s Store Header Template
Inside the header template we’re going to use getChildHtml('login-modal') to finally render the block.
/app/design/frontend/YOUR_PACKAGE/YOUR_THEME/template/page/html/header.phtml
1 2 3 | <?php if(!$this->helper('customer')->isLoggedIn()): ?> <?php echo $this->getChildHtml('login-modal') ?> <?php endif; ?> |
Place this code in an appropriate location at your own discretion. The remainder of the article will show you a rough example of how to use jQuery and CSS to build out the form interaction and user experience.
Write an AJAX Post Call for the Form Submit Buttons
The following code uses the lightbox_me jQuery plugin used in previous DIY Magento articles such as Quick View and Quick Cart. You can use this approach if you’d like or use something like jQuery hoverIntent to create a dropdown. I’m going to assume you already have jQuery installed but if not please refer to a previous DIY Magento article.
initLoginBox() handles the AJAX post requests and returns back JSON data for error-handling. At the very least use the submit events to handle form submission. At this time errors are only reported with simple alert popups but you can extend if needed.
/js/YOUR_THEME/login-box.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | (function($) { $(document).ready(function() { // Opens a modal window when clicking the "My Account" link // If #login-modal exists, then open the lightbox // Refer to lightbox_me jQuery Plugin (http://buckwilson.me/lightboxme/) for settings and documentation $('.links a:contains("My Account")').click(function(e) { if($('#login-modal').length) { $('#login-modal').lightbox_me({ centered: true, onLoad: function() { initLoginBox(); } }); e.preventDefault(); } }); }); // Activates events on the login form & handles AJAX form posts with JSON data return function initLoginBox() { $('#already-registered-link').click(function(e) { $('#signup-box').hide(); $('#login-box').show(); e.preventDefault(); }); $('#need-account-link').click(function(e) { $('#signup-box').show(); $('#login-box').hide(); e.preventDefault(); }); $('#signup-form').unbind().submit(function() { $.post($(this).attr('action'), $(this).serialize(), function(data) { if(!data.exceptions) { window.location.reload(); } else { for(var i = 0; i < data.exceptions.length; i++) { alert(data.exceptions[i]); } } }, 'json'); }); $('#login-form').unbind().submit(function() { $.post($(this).attr('action'), $(this).serialize(), function(data) { if(!data.exceptions) { window.location.reload(); } else { for(var i = 0; i < data.exceptions.length; i++) { alert(data.exceptions[i]); } } }, 'json'); }); } })(jQuery); |
Load JavaScript via Local.xml & Style with CSS
Finally we need to add login-box.js to the store footer. Again, we’ll be updating local.xml to make this happen:
/app/design/frontend/YOUR_PACKAGE/YOUR_THEME/layout/local.xml
1 2 3 4 5 6 7 8 9 10 11 | <?xml version="1.0"?> <layout version="0.1.0"> <default> <reference name="head"> <action method="addJs"><script>my_theme/login-box.js</script></action> </reference> <reference name="header"> <block type="customer/form_login" name="login.modal" as="login-modal" template="customer/quick_login.phtml"/> </reference> </default> </layout> |
That’s it. Now to add icing to the cake let’s update the CSS. Here’s a few rules to get started:
1 2 3 4 5 6 7 8 | /* Login Modal */ .modal-window { display: none; width: 640px; background: #fff; } #login-modal { padding: 20px; } #login-modal-content { padding: 20px; } #login-modal h1 { font-size: 26px; margin: 0 0 10px 0; } #login-modal p { color: #333; margin: 0 0 10px 0; } #login-modal-content .field label { width: 140px; } #signup-box { display: none; } |
We’re done! Obviously you’ll need to do some extra customization to make it fit with your design but I hope this article provided a great starting point and a glimpse into creating simple extensions for Magento. If you have any questions feel free to leave a comment.
Follow @fastdivision on Twitter for the latest updates on everything Magento including our premium Magento theme, Avalanche.


