---
id: api/commands/url
title: cy.url()
description: Get the full URL of the page under test in Cypress as a string.
section: api
source_path: docs/api/commands/url.mdx
version: f60251bf707b0077a9535579c9f90d615440cc35
updated_at: '2026-10-16T10:56:58.670Z'
---
# url

Get the full URL of the page under test as a string.

`cy.url()` yields the same value as [`cy.location('href')`](/llm/markdown/api/commands/location.md).

## Syntax

```
cy.url()
cy.url(options)
```

### Usage

**Correct Usage**

```
cy.url() // Yields the current URL as a string
```

**Incorrect Usage**

```
const url = cy.url() // Yields a chainable, not the URL string
cy.get('a').url() // Ignores the link, yields the page's URL
```

Cypress commands are enqueued and run later, so `cy.url()` never returns the URL directly. To work with the string, read it in a [`.then()`](/llm/markdown/api/commands/then.md) callback or save it as an [alias](/llm/markdown/api/commands/as.md):

```
cy.url().then((url) => {
  // url is the current URL as a string
})

cy.url().as('startUrl')
```

### Arguments

**options _(Object)_**

Pass in an options object to change the default behavior of `cy.url()`.

**cy.url( _options_ )**

| Option | Default | Description |
| --- | --- | --- |
| `decode` | `false` | Decode the URL with `decodeURI()` before yielding it |
| `log` | `true` | Displays the command in the [Command log](/llm/markdown/app/core-concepts/open-mode.md#Command-Log) |
| `timeout` | [`defaultCommandTimeout`](/llm/markdown/app/references/configuration.md#Timeouts) | Time to wait for `cy.url()` to resolve before [timing out](#Timeouts) |

### Yields

*   `cy.url()` yields the current URL as a string.
*   `cy.url()` is a query, and it is _safe_ to chain further commands.

`cy.url()` yields the whole URL, labeled `href` in this diagram:

## Examples

### No Args

#### Assert the URL is `http://localhost:8000/users/1/edit`

```
// clicking the anchor causes the browser to follow the link
cy.get('#user-edit a').click()
cy.url().should('include', '/users/1/edit')
cy.url().should('eq', 'http://localhost:8000/users/1/edit')
```

### `decode` option

The browser percent-encodes non-ASCII characters in the URL. To assert on the readable text, pass `decode: true`.

```
// For the curious, '사랑' means 'love' in Korean.
cy.url({ decode: true }).should('contain', '사랑')
```

`decode` runs the URL through [`decodeURI()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI), which leaves escapes for reserved characters such as `/`, `?`, `&`, and `#` encoded. Given the remote URL `http://localhost:8000/search?q=caf%C3%A9%26bar`, the decoded URL keeps `%26`:

```
cy.url({ decode: true }).should(
  'eq',
  'http://localhost:8000/search?q=café%26bar'
)
```

| In the URL | With `decode: true` | Why |
| --- | --- | --- |
| `%EC%82%AC%EB%9E%91` | `사랑` | Non-ASCII characters are decoded |
| `%C3%A9` | `é` | Non-ASCII characters are decoded |
| `%26` | `%26` | `&` is reserved, so it stays encoded |
| `%2F` | `%2F` | `/` is reserved, so it stays encoded |

### Redirects and navigation

#### Wait for a redirect after signing in

`cy.url()` retries until its assertions pass, so the test waits for your application to finish redirecting without a [`cy.wait()`](/llm/markdown/api/commands/wait.md).

```
cy.get('#email').type('jane@example.com')
cy.get('#password').type('secret')
cy.contains('button', 'Sign in').click()
cy.url().should('include', '/dashboard')
```

In the Command Log, the `url` assertion passes on the dashboard URL, and the redirect's page load and new URL appear as their own rows:

#### Match a URL that contains a generated ID

When a URL includes a value your test doesn't know in advance, such as the ID of a record the test created, match it with a regular expression.

```
cy.contains('button', 'Create user').click()
cy.url().should('match', /\/users\/\d+\/edit$/)
```

#### Assert that the URL doesn't change

Save the URL before an action, then compare against it afterward. Here, the `required` field blocks the form from submitting, so the page stays where it is.

index.html

```
<form action="/saved">
  <input name="name" required />
  <button>Save</button>
</form>
```

spec.cy.js

```
cy.url().then((startUrl) => {
  cy.contains('button', 'Save').click()
  cy.url().should('eq', startUrl)
})
```

### Cross-origin redirects

#### Assert that sign-in redirects to an identity provider

Because `cy.url()` reads the URL [across origins](#Cross-origin-pages-and-cyurl), you can confirm where a sign-in flow landed before you interact with that page inside [`cy.origin()`](/llm/markdown/api/commands/origin.md).

```
cy.visit('http://localhost:8000/')
cy.contains('a', 'Sign in with SSO').click()

cy.url().should('include', 'http://auth.example.com/login')

cy.origin('http://auth.example.com', () => {
  cy.get('input[name="username"]').type('jane')
})
```

## Notes

### `cy.url()` versus `cy.location('href')`

`cy.url()` reads the current URL and yields its `href`, the same string `cy.location('href')` yields. Given the remote URL `http://localhost:8000/index.html`, all 3 of these assertions are the same:

```
cy.location('href').should('include', '/index.html')

cy.location().its('href').should('include', '/index.html')

cy.url().should('include', '/index.html')
```

`href` is the name the `window.location` spec gives the full URL. There is no `url` property on the `location` object, so `cy.url()` gives the full current URL the name most developers reach for first.

### Assert on part of the URL with `cy.location()`

`cy.url()` yields the whole URL, including the protocol, host, and port, so an assertion on it names the host. When you only care about the path or the query string, assert on that part with [`cy.location()`](/llm/markdown/api/commands/location.md) instead of hardcoding or slicing the full URL. The assertion then passes on any host, such as a local server, CI, or a preview deployment. The `pathname` and `search` labels in the URL diagram under [Yields](#Yields) show which part each one reads.

```
// given remote URL: http://localhost:8000/users/1/edit?tab=profile
cy.location('pathname').should('eq', '/users/1/edit')
cy.location('search').should('eq', '?tab=profile')
```

To keep the host out of [`cy.visit()`](/llm/markdown/api/commands/visit.md) as well, define a `baseUrl` in your [Cypress configuration](/llm/markdown/app/references/configuration.md). For more details on why, see our Best Practices guide on [setting a global `baseUrl`](/llm/markdown/app/core-concepts/best-practices.md#Setting-a-Global-baseUrl).

### Trailing `#` and `?` in the URL

The browser keeps a bare `#` or `?` at the end of the URL, so `cy.url()` yields it even though the hash or query string is empty. A link to `#` adds a trailing `#`:

index.html

```
<nav>
  <a href="#/users/1">Open profile</a>
  <a href="#">Close profile</a>
</nav>
```

spec.cy.js

```
cy.contains('a', 'Close profile').click()
cy.url().should('eq', 'http://localhost:8000/index.html#')
cy.hash().should('eq', '')
cy.location('pathname').should('eq', '/index.html')
```

A URL ending in `?` works the same way:

```
// given remote URL: http://localhost:8000/index.html?
cy.url().should('eq', 'http://localhost:8000/index.html?')
cy.location('search').should('eq', '')
```

To leave the trailing character out of the assertion, compare the `pathname` with `cy.location()`.

### Cross-origin pages and `cy.url()`

`cy.url()` reads the URL from the browser rather than from the page's `window`, so it yields the URL after your application navigates to another origin, without wrapping the command in [`cy.origin()`](/llm/markdown/api/commands/origin.md).

## Rules

### Requirements

*   `cy.url()` requires being chained off of `cy`.

### Assertions

*   `cy.url()` automatically [retries](/llm/markdown/app/core-concepts/retry-ability.md) until all chained assertions have passed

### Timeouts

*   `cy.url()` can time out waiting for assertions you've added to pass.

## Command Log

**_Assert that the URL includes `#users/new`_**

```
cy.url().should('include', '#users/new')
```

The commands above display in the Command Log as:

When clicking on `url` within the Command Log, the console outputs the following:

## History

| Version | Changes |
| --- | --- |
| [15.0.0](/llm/markdown/app/references/changelog.md#15-0-0) | Reads the URL through the browser's automation client instead of the page's `window` in Chromium-based browsers and Firefox |
| [8.4.0](/llm/markdown/app/references/changelog.md#8-4-0) | `decode` option added |
| [< 0.3.3](/llm/markdown/app/references/changelog.md#0-3-3) | `cy.url()` command added |

## See also

*   [`cy.hash()`](/llm/markdown/api/commands/hash.md)
*   [`cy.location()`](/llm/markdown/api/commands/location.md)
*   [`cy.origin()`](/llm/markdown/api/commands/origin.md)
