Error Substates

Catch Failed Loads in error.hbs

Error Substates

When model() rejects, Ember renders the nearest error template with the rejection as `@model` — a clean way to show "post not found" or "server error".

4 min read Level 2/5 #ember#routing#errors
What you'll learn
  • Add an app/templates/error.hbs template
  • Display the error message and stack
  • Add a retry link that re-runs the model hook

When loading fails — the API is down, the record is missing, the user lacks permission — model() rejects. Ember’s error substate renders the nearest error.hbs with the rejection as @model.

Global Error Template

{{! app/templates/error.hbs }}
<section class="error">
  <h1>Something went wrong</h1>
  <p>{{@model.message}}</p>
  <button type="button" {{on "click" this.retry}}>Retry</button>
</section>

@model is the rejection — typically an Error. Show @model.message, status codes, or a generic message.

Per-Route Error Template

For the posts route, place a fallback at app/templates/posts/error.hbs:

{{! app/templates/posts/error.hbs }}
<h1>Couldn't load posts</h1>
<p>{{@model.message}}</p>
<LinkTo @route="index">Back to home</LinkTo>

More specific wins over global.

Programmatic Error Handling

Override error as a route action to log or redirect:

// app/routes/post.js
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { action } from '@ember/object';

export default class PostRoute extends Route {
  @service router;

  @action
  error(reason, transition) {
    if (reason?.status === 404) {
      this.router.transitionTo('not-found');
      return false;  // skip rendering the error substate
    }
    return true;     // bubble up and render error.hbs
  }
}

Return false to stop the bubble; return true (or nothing) to let the error substate render.

Transitions →