Use text.includes("code"). It returns true or false, it is case-sensitive, and it has been in every engine since ES2015. For the start or the end of a string use startsWith() and endsWith().
Before ES2015 the answer was text.indexOf("code") !== -1, and you will still see it in older code. Today each question has its own method: includes() tells you whether the text is there, startsWith() and endsWith() check one end, and indexOf() tells you where. None of them can ignore case or match a pattern, so the last two sections cover lowercasing, localeCompare() and regular expressions. Every example below runs on this page: hit Run, edit the code, and run it again.
1includes()Recommended
text.includes(search) returns a boolean, so it drops straight into an if with nothing to compare against. An optional second argument sets the index to start searching from. For the negative check, write !text.includes("x").
Output
Output: found it, true, false, false, false, true, false. The fourth line is the usual surprise: "Code" misses the lowercase code, because includes() compares exact characters (section 4 fixes that). The fifth is false because code starts at index 23 and the search began at 25. An empty needle always matches, so check for "" first if it comes from user input. Calling includes() on null throws a TypeError, which is why the last line uses ?. and ?? false.
2startsWith() and endsWith()
includes() matches anywhere, which is the wrong question for file extensions, URL schemes and path prefixes. startsWith() anchors the match to the start and endsWith() to the end. Both take an optional second argument: startsWith reads it as the index to begin at, and endsWith as the length to treat as the end of the string.
Output
Output: true, true, true false, true, true, true. The third line is why these methods exist: logo.png.txt is a text file, and only endsWith() gets that right. path.endsWith("logo", 11) is true because it only looks at the first 11 characters, images/logo. Neither method accepts an array, so for several candidates pass a callback to some(). Extensions can arrive in upper case, so lowercase the path first if .PNG should count.
3indexOf(), when you need the position
indexOf() returns the index of the first match, or -1 when there is none. lastIndexOf() searches from the end. Use them when you need the position, for example to slice around the match. Before includes() existed, indexOf(x) !== -1 was the contains check, and that idiom has one bug that keeps turning up.
Output
Output: 23, -1, 41, true, then missed Compile, which is at index 0, true, true. The if fails because indexOf() returns 0 for a match at the very start, and 0 is falsy, while a miss returns -1, which is truthy. Always compare against -1, or better, use includes(). The last two lines are the old startsWith and endsWith workarounds. They work, but the named methods say what you mean. You may also see ~text.indexOf(x) in old code, a bitwise trick that turns -1 into 0. It works, but it is hard to read.
4Ignoring case
includes(), startsWith(), endsWith() and indexOf() have no ignore-case option. To search, lowercase both sides and search that. To compare two whole strings, toLowerCase() on both works for plain text, but localeCompare() with a sensitivity option is the Unicode-aware answer: "accent" ignores case only, and "base" ignores accents too.
Output
Output: false, true, true, true, false, true, STRASSE, false, true, title tıtle. The two localeCompare lines on resume show the difference between the options: with "accent" the accents still count, so the result is false, and with "base" they don't, so it is true. The ß lines show why simple case mapping is not a full answer: Straße and STRASSE are equal in upper case but not in lower case. The last line is the Turkish problem: in the tr locale, a capital I lowercases to a dotless ı, so use toLocaleLowerCase() when you know the text's language. If you compare many strings, build one Intl.Collator with the same options and reuse its compare method.
5Regular expressions, when the needle is a pattern
When you are looking for a shape (a date, a number, a whole word) rather than fixed text, use a regular expression. regex.test(text) is the boolean check, text.search(regex) returns the position, and text.match(regex) returns what matched. The i flag ignores case and \b marks a word boundary.
Output
The first line prints TypeError: First argument to String.prototype.includes must not be a regular expression: includes(), startsWith() and endsWith() refuse regex arguments rather than quietly converting them to text. Then true, 6 91%, and true, false: \b keeps errors from counting as error. The escaping lines print true then false. Unescaped, each . in 1.5.0 matches any character, so v1x5y0 matches. Next, true for the keyword list, and last true false. With the g flag, test() resumes from lastIndex, so the second call on the same string fails. Drop g for yes-or-no checks. RegExp.escape is new in ES2025. On older engines, escape with s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").
6Which should you use?
| Method | Returns | Matches | Best for |
|---|---|---|---|
| text.includes("x") | true / false | Text, anywhere | The default check |
| text.startsWith("x") / endsWith("x") | true / false | Text, at one end | Extensions, schemes, prefixes |
| text.indexOf("x") | Index or -1 | Text, first match | When you need the position |
| text.toLowerCase().includes(...) | true / false | Text, ignoring case | Case-insensitive search |
| a.localeCompare(b, "en", { sensitivity }) | 0 when equal | Whole strings | Case- or accent-insensitive equality |
| /pattern/.test(text) | true / false | A pattern | Shapes, whole words, many needles |
Frequently asked questions
Does JavaScript have a string contains() method?
Not under that name. The method is includes(): "hello world".includes("world") returns true. It was drafted as String.prototype.contains and renamed to includes before ES2015 shipped, because old versions of the MooTools library already added their own contains and a built-in one would have broken sites. Calling text.contains("x") throws a TypeError because it is not a function.
Is includes() case-sensitive?
Yes. "CompileBytes Runs Your Code".includes("runs") is false. Lowercase both sides to ignore case: text.toLowerCase().includes(needle.toLowerCase()), or use a regex with the i flag, such as /runs/i.test(text). The same applies to startsWith(), endsWith() and indexOf().
How do I compare two strings ignoring case in JavaScript?
For plain text, a.toLowerCase() === b.toLowerCase() is enough. For Unicode text, use a.localeCompare(b, "en", { sensitivity: "accent" }) === 0, which ignores case but not accents, or sensitivity: "base" to ignore accents too, so "résumé" equals "resume". To compare many strings, create one Intl.Collator with the same options and reuse its compare method.
What is the difference between includes() and indexOf()?
On strings, both search for the same text. includes() returns true or false, and indexOf() returns the index of the first match or -1. Use includes() for a yes-or-no answer and indexOf() when you need the position. Never write if (text.indexOf("x")): a match at index 0 is falsy and a miss (-1) is truthy, so that check is wrong both ways.
Why does includes() throw a TypeError with a regular expression?
includes(), startsWith() and endsWith() only accept text, and the spec makes them throw when you pass a regex, so a pattern is never silently turned into the string "/ERROR/". To match a pattern, use /ERROR/.test(text), or text.search(regex) for the position.
How do I check if a string contains any of several substrings?
Put the needles in an array and use some(): needles.some((n) => text.includes(n)) returns true on the first match, and every() checks that all of them are present. needles.filter((n) => text.includes(n)) returns the ones that matched. For long lists, one regex built with needles.map(RegExp.escape).join("|") checks them all in a single pass.
Run it yourself
Open any of these in the full JavaScript editor to edit, run and share them.
JavaScript playground