hapi — How to Fix “X must return an error, a takeover response, or a continue signal”

Migrating an existing hapi app to hapi v17 might reveal errors that you didn’t see before. This tutorial focuses on errors that focus on lifecycle methods where you must return an error, a takeover response, or a continue signal.

If you ran into this issue, let’s fix it!

hapi Series Overview

Explicitly Return in hapi V17

The shift to async/await in hapi v17 requires you to return values from lifecycle methods. You don’t have the reply callback anymore. Tell hapi how to handle requests by returning individual values from lifecycle methods, like route handlers.

Lifecycle Methods Called Before the Handler

Lifecycle methods are an essential part of the hapi framework. They provide you the request and h response toolkit and optionally an error object. You also know lifecycle methods from extension points or the failAction method for validations.

Let’s use the failAction as an example. You might want to render the related view of your route handler, but with error details to point out issues to the user.

If you return from the failAction method, tell hapi to send your response from failAction right away without touching the route handler. Do this by chaining .takeover() on your custom response that you send with the response toolkit h.

failAction: (request, h, error) => {  
  const email = request.payload.email
  const errors = ['your', 'list', 'of', 'errors', ':)']

  return h
    .view('join', {
    .takeover()    // <-- this is the important line

You can surely use any kind of response, like JSON (h.response(json).code(400).takeover()) or an HTML view (see code snippet) with a custom HTTP status code.

onRequest Extension Methods

You can extend hapi’s request lifecycle with a dedicated lifecycle method. Depending on the extension point, hapi calls it before a route handler.

A special case for this is the onRequest extension point. Returning from that needs to be a takeover response (by using h.takeover()), an error or the continue signal.

The code snippet below illustrates a redirect for URL paths having a trailing slash to the URL pendant without the trailing slash.

const _ = require('lodash')

server.ext('onRequest', (request, h) => {  
  if (_.endsWith(request.url.path, '/')) {
    // redirect to the url without slash
    const url = _.trimEnd(request.url.path, '/')

    return h
      .takeover()    // <-- this is the important line

  // continue requests without trailing slash
  return h.continue

Tell hapi to send your custom response from onRequest and do not proceed the actual request lifecycle.

In contrast, requests without a trailing slash on the URL path proceed the request lifecycle.


Hapi is flexible when it comes to sending responses from different parts of the request lifecycle. Use the h.takeover() and tell the framework to send the response without further processing of other lifecycle steps.

Explore the Library

Find interesting tutorials and solutions for your problems.