URLs, Path and Query Parameters

A URL is the address of a resource, and its parameters are inputs written into that address. In https://shop.example.com/stores/riyadh-01/products?q=green%20tea&page=2, the path parameter riyadh-01 picks the store, and the query parameters q and page set the search term and the page of results. %20 is an encoded space, because a URL cannot contain a literal one.

The lesson on HTTP methods paired every request with a target. This lesson opens that target: the parts of a URL in RFC 3986, path and query parameters, encoding, and how to test each parameter.

Why it matters

Parameters are input fields that the interface hides. Search terms, IDs, filters and page numbers travel in the URL, so they need the same test design as a form field. Handled badly, a search for "tea & coffee" returns the wrong products, a filter is silently ignored, or changing /orders/1001 to /orders/1002 shows another customer's order: Broken Object Level Authorization, the first risk in the OWASP API Security Top 10 2023.

Editing a URL is also the fastest way to try an input or reproduce a report, and the exact encoded URL is the most precise evidence a bug report can carry.

The five parts of a URL

RFC 3986 defines the generic syntax of a URI in five parts: scheme://authority/path?query#fragment. Each part ends at a fixed delimiter, so you can split any URL by hand; the WHATWG URL Standard, which browsers follow, splits it the same way.

The client uses the scheme and authority to connect, sends the path and query as the request target, and puts the host in the Host header. The fragment never leaves the browser, so no server log or API test sees it.

  • Scheme: https or http; it also sets the default port, 443 or 80.
  • Authority: the host, such as shop.example.com, and an optional :port. Scheme and host are not case-sensitive.
  • Path: /stores/riyadh-01/products, segments separated by / that identify the resource.
  • Query: everything after ? up to #.
  • Fragment: everything after #, such as #reviews: a position in the page or a route inside a single-page app.
In this product search URL the path parameter riyadh-01 picks the store, q and page refine the list, and the #reviews fragment never reaches the server.

The URL in the address bar

Part 1:https://Part 2:shop.example.comPart 3:/stores/Part 4:riyadh-01Part 3:/products?Part 5:q=green%20tea&Part 6:page=2Part 7:#reviews

The request line the server receives

GET Part 3:/stores/Part 4:riyadh-01Part 3:/products?Part 5:q=green%20tea&Part 6:page=2 HTTP/1.1

The Host header

Host: Part 2:shop.example.com

  • 3–6Request target: the path and the query
  • 5–6Query: key=value pairs joined by &
  1. 1SchemeSets the default port, 443 for https, so no :port is written
  2. 2Authority (host)Used to connect, then sent in the Host header; not case-sensitive
  3. 3Fixed path segmentsThe same for every store in the template /stores/{storeId}/products
  4. 4Path parameter storeIdPicks the store, so it is required; an unknown store should get 404required
  5. 5Query parameter qThe search term, percent-encoded; the server decodes it to:green tea
  6. 6Query parameter pageUsually optional with a default, so leaving it out is a test of its ownoptional
  7. 7FragmentHandled by the browser only, so it is missing from the request linenot sent
Text version
https://shop.example.com/stores/riyadh-01/products?q=green%20tea&page=2#reviews

scheme      https
authority   shop.example.com             (no port: 443, the https default)
path        /stores/riyadh-01/products
query       q=green%20tea&page=2
fragment    reviews                      (stays in the browser)

What the server receives:
GET /stores/riyadh-01/products?q=green%20tea&page=2 HTTP/1.1
Host: shop.example.com

Try it: URL Parser

Path parameters: which resource

RFC 3986 only knows path segments. "Path parameter" is the API-design name for a segment whose value changes per request: the documentation writes a template, /stores/{storeId}/products, and each request fills it in. OpenAPI marks it in: path; websites use the same idea for slugs like /products/green-tea-500g.

A path parameter identifies the resource, so it is required: /stores//products reaches another route or none. A wrong type, such as /orders/abc for a numeric ID, should get a 4xx client error, not a 500; an unknown ID should get 404; and another user's ID must never return that user's data. An encoded slash %2F in the value is handled differently by different servers, so ask what is intended and test it.

Query parameters: how to read the resource

The query refines the request: search, filter, sort, paginate. RFC 3986 only defines where it starts and ends; the key=value pairs joined by & come from the HTML form format application/x-www-form-urlencoded, defined in the WHATWG URL Standard and followed by most frameworks. Query parameters are usually optional with defaults, so an absent parameter is behaviour too. Each of these is a different input:

  • ?page=2: a key with a value.
  • ?page= or ?page: present but empty, not the same as leaving page out.
  • ?tag=tea&tag=gifts: a repeated key. No standard says which value wins; frameworks take the first, the last or all of them.
  • ?Page=2: many frameworks compare keys case-sensitively, so this may be an unknown key that is silently ignored.

Try it: Query String Parser

Percent-encoding: keeping values intact

RFC 3986 reserves the characters that structure a URL: : / ? # [ ] @ separate the parts, and ! $ & ' ( ) * + , ; = separate data inside them. The letters A to Z and a to z, the digits 0 to 9 and - . _ ~ never need encoding. Every other character in a value, including a space, should be percent-encoded: each byte becomes % and two hexadecimal digits.

Characters outside ASCII, the basic set of 128 Latin letters, digits, symbols and control codes, are first turned into bytes with UTF-8, the character encoding of the web: an ASCII character takes one byte, any other character two to four. The Arabic word قهوة (coffee) is four letters of two bytes each, so it is sent as eight encoded bytes: %D9%82%D9%87%D9%88%D8%A9. Encode each value, never the whole URL, or / and ? turn into %2F and %3F and the URL breaks.

  • + means a space only in form-encoded data, which is how most servers read a query; in a path it is a plus sign. Encode a real plus as %2B, or +966… arrives as a space followed by 966….
  • Encode once and decode once: %2520 is a space encoded twice, and a server that decodes twice may let an encoded character slip past its input validation.
Value           Encoded query value
green tea       green%20tea
tea & coffee    tea%20%26%20coffee
50%             50%25
#1 seller       %231%20seller
+966500000000   %2B966500000000
قهوة            %D9%82%D9%87%D9%88%D8%A9

Try it: URL Encoder Try it: URL Decoder

Worked example: testing a product list URL

Requirement: GET /api/v1/stores/{storeId}/products lists a store's products. q is an optional search term; sort is price_asc (the default) or price_desc; page is an integer from 1, default 1; limit is an integer from 1 to 50, default 20. An unknown store gets 404; an invalid parameter value gets 400 with a message naming the parameter.

Two test design techniques do most of the work, and each has its own lesson that covers it in depth: equivalence partitioning splits a parameter's values into partitions, groups that the system should treat the same way, such as valid and invalid limit values, and tests one value from each; boundary value analysis then tests the value at each edge of a partition and its nearest neighbour across that edge, such as limit=50 and limit=51. Apply both to each parameter, then add the URL cases: missing, empty, repeated, unknown and encoded values. Expected results:

  • No query: 200, page 1, at most 20 products, cheapest first.
  • Unknown store riyadh-99: 404, not an empty list or a 500.
  • limit=50 is accepted; limit=51 gets 400 with a message naming limit. Add limit=1 and limit=0 for the lower boundary.
  • page=0, page=abc and sort=newest: 400, never a 500 or an undocumented fallback.
  • The searches must decode to "tea & coffee" and قهوة and return matching products. Repeat them in the user interface and check the DevTools Network tab: a page that joins strings sends q=tea%20&%20coffee, and the server reads q as "tea " plus a stray key.
  • Repeated page and unknown debug: the expected result must come from the documentation; if nothing is written, ask.
GET /api/v1/stores/riyadh-01/products
GET /api/v1/stores/riyadh-99/products
GET /api/v1/stores/riyadh-01/products?limit=50
GET /api/v1/stores/riyadh-01/products?limit=51
GET /api/v1/stores/riyadh-01/products?page=0
GET /api/v1/stores/riyadh-01/products?page=abc
GET /api/v1/stores/riyadh-01/products?sort=newest
GET /api/v1/stores/riyadh-01/products?q=tea%20%26%20coffee
GET /api/v1/stores/riyadh-01/products?q=%D9%82%D9%87%D9%88%D8%A9
GET /api/v1/stores/riyadh-01/products?page=2&page=3
GET /api/v1/stores/riyadh-01/products?debug=true

Try it: API Query Parameter Builder Try it: Special Character Dataset Generator

Common mistakes

  • Building URLs by joining strings, so a value containing &, #, + or % breaks the request or changes its meaning.
  • Encoding the whole URL instead of each value, or encoding a value twice.
  • Testing only valid values, never missing, empty, wrong-type, boundary, repeated or unknown ones.
  • Expecting the server to see the fragment: everything after # stays in the browser.
  • Accepting tokens, passwords or personal data in a query string: URLs end up in server and proxy logs and in browser history, and RFC 9110 warns against putting sensitive information in them.
  • Trusting a path parameter because the interface never shows other IDs, instead of checking that the server refuses another user's resource.

The QA perspective

When a requirement or API reference introduces a parameter, ask: is it required, what is its default, which values and limits are allowed, is it case-sensitive, and what happens when it is missing, empty, repeated or unknown? Each answer becomes an expected result; each "nobody knows" becomes a question before coding starts.

In test cases and bug reports, record the full URL as sent, copied from the Network tab or API client rather than retyped, plus the decoded value of any parameter that matters. Swapping a path parameter to reach another user's data is authorised testing only: use your own test accounts in an environment you may test, and report a leak through the team's security process.

Practice exercise

Read and test an order list URL

Your task

A store manager says the order list looks wrong and sends the URL below. Above it is the endpoint's reference: the endpoint returns one store's orders to that store's signed-in manager.

Work on paper, then check your answers with the URL Parser and the URL Encoder.

Material
GET /stores/{storeId}/orders
  storeId    path    required   example: riyadh-01
  status     query   optional   new | paid | shipped    default: all
  customer   query   optional   free text
  page       query   optional   integer >= 1            default: 1
  errors     400 for an invalid parameter value, 404 for an unknown storeId

https://admin.example.com/stores/riyadh-01/orders?status=paid&page=0#list
Steps
  1. Split the URL into its five parts; which one never reaches the server?
  2. Name the parameters and check each value against the reference.
  3. Encode a customer filter for "Al Noor & Sons".
  4. List eight tests with expected results.
Show the model answer
Model answer

Scheme https; authority admin.example.com (default port 443); path /stores/riyadh-01/orders; query status=paid&page=0; fragment list, which stays in the browser.

Path parameter storeId = riyadh-01; query parameters status = paid and page = 0, which is below the minimum of 1: expect 400. If the list loaded anyway, that may be the reported defect.

customer=Al%20Noor%20%26%20Sons (form encoding also accepts Al+Noor+%26+Sons). Unencoded, the & ends the value, and the server reads customer as "Al Noor " plus a stray key " Sons".

Tests: (1) no query: all statuses, page 1. (2) each allowed status: only matching orders. (3) status=cancelled: 400. (4) page=-1, page=abc: 400. (5) page=1 and the last page: orders. (6) encoded customer with &, %, + and Arabic letters: whole-name match. (7) unknown store riyadh-99: 404. (8) another test account's store: 403 or 404, no order data. Ask what a repeated status should do.

Frequently asked questions

What is the difference between a URL and a URI?

A URI identifies a resource. RFC 3986 calls a URI a URL when it also says how to locate the resource, for example with the https scheme and a host. Both use the same generic syntax, and in web testing the two words are used almost interchangeably.

Is + in a URL a space or a plus sign?

It depends on where it is. In a query read as form data (application/x-www-form-urlencoded), + is a space; in the path it is a plus sign. Write a real plus as %2B and a space as %20, and the value means the same everywhere.

Are URLs case-sensitive?

The scheme and host are not: HTTPS://Shop.Example.com and https://shop.example.com are the same. RFC 3986 treats the other parts as case-sensitive, but each server decides how it matches them, so /Products may work, redirect or return 404. Test it and record the behaviour the team chooses.

How long can a URL be?

No standard sets one maximum. RFC 9110 recommends that senders and recipients support URIs of at least 8000 octets, and browsers, servers and proxies each apply their own limits. A server that refuses a URL for its length should answer 414 URI Too Long. Ask for your system's limit and test just below and above it.

References

  • RFC 3986: Uniform Resource Identifier (URI): Generic SyntaxIETF · 2005The parts of a URI (scheme, authority, path, query, fragment), reserved characters and percent-encoding.
  • URL StandardWHATWGHow browsers actually parse, serialize and encode URLs, including application/x-www-form-urlencoded.
  • RFC 9110: HTTP SemanticsIETF · 2022The meaning of HTTP methods, header fields, status codes and content negotiation, independent of the wire version.
  • OWASP API Security Top 10 (2023)OWASP Foundation · 2023The ten most common API weaknesses: broken object-level authorization, broken authentication, unrestricted resource consumption and more.

Practise with Mutqan tools

Break a URL into protocol, host, port, path, query parameters, fragment and credentials.

Web

Parse and build query strings, including repeated keys and bracket-array notation.

Web

Percent-encode text for safe use in URLs, query strings and form data.

Encoding

Decode percent-encoded URLs and query strings back to readable text.

Encoding

Build correctly encoded query strings for API calls with pagination, filtering and sorting parameters.

Web

Go deeper

Your progress on this topic

Part of the QA Roadmap