
⚙️ How to restrict unwanted character input in INPUT with JavaScript
Registration form, phone field, catalog search: the user clicks on an INPUT and types anything. Letters where you expect digits. Special characters in an order number. Emoji in an email field. The result: garbage in the database, broken queries, backend validation failing with an error.
The problem is solved on the frontend in 15 lines of code. No plugins, no libraries, just plain JavaScript or a couple of HTML attributes. Below are three working approaches: from quick HTML5 to fine-grained filtering on keydown, with ready-made examples for common scenarios.
💡 Quick overview:
- We examine when HTML attributes
pattern,inputmode, andtypeare enough, and when JavaScript is needed - We write a "digits only" filter in jQuery and plain JS, both versions with line-by-line breakdown
- We add protection against pasting via Ctrl+V and context menu
- We build a universal template function for any restrictions: letters, Latin characters, custom masks
HTML5: what you can do without a single line of JavaScript
The simplest and most reliable way to restrict input is to tell the browser exactly what you expect in a field. Modern browsers understand three key attributes that cover most common tasks.
type, the primary filter. type="number" allows only digits, minus, and decimal separator. type="tel" on mobile devices shows a numeric keyboard. type="email" checks for @ on form submission. type="url" requires a protocol. This doesn't block input entirely, but it provides the correct on-screen keyboard and basic validation on submit.
pattern, a regular expression on the field. The attribute accepts a regex and validates the value on form submission:
1 <input type="text" pattern="[0-9]{6}" placeholder="Six digits" title="Exactly 6 digits">
The browser blocks submit if the value doesn't match the pattern. The downside: pattern doesn't prevent entering disallowed characters; it only validates the result. The user can type "abc," and they'll only see the error when trying to submit the form.
inputmode, the correct keyboard on mobile. inputmode="numeric" opens a numeric keyboard but doesn't prohibit entering letters. inputmode="decimal" adds a separator. Useful in combination with a JavaScript filter: the keyboard is already correct, and JS handles the rest.
What to choose. If you need to actually prevent entering disallowed characters in real time, HTML attributes aren't enough. Let's move to JavaScript.
JavaScript: blocking keystrokes via keydown
The keydown approach catches each keystroke before the character appears in the field. If the key isn't on the whitelist, event.preventDefault() suppresses it.
Option 1: jQuery, digits only
The code is attached to an INPUT by id, allows digits and service keys (backspace, arrows, Tab), and blocks everything else:
1 $(document).ready(function() { 2 $("#phone_input").keydown(function(event) { 3 // Service keys — let them through 4 if (event.keyCode === 46 || event.keyCode === 8 || 5 event.keyCode === 9 || event.keyCode === 27 || 6 (event.keyCode === 65 && event.ctrlKey === true) || 7 (event.keyCode >= 35 && event.keyCode <= 39)) { 8 return; 9 } 10 // Block everything except digits (main block and numpad) 11 if ((event.keyCode < 48 || event.keyCode > 57) && 12 (event.keyCode < 96 || event.keyCode > 105)) { 13 event.preventDefault(); 14 } 15 }); 16 });
Here's what's happening line by line: keyCode 46 (Delete), 8 (Backspace), 9 (Tab), 27 (Escape), 65 + Ctrl (select all), 35..39 (Home, End, arrows). Then come the main block digits 48..57 (0..9) and numpad 96..105. Anything that doesn't fall into these ranges gets preventDefault.
Full key code table at Cambia Research.
Option 2: plain JavaScript, digits only
The same thing without jQuery, using onkeypress. This version is more compact and doesn't pull in a library:
1 document.getElementById("phone_input").onkeypress = function(event) { 2 event = event || window.event; 3 if (event.charCode && (event.charCode < 48 || event.charCode > 57)) { 4 return false; 5 } 6 };
charCode checks that the event has a printable character; service keys (arrows, backspace) pass automatically since their charCode equals 0. Digits: 48..57, everything else returns false.
Important: keyCode is considered deprecated. In modern code it's better to use event.key, which returns a string with the key name and doesn't depend on keyboard layout:
1 element.addEventListener("keydown", function(event) { 2 // Allow digits, Backspace, Delete, arrows, Tab 3 const allowedKeys = [ 4 "Backspace", "Delete", "Tab", "Escape", 5 "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", 6 "Home", "End" 7 ]; 8 if (allowedKeys.includes(event.key)) return; 9 // Ctrl+A / Ctrl+C / Ctrl+V 10 if (event.ctrlKey) return; 11 // Only digits 12 if (!/^[0-9]$/.test(event.key)) { 13 event.preventDefault(); 14 } 15 });
The event.key approach reads more clearly and doesn't require memorizing numeric codes. But for supporting older browsers, the keyCode version above still works.
Blocking paste: catching Ctrl+V and context menu
keydown catches keystrokes, but the user can paste text via Ctrl+V or right-click → "Paste." They copied "abc123" into a digits-only field, and half of it got through.
The solution is the input event. It fires after ANY change to the field value: manual input, paste, autofill, text drag-and-drop. We filter the final value with a regular expression:
1 document.getElementById("digits_only").addEventListener("input", function(event) { 2 this.value = this.value.replace(/[^0-9]/g, ""); 3 });
That's it. One line, and not a single non-digit will pass through the field, regardless of how it got there. For letters: replace(/[^a-zA-Zа-яА-ЯёЁ]/g, ""). For Latin characters: replace(/[^a-zA-Z]/g, "").
Combined approach (the golden mean). keydown provides instant feedback (the key "won't press"), while input guards against pasting. Use both handlers:
1 const field = document.getElementById("digits_only"); 2 3 field.addEventListener("keydown", function(event) { 4 const allowed = ["Backspace", "Delete", "Tab", "Escape", 5 "ArrowLeft", "ArrowRight", "Home", "End"]; 6 if (allowed.includes(event.key)) return; 7 if (event.ctrlKey) return; 8 if (!/^[0-9]$/.test(event.key)) event.preventDefault(); 9 }); 10 11 field.addEventListener("input", function() { 12 this.value = this.value.replace(/[^0-9]/g, ""); 13 });
The first handler cuts disallowed keystrokes instantly. The second cleans up leftovers after paste and autofill.
Ready-made template for any restrictions
Let's build a function that takes an INPUT and a regular expression of allowed characters. It works for phone numbers, postal codes, logins, SKU search:
1 function restrictInput(element, allowedPattern) { 2 element.addEventListener("keydown", function(event) { 3 const serviceKeys = [ 4 "Backspace", "Delete", "Tab", "Escape", 5 "ArrowLeft", "ArrowRight", "Home", "End" 6 ]; 7 if (serviceKeys.includes(event.key)) return; 8 if (event.ctrlKey && ["a", "c", "v", "x", "z"].includes(event.key.toLowerCase())) return; 9 if (!allowedPattern.test(event.key)) event.preventDefault(); 10 }); 11 12 element.addEventListener("input", function() { 13 // Build a global pattern from the provided one and clean 14 const globalPattern = new RegExp( 15 allowedPattern.source.replace("^", "").replace("$", ""), 16 "g" 17 ); 18 // Remove everything that does NOT match the pattern 19 const inverted = new RegExp( 20 "[^" + allowedPattern.source.slice(1, -1) + "]", "g" 21 ); 22 this.value = this.value.replace(inverted, ""); 23 }); 24 }
Usage:
1 // Digits only 2 restrictInput(document.getElementById("phone"), /^[0-9]$/); 3 // Latin letters only without spaces 4 restrictInput(document.getElementById("login"), /^[a-zA-Z]$/); 5 // Letters and digits 6 restrictInput(document.getElementById("postal"), /^[a-zA-Z0-9]$/);
The function doesn't depend on jQuery, works in all modern browsers, and covers three scenarios: manual input (keydown), paste (input), autofill (input).
Note: keydown doesn't fire on virtual keyboards on some mobile devices; there input remains the only safeguard. That's why two handlers together is not redundancy but necessity.
The video above is a breakdown of form validation in plain JavaScript from Web Dev Simplified. It covers the built-in Constraint Validation API, custom error messages, and styling invalid fields, a great complement to the approaches described here.
⁉️🤔 Frequently asked questions
Why not just use type="number"?
type="number"allows enteringeandE(exponent), plus, minus, and period. All of these are valid characters for floating-point numbers and exponential notation. For a phone field or postal code, they're unnecessary. Plus, browsers handle non-numeric input differently: some silently let it through, others show an error only on submit.
Can you completely block pasting via Ctrl+V?
Blocking paste entirely is bad practice. It breaks user experience and doesn't protect against other paste methods (context menu, drag-and-drop). The correct approach: allow paste, but immediately clean the result via an
inputhandler, as shown in the section above.
What about browser autofill?
Autofill fires before
keydownandinput; the browser inserts the value directly. Theinputhandler catches this case too: the value appears in the field, then gets filtered immediately. If autofill is interfering (inserting letters into a phone field), add theautocomplete="off"attribute to the INPUT.
Does this work on mobile devices?
Yes, but with a caveat: virtual keyboards don't always generate
keydownwith expectedkeyCodeandkey. Theinputhandler works reliably everywhere. On mobile, setinputmode="numeric"orinputmode="decimal"on the INPUT; the user will get the correct keyboard even if thekeydownfilter doesn't fire.
How do you disallow spaces?
Add space to the disallowed character class:
replace(/[^0-9a-zA-Z]/g, "")removes everything except digits and Latin letters, including spaces. Or explicitly:this.value = this.value.replace(/\s/g, "")will remove all spaces, tabs, and line breaks.
Which approach to choose for your task

If the form is simple and a couple of wrong characters won't cause critical harm, use HTML5: type, pattern, and inputmode will cover basic scenarios without a single line of JS. Fast, native, no dependencies.
If real-time control and instant reaction to every keystroke matters, use the keydown + input combination. The first blocks keystrokes, the second cleans up paste consequences. The universal template function from the section above adapts to any field in a minute.
The main rule: don't block paste entirely. It annoys users and doesn't provide real protection. Clean the result after paste rather than prohibiting it.



