skip to content

In a Cypress `cy.intercept()` URL glob, what do `*`, `**` and `?` mean?

level: middleimportance: should knowfreq 55%

answer

  1. URL strings are patterns, not substrings
  2. One star and two stars differ at the slash
  3. One glob character is a single-character wildcard
  4. Query strings need an escape in the pattern
  5. A Cypress utility tests patterns without a run

basics

~20 s

Cypress glob-matches URL strings with minimatch: one star matches within a single path segment, two stars match across segments, and a question mark is a single-character wildcard, so a literal query-string question mark must be written as an escaped backslash-question in JavaScript.

solid answer

~40 s

A string URL passed to `cy.intercept()` is a **minimatch glob**, applied with `{ matchBase: true }` against the full request URL and then, as a fallback, against the path. `*` matches any characters inside one path segment and stops at a `/`; `**` matches across segments, so `/api/forecast/**` covers `/api/forecast/KSEA/hourly` while `/api/forecast/*` does not. `?` is a **single-character wildcard**, not a literal question mark — to match the `?` that starts a query string, escape it, writing `'\?'` in the JavaScript string so minimatch receives `\?`. Alternation groups such as `+(PUT|PATCH)` work too. Where a glob stops being readable, pass a `RegExp` instead; `url`, `path`, `pathname`, `hostname` and `method` all accept one. Test any pattern with `Cypress.minimatch()` before committing it.

code

javascript · 6 lines
javascript
it('checks forecast URL patterns', () => {
  expect(Cypress.minimatch('/api/forecast/KSEA', '/api/forecast/*', { matchBase: true })).to.be.true
  expect(Cypress.minimatch('/api/forecast/KSEA/hourly', '/api/forecast/*', { matchBase: true })).to.be.false
  expect(Cypress.minimatch('/api/forecast/KSEA/hourly', '/api/forecast/**', { matchBase: true })).to.be.true
  expect(Cypress.minimatch('/api/stations?limit=5', '/api/stations\\?limit=*', { matchBase: true })).to.be.true
})

go deeper

for a junior

Recall that the URL you hand cy.intercept is a pattern, and that a trailing star is what lets one route cover a path plus its query string.

for a middle

Explain the segment boundary between a single and a double star, and why the question mark has to be escaped to match a real query string.

for a senior

Demonstrate settling a misbehaving pattern with Cypress.minimatch rather than by trial-and-error re-runs, and know when to abandon a glob for a regular expression.

for a principal

Own the readability tradeoff: globs that everyone on the team can read versus regular expressions that are precise but become a maintenance cost as the API surface grows.

When a Cypress `cy.intercept()` route is given a URL **string**, that string is not a substring test. Cypress compares it for equality, then hands it to the **minimatch** glob library with the `{ matchBase: true }` option, first against the full request URL and then, as a fallback, against the request path. The same library is exposed on the Cypress global as `Cypress.minimatch`, so any pattern you are about to put in a spec can be tried out first. ## The wildcard set | Token | Meaning | Against `/api/forecast/KSEA/hourly` | | --- | --- | --- | | `*` | any characters **within one path segment** | `/api/forecast/*` does **not** match | | `**` | any number of characters **across** segments | `/api/forecast/**` matches | | `?` | exactly one character, any character | `/api/forecast/KSE?/hourly` matches | | `+(a\|b)` | one of the listed alternatives | `method: '+(PUT\|PATCH)'` matches either verb | The distinction between `*` and `**` is the one that decides most patterns in practice. `*` stops at a `/`, so `/api/forecast/*` covers `/api/forecast/KSEA` but not `/api/forecast/KSEA/hourly`. `**` walks straight through separators, so `/api/forecast/**` covers both and everything deeper. ## The question mark is a wildcard, not a literal This is the single most-hit trap in Cypress glob patterns, and it matters because almost every interesting weather-dashboard URL has a query string: - In minimatch syntax `?` matches **any single character**. It does not mean "here comes the query string". - To match the literal `?` that introduces a query string you must escape it, and because you are writing the pattern inside a JavaScript string you write two backslashes: `'\\?'` reaches minimatch as `\?`. - Cypress's own documentation spells this out: write `cy.intercept('/api/stations\\?limit=*')`, not `cy.intercept('/api/stations?limit=*')`. - The same escape applies inside `Cypress.minimatch()` when you check the pattern by hand. ## Where the pattern is applied A pattern is tried against the **full URL** — protocol, hostname and all — and, if that fails, against the **path**, meaning everything after the hostname including the query string. Two consequences follow: 1. A host-less pattern such as `/api/forecast*` works even though it names no hostname, because of the path fallback. That is what makes a suite portable between `localhost`, staging and production. 2. When you verify such a pattern with `Cypress.minimatch()`, pass **just the path**, not the full URL. Handing the full URL to a path-only pattern can report `false` even though `cy.intercept()` would have matched through the fallback. ## When a RegExp is the better tool The `url` argument also accepts a `RegExp`, and so do the other string matcher fields — `path`, `pathname`, `hostname`, `method`, and the individual values inside `query` and `headers`. A regular expression is tested against the value directly, with the same full-URL-then-path fallback for `url`. Reach for one when: - The condition is character-level rather than segment-level: anchoring an end (`/\/api\/forecast$/`), or matching a numeric station id. - You need real alternation with capture-free grouping across a whole URL rather than a single segment. - The query string is the thing you are matching on and escaping every `?`, `&` and `=` in a glob has become unreadable. Globs stay easier to read for the common case — a path prefix with a wildcard tail — so most specs use a glob and drop to a `RegExp` only where the glob stops being obvious. ## Debugging a pattern `Cypress.minimatch(target, pattern, options)` returns a boolean and can be called straight from the browser console of the Cypress runner, so a pattern that is not behaving can be settled in seconds instead of by editing the spec and re-running: - Remember to pass `{ matchBase: true }`, because that is what `cy.intercept()` applies under the hood. - Pass `{ debug: true }` to get verbose output explaining how the pattern was compiled. - `Cypress.minimatch()` is a utility on the `Cypress` object, not a command; `cy.minimatch()` does not exist and errors.

  • Why does Cypress pass `{ matchBase: true }` to minimatch, and when does that option change the result?
    `matchBase` makes a pattern containing no slashes match against the last segment of the target instead of the whole string, so a bare `forecast.json` pattern still matches a deep URL. It has no effect once your pattern contains a `/`, which most intercept patterns do — but you must pass it to `Cypress.minimatch()` yourself to reproduce what `cy.intercept()` does.
  • Can a Cypress routeMatcher field other than `url` take a regular expression?
    Yes. `path`, `pathname`, `hostname` and `method` are string matchers just like `url`, and so is each value inside the `query` and `headers` dictionaries, so any of them accepts a glob string or a `RegExp`. Only `url` gets the extra full-URL-then-path fallback; the others are tested against their own parsed value.

Minimatch globs think in path segments the way shell filename wildcards do: a single star stops at a slash and a double star walks through them. A regular expression ignores that structure entirely and matches raw characters.

saying these in an interview costs you the question

  • Thinks a URL string is matched as a plain substring
  • Uses a bare question mark expecting a literal query separator
  • Believes a single star crosses path separators
  • Assumes globs and regular expressions cannot both be used
  • Calls cy.minimatch instead of Cypress.minimatch