When the server responds with an error code, like 422 for an invalid form or 500 for a crash, Unpoly renders the response with a separate set of options. This way a failed form submission re-renders the form with validation errors, instead of showing the next screen. You can also decide which responses count as errors.
Any HTTP status other than 2xx or 304 makes a response an error response. For an error response you can pass different render options than for a successful one.
A common use case is a form submission. A successful response should show a follow-up screen, but an error response should re-render the form with validation errors. A good HTTP status code for an invalid form submission is 422 (Unprocessable Content).
To use a different render option for an error response, prefix the option with fail:
up.render({
url: '/action',
method: 'post',
target: '.content', // when submission succeeds update '.content'
failTarget: 'form', // when submission fails update the form
scroll: 'auto', // when submission succeeds use default scroll behavior
failScroll: '.errors', // when submission fails scroll to the error messages
onRendered: () => { ... }, // when submission succeeds run this callback
onFailRendered: () => { ... }, // when submission fails run this other callback
})
Event handlers still begin with on, so { onRendered } becomes { onFailRendered }.
When using Unpoly's HTML attributes with links or forms,
infix the attribute with fail:
<form method="post" action="/action"
up-submit <!-- have Unpoly handle the form -->
up-target=".content" <!-- when submission succeeds update '.content' -->
up-fail-target="form" <!-- when submission fails update the form -->
up-scroll="auto" <!-- when submission succeeds use default scroll behavior -->
up-fail-scroll=".errors"> <!-- when submission fails scroll to the error messages -->
...
</form>
Tip
When Unpoly handles a form, the default
[up-fail-target]is the form itself. This makes sure that any validation errors appear within the<form>element, so you rarely need to set the attribute. See validating forms for examples.
Options that are used before the request is made, like { url, method, confirm }, have no fail-prefixed variant.
Some options, like { history, fallback }, are used for both successful and error responses,
but may be overridden with a fail-prefixed variant, e.g. { history: true, failHistory: false }.
Options related to layers, scrolling or focus are never shared.
An error response uses { failLayer, failScroll, failFocus }, or the configured defaults
when these are not given.
Unlike a form, a link has no natural fragment to show an error in.
When a link receives an error response, Unpoly updates the main fragment instead of the link's [up-target].
This way a server error page shows up in the main fragment, where the user expects it.
To re-render a different fragment, set an [up-fail-target] attribute.
A plain up.render() call has no default fail target. When the server responds with an error
and you passed neither { failTarget } nor { fallback }, the render pass rejects with an up.CannotMatch error.
With { fail: false } or [up-fail=false] Unpoly will always consider the response
to be successful, even with an HTTP 4xx or 5xx status code:
<a href="/missing-page" up-target=".content" up-fail="false">Show page</a> <!-- mark: up-fail="false" -->
The error page is now rendered into .content, as if the server had responded with 200 OK.
By default only the status code decides whether a response is an error response. You can also fail a response if it contains a given header or body text.
The following configuration fails all responses with an X-Unauthorized header:
let badStatus = up.network.config.fail
up.network.config.fail = (response) => badStatus(response) || response.header('X-Unauthorized') // mark: response.header('X-Unauthorized')
You can also decide to fail a response in an up:fragment:loaded listener:
up.on('up:fragment:loaded', function(event) {
if (event.response.header('X-Unauthorized')) {
event.renderOptions.fail = true // mark-line
}
})
When the new fragment content is passed as an HTML string ({ document, fragment, content })
instead of being requested from a { url }, the update is always considered successful.
Rendering functions like up.render(), up.follow() or up.submit() return a promise that rejects when the server
sends an error response, or when there is another error.
See Render error handling example.
In development, many server frameworks show a detailed error page when your code crashes, with a stack trace and sometimes an interactive console. When Unpoly renders that page into a fragment, it loses its own styles and scripts, and parts of it may not be shown at all.
To see the full error page, prevent the up:fragment:loaded event and repeat the request as a full page load:
// Returns true when your development server shows an error page.
function isDevelopmentError(response) {
return document.body.dataset.environment === 'development'
&& !response.ok
&& /backtrace|stack ?trace|traceback/i.test(response.text)
}
up.on('up:fragment:loaded', function(event) {
if (isDevelopmentError(event.response)) {
event.preventDefault() // don't render the error page into a fragment
event.request.loadPage() // repeat the request as a full page load
}
})
The code reads the environment from a [data-environment] attribute on the <body>.
Render that attribute in your layout. In Rails:
<body data-environment="<%= Rails.env %>">
Other frameworks have a similar way to print the current environment into your layout.
up.Request#loadPage() cannot repeat file uploads, so a form with a file field is sent again without its files.
When the server responds successfully, but with unexpected content like a login form, your target may be missing from the response. See Targeting fragments: Dealing with missing targets.
When a request gets no response at all, because the device is offline or the request timed out, nothing is rendered. See Handling network issues.
When a request is aborted, nothing is rendered either. See Aborting requests: Reacting to aborted requests.
Tip Your coding agent can search these docs locally with the Unpoly agent skill