An HTML attribute potentially worth $4.4M to Chipotle
At 4/19/2024
I recently found myself racing to fill out Chipotle’s online order form before my mother could find her credit card. In the process, I discovered a bug that could cost Chipotle $4.4 million annually.
My parents are retired. They continue to try to pay for meals. I don’t want them to. So we often end up in a competition to see who can pay first.
In this case, I knew I had an advantage. My card details were already stored in the browser. I just needed to use autofill to fill in the form quickly and complete the order before my mother could find her card.
Unfortunately, the form returned errors upon submission:
At that moment, I was still in a race against my mother’s parental instincts, so I quickly copied my card information from 1Password and completed the transaction.
Later, I wondered if this was a problem with autofill. I’ve written extensively about autofill in the past, and if there was a problem with autofill, I wanted to know about it.
Chipotle’s cryptic error messages
Chipotle’s error messages didn’t give me much to go on. The first error said:
We encountered an error saving your card. Please check your card info and try again.
What? I didn’t ask Chipotle to save my card. I intentionally left that box unchecked.
While this was irritating, my hunch was that this error wasn’t causing the problem. I got the same error if I checked the box. I suspected the second error was what prevented me from buying lunch.
Unfortunately, the second error didn’t provide any useful information:
We ran into an error submitting your order, please try again.
Every time I autofilled the form and submitted it, I got the same two errors. If I didn’t use autofill, the form worked.
Then I noticed something. The credit card expiration date changed after it was filled in.
There was the culprit. My credit card expires in 2023. The field only accepts two digits and instead of 23 to represent 2023, the form received 20.
Now that I could replicate the problem, I wanted to figure out why it was happening. Is it a problem with autofill or with Chipotle’s form?
Time to put on my detective’s cap
I enjoy figuring out why browsers aren’t working the way we expect. It’s like a good mystery novel, but one you get to solve. In this case, the first clue I examined was the HTML for the expiration year field:
<input
class="form-control payment-control ng-pristine ng-isolate-scope ng-valid-mask ng-empty ng-invalid ng-invalid-required ng-touched"
aria-label="expiration year"
cmg-cc-expiration-validator="cmg-cc-expiration-validator"
cmg-cc-expiration-validator-func="$ctrl.isCardExpired($ctrl.card.expiration)"
ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0 } }"
id="expirationDateYear"
name="expdateyear"
ng-required="true"
ng-model="$ctrl.card.expiration.year"
placeholder="YY"
autocomplete="cc-exp-year"
ui-mask="99"
type="text"
required="required"
aria-invalid="true"
style="">
Code language: HTML, XML (xml)
Many of the classes starting with ng-
. This is a telltale sign of Angular.
Next, I dug into Chipotle’s JavaScript to look for something that was referencing cmg-cc-expiration-validator
which sounded like it might be a key attribute for validation on this field. I found what I was looking for in Chipotle’s app JS file. I’ve pulled the relevant section into a gist for reference in case Chipotle changes in their JavaScript in the future.
Among the 400 lines or so in the validation section, this reference stood out to me:
angular.module('ui.mask', []).value('uiMaskConfig', {
Code language: Bash (bash)
An Angular module that masks UI? A quick search found the github repo for ui-mask. Chipotle’s JavaScript code I found earlier appeared to be leveraging this module.
What is ui-mask doing for the expiration field?
After discovering the ui-mask module, one of the input field’s attributes that I had overlooked earlier suddenly took on increased significance:
ui-mask="99"
Code language: Bash (bash)
The ui-mask documentation provides this example:
For example, we use
'9'
here to accept any numeric values for a phone number:ui-mask="(999) 999-9999"
So ui-mask="99"
on the expiration year field is telling the ui-mask module to only accept two numeric values. When autofill tries to enter 2023
, this ui-mask only lets the first two characters be entered.
Web standards alternatives
I’m sure there are good reasons why Chipotle is using the ui-mask module. I don’t want to sound like I’m questioning their decisions. I suspect that the module is useful for multiple fields and serves other purposes on the site.
However, for this expiration year field, we could accomplish the same thing using two standard HTML5 attributes:
pattern="\d\d"
maxlength="2"
Code language: HTML, XML (xml)
The pattern attribute uses regular expressions to validate that the user’s input matches what we expect. In this case, \d
tells the browser we want a digit character. By supplying \d
twice, we tell the browser that we want two digits so that 01
will be accepted, but 1
will not.Footnote
1
The maxlength
attribute tells the browser how many characters are allowed and will prevent users from typing more than the allotted amount.
Fixing autofill
To test what would happen if Chipotle’s form used these standards, I opened my browser’s developer tools and edited the expiration year field:
Adding the maxlength
attribute to the field fixes the problem. This makes sense. We’re telling the browser, and by extension the autofill feature, how many digits it should use for the expiration year.
Autofill is smart enough to know that if we only want two digits for a year field, that the form needs the last two digits of the year. We just need to tell the browser how many digits we expect.
And we need to tell it in a standard way.
What impact does this have on Chipotle’s business?
Now that the mystery was solved, I wondered how much this problem might cost Chipotle. The first time I encountered this error—long before the credit card race with my mother—I thought Chipotle’s website was broken. I gave up trying to order online.
How many other people have failed to finish an order because the form doesn’t support autofill and the error messages aren’t helpful?
We know that when people use autofill, they complete forms 30% faster, but we don’t know how many people use autofill overall.
So because we don’t know how many people use autofill, let’s use a conservative number.
What if Chipotle could gain half a percentage point increase in transactions from fixing autofill?
Could we calculate how much revenue Chipotle might see from half a percentage point increase in transactions? We might be able to.
In their April 2019 quarterly report, Chipotle revealed that “the company’s relaunched website is attracting 1 million transactions a week on average”. In 2018, Chipotle said that their average online order was “$16 to $17.”
I’m going to use the upper end of that range ($17) for the average online order because that average is over a year old and in July of this year, Chipotle reported that “online sales nearly doubled in the second quarter” and “average check jumped by 3.5%.”
Based on the information in the most recent earnings report, using $17 for the average per online order and 1 million transactions per week is probably a low. That’s perfect for our attempt to make a conservative estimate.
maxlength="2"
worth?
How much is maxlength="2"
worth?
Let’s do the math:
1,000,000 Online transactions per week
* .005 Half a percentage point
= 5,000 Equals 5k transactions per week
* $17 Times average online order in 2018
= $85,000 Equals $85k per week
* 52 Multiplied by the weeks in a year
= $4,420,000 for maxlength="2"
Code language: Bash (bash)
If fixing autofill caused a half of a percentage point increase in online orders, it would increase Chipotle’s revenue by $4.4 million.
But perhaps half of a percentage point is too high. Let’s use a quarter of a percentage point instead. That’s still $2.2 million.
I’m not sure how low you would need to go before adding the maxlength
attribute wouldn’t be worth it.
Lessons for your web forms
There are three key takeaways from Chipotle’s order form that you should consider:
1. Use HTML5 input features
Select the correct input type for your field. Use other input attributes such as maxlength
, minlength
, and pattern
to provide additional clues to the browser about what information the field expects.
2. Support autofill
Add the autocomplete
attribute with the appropriate token to tell autofill the purpose of the field. This is now the standard way to support autofill.
3. Make autofill part of your test plans
Once your form works with autofill, make sure it continues to work by including autofill in your QA process. This will help you identify problems like the one with Chipotle’s form before they impact users.
P.S. Chipotle, if this is helpful, may I suggest you buy my parents’ lunch next time? 🙂
P.P.S. Oh, and if you make $4.4 million off this article, you should hire us to help you with progressive web apps and the payment request API.
Footnotes
-
Originally I suggested changing the type of field from
text
tonumber
to force a numeric value. However, Amier pointed out in the comments thatnumber
doesn’t acceptmaxlength
. Instead,number
expects you to usemin
andmax
to set the minimum and maximum values. I did some testing and found that using those values doesn’t solve the autofill issues. That’s fine. We want01
not1
for the expiration year which means we’re actually asking for two digits, not a number. Therefore, it makes more sense for the input type to be set totext
and use thepattern
attribute to constrain the input to digits. Return to the text before footnote 1