request.completeUrl contains unencoded spaces
What you'll see
A canonical tag, an og:url, a sitemap entry or a Location header comes out containing literal spaces. Browsers often paper over it, so it survives a manual click-through and fails where nothing is watching: crawlers, link checkers, social preview fetchers and anything that compares the canonical against the requested URL.
Measured on a page stored at /Test files/zz-inventory.hash:
request.completeUrl // https://example.net/Test files/zz-inventory <- spaces
request.encodedUrl // https://example.net/Test-files/zz-inventory <- slugified The name reads like the most complete and therefore the most correct option. It is the most complete and the least suitable for output.
What's actually happening
There are eight URL members on request, and they are not eight variations on a theme. They are two axes crossed: raw or slugified, and relative or absolute.
| Member | Value for /Test files/zz-inventory.hash |
|---|---|
url, Url | /Test-files/zz-inventory |
urlpath | /Test-files/zz-inventory |
rawurl, rawUrl | /Test files/zz-inventory |
completeUrl | https://example.net/Test files/zz-inventory |
encodedUrl, encodedFileUrl | https://example.net/Test-files/zz-inventory |
siteUrl | https://example.net/ |
The capitalised pairs are aliases, not variants: url and Url carry the same value, as do rawurl and rawUrl. Either spelling works.
The slugified forms are what the site actually serves — a folder named Test files is reachable at /Test-files/. The raw forms mirror the names on the drive, and are legitimate for matching against a drive path.
completeUrl is the exception, and it is a defect rather than a design. It is meant to be the complete form of the URL the site serves, so it should slugify the folder and file names exactly as url and encodedUrl do. It currently does not, which is why it lands in the raw column of the table above. That has been reported and a fix is pending — read this entry as a description of current behaviour, not of intended behaviour.
Note also that the file-system members keep the extension while the URL members drop it: filepath is /Test files/zz-inventory.hash, folderpath is /Test files/. Those are drive paths, not links — see Omit file extensions in links.
One related surprise sits next to this. The bare globals site, host, domain and module are all null, which sends people looking for a way to reconstruct the host by hand. There is no need: request.sitename is example.net and request.siteUrl is https://example.net/.
What to do
1. Choose by destination, not by name.
| You want | Use |
|---|---|
A link, canonical, og:url, sitemap entry or Location header | request.encodedUrl |
| A site-relative link in markup | request.url |
| The host or site root | request.sitename, request.siteUrl |
| To read or write the file behind this request | request.filepath, request.folderpath |
| To compare against a stored path | Pick one family and stay in it — raw or slugified, never a mix |
2. Do not use completeUrl in output while the defect stands. Once it slugifies correctly it becomes interchangeable with encodedUrl here, and this precaution can be dropped.
3. Do not encode it yourself afterwards. Running completeUrl through an encoder produces %20 where the site serves a hyphen, so the result is a valid URL that resolves to nothing. The slug is a rename, not an escape. See Do not build file and image URLs by hand.
4. Do not reach for host or domain. They are null, and because an unresolved or null name renders as an empty string rather than raising, a template that uses one produces a URL missing its host with no error anywhere. See Unresolved names in hash expressions render empty.
5. Print the members rather than guessing between them. Which one you have is a one-request question — see Use a scratch hash file to test Docly functions.