Skip to main content
doc0blade47/honodocumentation home
Sign in

Trailing Slash and Override

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

The trailing slash and method override middleware modules provide essential URL normalization and protocol adaptation capabilities for Hono applicationsTrailing Slash and Override. The trimTrailingSlash and appendTrailingSlash functions normalize incoming request paths by either stripping or appending trailing slashes, supporting both unconditional redirects and fallback 404 redirection with configurable skip filterssrc/middleware/trailing-slash/index.ts:44-157. Concurrently, the methodOverride middleware enables clients to emulate RESTful mutations via standard HTML forms using custom header detection, query parameters, or form body inspection with _method field resolutionsrc/middleware/method-override/index.ts:11-134. Together, these utilities streamline routing consistency and bridge browser form limitations across web standard runtimes.

Sources: src/middleware/method-override/index.ts:11-134, src/middleware/trailing-slash/index.ts:44-157

Trailing Slash Redirection and Rewriting

Normalization Mechanics

The trailing slash middleware implementations (trimTrailingSlash and appendTrailingSlash) enforce canonical URL structures by inspecting incoming request paths and issuing permanent HTTP 301 redirects. Both factories accept configuration options governing whether redirection occurs prior to handler execution or strictly as a fallback upon encountering a 404 Not Found response status. Redirections are restricted exclusively to GET and HEAD request methods, and root paths (/) are explicitly excluded from path trimming. When alwaysRedirect is disabled, the middleware awaits downstream handler execution (await next()), intercepting responses that yield a 404 status before modifying c.res with a redirected URL instance.

Sources: src/middleware/trailing-slash/index.ts:44-74, src/middleware/trailing-slash/index.ts:128-157

Note

Setting alwaysRedirect: true evaluates the normalization condition before calling downstream handlers, which is required when using wildcard (*) routes that would otherwise match unnormalized paths and prevent 404 fallback triggers.

Sources: src/middleware/trailing-slash/index.ts:8-16, src/middleware/trailing-slash/index.ts:76-84

Options and Skip Filters

The appendTrailingSlash middleware supports a skip callback function allowing specific paths to bypass redirection rules, such as requests targeting files with extensions (/\.\w+$/).

OptionTypeDefaultDescription
alwaysRedirectbooleanfalseRedirects requests before handler execution instead of waiting for a 404 response status.
skip(path: string) => booleanundefinedDetermines whether to bypass the trailing slash redirection for a specific request path.

Sources: src/middleware/trailing-slash/index.ts:76-101

HTTP Method Override Execution Flow

Middleware Configuration Options

The methodOverride middleware factory accepts a configuration object defining the targeted application instance and the mechanism used to detect override values. The configuration type enforces mutual exclusivity across inspection strategies, ensuring that exactly one detection vector—form, header, or query—is specified per middleware instance. When no override key is explicitly provided for form submissions, the default field name _method is used. Requests arriving with the GET method bypass inspection entirely and immediately invoke next().

OptionTypeDefaultDescription
appHono<any, any, any>(Required)The Hono application instance used to dispatch the mutated request.
formstring'_method'Form field name containing the override method name when inspecting request bodies.
headerstringundefinedHTTP header name containing the override method name.
querystringundefinedQuery parameter key containing the override method name.

Sources: src/middleware/method-override/index.ts:11-33, src/middleware/method-override/index.ts:60-64

Inspection and Request Mutation Strategies

When an override condition is met, the middleware clones the incoming request, strips the override indicator from the payload, headers, or query string, and instantiates a new Web Standard Request object with the overridden HTTP method. This newly constructed request is then dispatched directly into the configured Hono application instance using app.fetch(request, c.env, getExecutionCtx(c)).

Warning

When processing multipart/form-data payloads, the middleware explicitly deletes both content-type and content-length headers from the cloned headers collection before constructing the new Request instance so the runtime can correctly re-calculate them for the boundary-delimited body.

Sources: src/middleware/method-override/index.ts:68-134, src/middleware/method-override/index.ts:138-146

For header-based overrides, the specified header name is extracted from the incoming request, deleted from a cloned headers collection, and supplied as the method parameter to the dispatched request. For query-based overrides, the target query parameter is removed from the URL search parameters while preserving the original request body and headers. If no override value is detected by the active inspection branch, the middleware falls back to calling next().

Sources: src/middleware/method-override/index.ts:106-136

Form Body Parsing and Resolution

Call-Chain Execution Walkflows

The form body parsing utility supports both flat form values and complex dot-notation structures. When resolving form data, the execution follows specific call chains depending on whether all values or nested structures are processed.

The first execution sequence handles parsing all values into arrays or flat fields:

  1. methodOverride invokes parseBody when processing application/x-www-form-urlencoded or multipart/form-data requests.
  2. parseBody inspects the content type header and delegates to parseFormData.
  3. parseFormData calls (request as Request).formData() and passes the resulting FormData object to convertFormDataToBodyData.
  4. convertFormDataToBodyData iterates over entries, checking whether options.all or a [] key suffix applies, and invokes handleParsingAllValues.
  5. handleParsingAllValues assigns or pushes multiple values into array structures on the target object.

Sources: src/middleware/method-override/index.ts:92-94, src/utils/body.ts:94-108, src/utils/body.ts:121-132, src/utils/body.ts:142-170, src/utils/body.ts:179-197

The second execution sequence handles nested keys when options.dot is enabled:

  1. methodOverride invokes parseBody for form submissions.
  2. parseBody forwards the request to parseFormData.
  3. parseFormData retrieves form data and calls convertFormDataToBodyData.
  4. convertFormDataToBodyData identifies keys containing dot characters when options.dot is active and triggers handleParsingNestedValues.
  5. handleParsingNestedValues splits the key string by dots, guards against prototype pollution, and instantiates nested objects for intermediate keys.

Sources: src/middleware/method-override/index.ts:92-94, src/utils/body.ts:94-108, src/utils/body.ts:121-132, src/utils/body.ts:142-170, src/utils/body.ts:206-233

Sources: src/middleware/method-override/index.ts:92-94, src/utils/body.ts:94-108, src/utils/body.ts:121-132, src/utils/body.ts:142-170, src/utils/body.ts:179-197, src/utils/body.ts:206-233

Body Parsing Options and Security Protections

The body parsing utility accepts configuration options that dictate how multi-value fields and dot-notation keys are structured in the returned data object.

OptionTypeDefaultDescription
allbooleanfalseParses all fields with multiple values or trailing [] brackets as arrays.
dotbooleanfalseParses dot-notation keys into nested JavaScript objects.

Sources: src/utils/body.ts:42-74

Caution

The handleParsingNestedValues function explicitly tests keys against the regular expression /(?:^|\.)__proto__\./ and immediately returns without assigning values if a prototype pollution attempt is detected.

Sources: src/utils/body.ts:206-213

JSX Form Action Integration

Form Component and Server Actions

Hono's JSX implementation supports progressive enhancement for forms through function-valued actions. The form functional component intercepts submission events when client-side DOM rendering is active, inspecting whether the event is trusted and evaluating the action handler. If the action is a function, form prevents default browser submission, extracts form entries into a FormData instance, updates the internal pending and dirty state via useState, and invokes the action function. If the action returns a Promise, it registers the asynchronous operation using registerAction and awaits its resolution.

export const form: FC<
  PropsWithChildren<{
    action?: Function | string
    method?: 'get' | 'post'
    ref?: RefObject<HTMLFormElement> | ((e: HTMLFormElement | null) => void | (() => void))
  }>
> = (props) => {
  const { action, ...restProps } = props
  if (typeof action !== 'function') {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ;(restProps as any).action = action
  }
 
  const [state, setState] = useState<[FormData | null, boolean]>([null, false]) // [FormData, isDirty]
  const onSubmit = useCallback<(ev: SubmitEvent | CustomEvent) => void>(
    async (ev: SubmitEvent | CustomEvent) => {
      const currentAction = ev.isTrusted
        ? action
        : (ev as CustomEvent).detail[customEventFormAction]
      if (typeof currentAction !== 'function') {
        return
      }
 
      ev.preventDefault()
      const formData = new FormData(ev.target as HTMLFormElement)
      setState([formData, true])
      const actionRes = currentAction(formData)
      if (actionRes instanceof Promise) {
        registerAction(actionRes)
        await actionRes
      }
      setState([null, true])
    },
    []
  )
 
  const ref = composeRef(props.ref, (el: HTMLFormElement) => {
    el.addEventListener('submit', onSubmit)
    return () => {
      el.removeEventListener('submit', onSubmit)
    }
  })
 
  const [data, isDirty] = state
  state[1] = false
  return {
    tag: FormContext as unknown as Function,
    props: {
      value: {
        pending: data !== null,
        data,
        method: data ? 'post' : null,
        action: data ? action : null,
      },
      children: {
        tag: 'form',
        props: {
          ...restProps,
          ref,
        },
        type: 'form',
        ref,
      },
    },
    f: isDirty,
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
  } as any
}

Sources: src/jsx/dom/intrinsic-element/components.ts:319-386

Note

When server-side rendering is used instead of the client DOM renderer, the form component checks for a PERMALINK symbol on function actions to resolve string endpoints for non-JS environments.

Sources: src/jsx/intrinsic-element/components.ts:182-192

Form Actionable Elements

Interactive elements inside forms such as inputs and buttons can specify individual action handlers via formAction. The helper function formActionableElement intercepts clicks on elements carrying a function-valued formAction. When clicked, it prevents the default click behavior, locates the parent form element, and dispatches a custom submit event carrying the action function inside detail keyed by a private symbol.

const formActionableElement = (
  tag: string,
  {
    formAction,
    ...props
  }: {
    formAction?: Function | string
    ref?: RefObject<HTMLInputElement> | ((e: HTMLInputElement) => void | (() => void))
  }
) => {
  if (typeof formAction === 'function') {
    const onClick = useCallback<(ev: MouseEvent) => void>((ev: MouseEvent) => {
      ev.preventDefault()
      ;(ev.currentTarget! as HTMLInputElement).form!.dispatchEvent(
        new CustomEvent('submit', { detail: { [customEventFormAction]: formAction } })
      )
    }, [])
 
    props.ref = composeRef(props.ref, (el: HTMLInputElement) => {
      el.addEventListener('click', onClick)
      return () => {
        el.removeEventListener('click', onClick)
      }
    })
  }
 
  return {
    tag,
    props,
    type: tag,
    ref: props.ref,
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
  } as any
}
 

Sources: src/jsx/dom/intrinsic-element/components.ts:388-422

Important

The custom event mechanism relies on customEventFormAction symbol keys to securely pass function references across DOM boundaries without leaking private handlers into standard attribute serialization.

Sources: src/jsx/dom/intrinsic-element/components.ts:318-337, src/jsx/dom/intrinsic-element/components.ts:388-403

Edge Runtime and Middleware Packaging

Module Export Layout and Subpath Imports

Hono exposes its built-in utility middleware and runtime features through fine-grained subpath exports defined in package.json. Each subpath provides separate type definitions (types), ESM imports (import), and CommonJS requires (require) to support bundlers, Node.js, and edge runtimes seamlessly.

Subpath ExportTypes EntryESM Import EntryCommonJS Entry
./powered-by./dist/types/middleware/powered-by/index.d.ts./dist/middleware/powered-by/index.js./dist/cjs/middleware/powered-by/index.js
./pretty-json./dist/types/middleware/pretty-json/index.d.ts./dist/middleware/pretty-json/index.js./dist/cjs/middleware/pretty-json/index.js
./trailing-slash./dist/types/middleware/trailing-slash/index.d.ts./dist/middleware/trailing-slash/index.js./dist/cjs/middleware/trailing-slash/index.js
./method-override./dist/types/middleware/method-override/index.d.ts./dist/middleware/method-override/index.js./dist/cjs/middleware/method-override/index.js

Sources: package.json:138-142, package.json:233-247

Lightweight Utility Middleware Packaging

Utility middleware such as poweredBy and prettyJSON are implemented as zero-dependency factories returning standard MiddlewareHandler closures. The poweredBy middleware accepts an optional serverName configuration string defaulting to 'Hono' and appends the X-Powered-By response header after downstream handlers execute.

export const poweredBy = (options?: PoweredByOptions): MiddlewareHandler => {
  return async function poweredBy(c, next) {
    await next()
    c.res.headers.set('X-Powered-By', options?.serverName ?? 'Hono')
  }
}

Sources: src/middleware/powered-by/index.ts:30-35

Note

The prettyJSON middleware inspects response content types and query parameters according to its PrettyOptions, allowing formatting overrides via a configurable query parameter or the force flag.

Sources: src/middleware/pretty-json/index.ts:8-26, src/middleware/pretty-json/index.ts:46-55

Was this page helpful?