# Bad value for attribute “action” on element “form”: Percentage ("%") is not followed by two hexadecimal digits.

> Canonical HTML version: https://rocketvalidator.com/html-validation/bad-value-for-attribute-action-on-element-form-percentage-is-not-followed-by-two-hexadecimal-digits
> Attribution: Rocket Validator (https://rocketvalidator.com)
> License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

In URLs, certain characters must be percent-encoded — a process where a character is replaced by `%` followed by exactly two hexadecimal digits representing its byte value. For example, a space becomes `%20`, a hash becomes `%23`, and an ampersand becomes `%26`. The `%` character itself is the escape prefix, so when a URL parser encounters `%`, it expects the next two characters to be valid hexadecimal digits (0–9, A–F, a–f). If they aren't, the URL is malformed.

This validation error typically arises in a few scenarios:

- **A literal `%` is used in the URL without encoding.** For instance, a filename or path segment contains a `%` sign that wasn't converted to `%25`.
- **An incomplete or corrupted percent-encoding sequence.** Someone may have partially encoded a URL, leaving behind sequences like `%G5` or `%2` that don't form valid two-digit hex codes.
- **Copy-paste errors.** A URL was pasted from a source that stripped characters or introduced stray `%` symbols.

This matters because browsers may interpret malformed URLs inconsistently. One browser might try to "fix" the URL by encoding the stray `%`, while another might pass it through as-is, leading to broken form submissions or unexpected server-side behavior. Ensuring valid URLs in the `action` attribute guarantees predictable behavior across all browsers and complies with both the [WHATWG URL Standard](https://url.spec.whatwg.org/) and the HTML specification.

## How to Fix

1. **Locate every `%` in the URL.** Check whether each `%` is followed by exactly two hexadecimal digits (e.g., `%20`, `%3A`, `%7E`).
2. **Encode literal `%` characters.** If a `%` is meant to appear as a literal character in the URL (not as part of a percent-encoding sequence), replace it with `%25`.
3. **Fix incomplete sequences.** If a sequence like `%2` or `%GZ` exists, determine the intended character and encode it correctly, or remove the stray `%`.
4. **Use proper encoding tools.** In JavaScript, use `encodeURIComponent()` or `encodeURI()` to safely encode URLs. Most server-side languages have equivalent functions (e.g., `urlencode()` in PHP, `urllib.parse.quote()` in Python).

## Examples

### Literal `%` not encoded

This triggers the error because `%d` is not a valid percent-encoding sequence (`d` is only one character, and what follows may not be hex):

```html
<!-- ❌ Bad: bare % not followed by two hex digits -->
<form action="/search?discount=20%off">
  <button type="submit">Search</button>
</form>
```

Encode the `%` as `%25`:

```html
<!-- ✅ Good: literal % encoded as %25 -->
<form action="/search?discount=20%25off">
  <button type="submit">Search</button>
</form>
```

### Incomplete percent-encoding sequence

Here, `%2` is missing its second hex digit:

```html
<!-- ❌ Bad: %2 is an incomplete sequence -->
<form action="/path/to%2file.html">
  <button type="submit">Submit</button>
</form>
```

If the intent was to encode a `/` character (`%2F`), complete the sequence:

```html
<!-- ✅ Good: complete percent-encoding for "/" -->
<form action="/path/to%2Ffile.html">
  <button type="submit">Submit</button>
</form>
```

### Invalid hex characters after `%`

The characters `G` and `Z` are not hexadecimal digits:

```html
<!-- ❌ Bad: %GZ is not valid hex -->
<form action="/data%GZprocess">
  <button type="submit">Go</button>
</form>
```

If `%GZ` was never intended as encoding, escape the `%` itself:

```html
<!-- ✅ Good: literal % properly encoded -->
<form action="/data%25GZprocess">
  <button type="submit">Go</button>
</form>
```

### Using JavaScript to encode safely

When building URLs dynamically, use built-in encoding functions to avoid this issue entirely:

```js
const query = "20% off";
const safeURL = "/search?q=" + encodeURIComponent(query);
// Result: "/search?q=20%25%20off"
```

This ensures every special character — including `%` — is properly percent-encoded before it's placed in the `action` attribute.
