<p>This is definitely a sub-optimal workaround, but it might work:</p>
<p>Have your criteria in the API config fetch something you know is there, and there is only one of - your homepage, or a global set (maybe a global set that's called <code>errorMessages</code>?)</p>
<p>Then do the normal</p>
<pre><code>$criteria = craft()-&gt;elements-&gt;getCriteria(ElementType::Entry);
$criteria-&gt;section = 'sectionHandle';
// maybe some more criteria in here
$entries = $criteria-&gt;find();
</code></pre>
<p>and return your desired message if <code>$entries</code> is <code>null</code>.</p>

CC BY-SA 3.0
2016-01-13T19:16:17.323Z
Thanks megatrond... This is somewhat helpful, though doesn't exactly resolve the problem. I edited my question to elaborate the use-case. I think your solution could potentially be used to achieve the desired effect... but as you said, it's a "sub-optimal workaround". At that rate, I'd wonder why I wasn't just using a JSON-formatted template in the first place?
Lindsey D
CC BY-SA 3.0
2016-01-14T08:23:26.493Z
I agree with you, and so I [forked](https://github.com/megatrond/ElementAPI) the ElementApi and added a config param called `handleNotFound`, which whould be a function, and return whatever you'd like if an element is not found. It is only used when `first` is enabled. If no handler is provided, a 404 will be thrown.
megatrond
CC BY-SA 3.0
2016-01-14T08:48:19.390Z
Very interesting... thanks for the fork link!
Lindsey D
CC BY-SA 4.0
2016-01-13T08:26:37.393Z
2022-11-15T02:42:42.953Z
2022-11-15T02:42:42.953Z
megatrond
<p>Currently, Element API is hard coded to throw a 404 exception for single element endpoints, if the element criteria returns no results. There's no way to prevent this exception, add "fallback" criteria or to display an error message.</p>

<p>Note that the 404 exception will only be thrown if the <code>first</code> parameter is set to <code>true</code>, though. If it isn't, Element API will just return an empty dataset.</p>

<p>Obviously, omitting the <code>first</code> parameter is not a real solution to your issue. You still have no way to set a custom error message, and it would also mean that all single element endpoints would return arrays, as well as paging metadata etc, instead of a "flat" JSON object.</p>

<p>On that note, my opinion is that the canonical answer to this question should probably be to <strong>fork the Element API plugin</strong> and/or add a <a href="http://feedback.craftcms.com/forums/285221-feature-requests" rel="nofollow">feature request</a>.</p>

<p>However, if you don't want to go down that road, here's a possible workaround. It's not pretty :)</p>

<p>Basically, what you'll need to do is to catch the <code>HttpException</code>, in order to return a custom payload.</p>

<p>To do this, you'll need to write a custom plugin. Important: <strong>make sure that the string returned by your plugin's <code>getName()</code> method comes before "Element API", alphabetically</strong>. The reason for this is that you need to make sure that Craft loads your plugin before the Element API plugin (Craft loads plugins alphabetically, by name). I'll pretend you opt for the name "Awesome Plugin", which would work nicely.</p>

<p>Your plugin will need a controller, so create one called "AwesomeController" or the like. This controller should have a method called <code>actionGetElements</code>, taking the same arguments as the ElementApiController's <code>actionGetElements</code> method.</p>

<p>The only purpose of your controller is to "wrap" the ElementApiController, in order to catch the 404 HttpException fired by Element API, and echo a custom error message. Here's how that could look:</p>

<pre><code>&lt;?php
namespace Craft;

class AwesomeController extends BaseController
{

    protected $allowAnonymous = true;

    public function actionGetElements($configFactory = null, array $config = null)
    {
        try {

            // Fire up the Element API controller
            craft()-&gt;runController('elementApi/getElements');

        } catch (HttpException $e) {

            switch ($e-&gt;statusCode) {

                case 404 :

                    // It's a 404. Output a custom error message
                    JsonHelper::sendJsonHeaders();
                    echo JsonHelper::encode(array(
                        'error' =&gt; 'No element found',
                    ));
                    // End the request
                    craft()-&gt;end();

            }
        }
    }

}
</code></pre>

<p>Obviously, the controller will do nothing by itself – the final thing you need to do is to "hijack" the Element API routes; making them point to your controller instead of the ElementApiController. To achieve this, you'll need to create a method called <code>registerSiteRoutes()</code> in your plugin's primary class. Inside this method, you'll basically want to pull all the routes from the Element API plugin, changing their <code>action</code> value to <code>awesomePlugin/getElements</code>.</p>

<p>This is why it's important to make sure that your plugin is loaded by Craft before the Element API plugin: when an existing route is added to Craft, it'll be ignored. This means that for every endpoint in <code>/config/elementapi.php</code>, you can trick Craft into loading your custom controller instead of ElementApiController!:</p>

<p>Here's how that <code>registerSiteRoutes()</code> method could look:</p>

<pre><code>public function registerSiteRoutes()
{

    $routes = [];

    // Get the Element API plugin
    $elementApiPlugin = craft()-&gt;plugins-&gt;getPlugin('elementApi');

    if (!$elementApiPlugin) {
        return $routes;
    }

    // Get the Element API routes
    $elementApiRoutes = $elementApiPlugin-&gt;registerSiteRoutes();

    // Set action property
    foreach ($elementApiRoutes as $endpoint =&gt; $config) {
        $config['action'] = 'awesomePlugin/getElements';
        $routes[$endpoint] = $config;
    }

    return $routes;

}
</code></pre>

<p>All in all – not exactly an elegant solution; but if the 404's are a dealbreaker to you, its at least a working workaround that doesn't require you to tamper with the Element API source, and the performance footprint should be minimal, too (no redundant database queries). </p>

CC BY-SA 3.0
2016-01-14T17:51:40.967Z
Great answer, thank you! It still seems like a lot of workaround code to solve a (theoretically) simple problem. I'm going to submit a formal feature request... I feel strongly that the plugin should natively handle the situation more gracefully.
Lindsey D
CC BY-SA 3.0
2016-01-14T18:05:40.503Z
Per your suggestion, I've added a formal feature request... Please vote if you agree! [Element API plugin: Better handling of NULL criteria results](http://feedback.craftcms.com/forums/285221-feature-requests/suggestions/11448138-element-api-plugin-better-handling-of-null-criter)
Lindsey D
CC BY-SA 3.0
2016-01-14T18:13:36.333Z
I completely agree and voted for your request :)
Mats Mikkel Rummelhoff
CC BY-SA 3.0
2016-01-14T18:41:27.083Z
+1 for the ridiculous workaround, though. :)
Brad Bell
CC BY-SA 3.0
2016-01-14T19:11:11.257Z
@BradBell Where there's a will, there's a ridiculous way :)
Mats Mikkel Rummelhoff
CC BY-SA 3.0
2016-01-14T17:39:34.117Z
2016-01-14T19:12:20.657Z
2016-01-14T19:12:20.657Z
Mats Mikkel Rummelhoff
<p>Per this code example, it's clear that the API is expecting to return results based on an <strong>existing element</strong>...</p>

<pre><code>'transformer' =&gt; function(EntryModel $entry) {
    ...
}
</code></pre>

<p>And when the criteria finds a matching entry, everything works great!</p>

<p>But when there is <strong>no matching criteria results</strong>, the API seems to simply return a generic 404 error.</p>

<p><strong>How can I make the API return a message (in JSON format) instead of a 404?</strong></p>

<hr>

<p><strong>EDIT:</strong></p>

<p><a href="https://craftcms.stackexchange.com/a/13194/45">The solution provided by @megatrond</a> is helpful, but doesn't really nail the problem here. I'm actually using that workaround for a different problem I encountered... I needed to guarantee that the user is authorized to ping my endpoint. In implementing that solution, I created a unique access key, included it in the URL, and checked against it before potentially returning a JSON-formatted error message. Under those circumstances, I was able to <strong>run a useless query</strong> for the sake of allowing me to return JSON.</p>

<p>However, for the issue in <em>this thread</em>... The criteria will either return some results, or no results, and it's not possible to tell before allowing the query to run.</p>

<p>Consider this scenario...</p>

<p>Someone uses my endpoint to target the entry with id 42. Assuming entry #42 exists, the endpoint will successfully return data for it. But, if entry #42 has been <strong>deleted or disabled</strong>, the endpoint doesn't give me the opportunity to return an error message. Instead, it just kicks out a "404 Not Found" error. :(</p>

<p>Ideally, I'm looking for <strong>a fallback for when the criteria returns zero results.</strong></p>

<404><plugin-elementapi>
Element API plugin - Return message when no results are found
# Element API plugin - Return message when no results are found

(Asked by: username_0 on 2016-01-12)

Per this code example, it's clear that the API is expecting to return results based on an existing element...
'transformer' => function(EntryModel $entry) {
    ...
}

And when the criteria finds a matching entry, everything works great!
But when there is no matching criteria results, the API seems to simply return a generic 404 error.
How can I make the API return a message (in JSON format) instead of a 404?

EDIT:
The solution provided by @username_1 is helpful, but doesn't really nail the problem here. I'm actually using that workaround for a different problem I encountered... I needed to guarantee that the user is authorized to ping my endpoint. In implementing that solution, I created a unique access key, included it in the URL, and checked against it before potentially returning a JSON-formatted error message. Under those circumstances, I was able to run a useless query for the sake of allowing me to return JSON.
However, for the issue in this thread... The criteria will either return some results, or no results, and it's not possible to tell before allowing the query to run.
Consider this scenario...
Someone uses my endpoint to target the entry with id 42. Assuming entry #42 exists, the endpoint will successfully return data for it. But, if entry #42 has been deleted or disabled, the endpoint doesn't give me the opportunity to return an error message. Instead, it just kicks out a "404 Not Found" error. :(
Ideally, I'm looking for a fallback for when the criteria returns zero results.

**Tags:** 404, plugin-elementapi


---

## 2 answers

### Answer by username_2 on 2016-01-14. Score: 6 | Accepted answer

Currently, Element API is hard coded to throw a 404 exception for single element endpoints, if the element criteria returns no results. There's no way to prevent this exception, add "fallback" criteria or to display an error message.
Note that the 404 exception will only be thrown if the first parameter is set to true, though. If it isn't, Element API will just return an empty dataset.
Obviously, omitting the first parameter is not a real solution to your issue. You still have no way to set a custom error message, and it would also mean that all single element endpoints would return arrays, as well as paging metadata etc, instead of a "flat" JSON object.
On that note, my opinion is that the canonical answer to this question should probably be to fork the Element API plugin and/or add a feature request.
However, if you don't want to go down that road, here's a possible workaround. It's not pretty :)
Basically, what you'll need to do is to catch the HttpException, in order to return a custom payload.
To do this, you'll need to write a custom plugin. Important: make sure that the string returned by your plugin's getName() method comes before "Element API", alphabetically. The reason for this is that you need to make sure that Craft loads your plugin before the Element API plugin (Craft loads plugins alphabetically, by name). I'll pretend you opt for the name "Awesome Plugin", which would work nicely.
Your plugin will need a controller, so create one called "AwesomeController" or the like. This controller should have a method called actionGetElements, taking the same arguments as the ElementApiController's actionGetElements method.
The only purpose of your controller is to "wrap" the ElementApiController, in order to catch the 404 HttpException fired by Element API, and echo a custom error message. Here's how that could look:
runController('elementApi/getElements');

        } catch (HttpException $e) {

            switch ($e->statusCode) {

                case 404 :

                    // It's a 404. Output a custom error message
                    JsonHelper::sendJsonHeaders();
                    echo JsonHelper::encode(array(
                        'error' => 'No element found',
                    ));
                    // End the request
                    craft()->end();

            }
        }
    }

}

Obviously, the controller will do nothing by itself – the final thing you need to do is to "hijack" the Element API routes; making them point to your controller instead of the ElementApiController. To achieve this, you'll need to create a method called registerSiteRoutes() in your plugin's primary class. Inside this method, you'll basically want to pull all the routes from the Element API plugin, changing their action value to awesomePlugin/getElements.
This is why it's important to make sure that your plugin is loaded by Craft before the Element API plugin: when an existing route is added to Craft, it'll be ignored. This means that for every endpoint in /config/elementapi.php, you can trick Craft into loading your custom controller instead of ElementApiController!:
Here's how that registerSiteRoutes() method could look:
public function registerSiteRoutes()
{

    $routes = [];

    // Get the Element API plugin
    $elementApiPlugin = craft()->plugins->getPlugin('elementApi');

    if (!$elementApiPlugin) {
        return $routes;
    }

    // Get the Element API routes
    $elementApiRoutes = $elementApiPlugin->registerSiteRoutes();

    // Set action property
    foreach ($elementApiRoutes as $endpoint => $config) {
        $config['action'] = 'awesomePlugin/getElements';
        $routes[$endpoint] = $config;
    }

    return $routes;

}

All in all – not exactly an elegant solution; but if the 404's are a dealbreaker to you, its at least a working workaround that doesn't require you to tamper with the Element API source, and the performance footprint should be minimal, too (no redundant database queries).

> **Comments:**
> **username_0**: Great answer, thank you! It still seems like a lot of workaround code to solve a (theoretically) simple problem. I'm going to submit a formal feature request... I feel strongly that the plugin should natively handle the situation more gracefully.  
> **username_0**: Per your suggestion, I've added a formal feature request... Please vote if you agree! [Element API plugin: Better handling of NULL criteria results](http://feedback.craftcms.com/forums/285221-feature-requests/suggestions/11448138-element-api-plugin-better-handling-of-null-criter)  
> **username_2**: I completely agree and voted for your request :)  
> **username_3**: +1 for the ridiculous workaround, though. :)  
> **username_2**: @BradBell Where there's a will, there's a ridiculous way :)  

---

### Answer by username_1 on 2016-01-13. Score: 1

This is definitely a sub-optimal workaround, but it might work:
Have your criteria in the API config fetch something you know is there, and there is only one of - your homepage, or a global set (maybe a global set that's called errorMessages?)
Then do the normal
$criteria = craft()->elements->getCriteria(ElementType::Entry);
$criteria->section = 'sectionHandle';
// maybe some more criteria in here
$entries = $criteria->find();

and return your desired message if $entries is null.

> **Comments:**
> **username_0**: Thanks username_1... This is somewhat helpful, though doesn't exactly resolve the problem. I edited my question to elaborate the use-case. I think your solution could potentially be used to achieve the desired effect... but as you said, it's a "sub-optimal workaround". At that rate, I'd wonder why I wasn't just using a JSON-formatted template in the first place?  
> **username_1**: I agree with you, and so I [forked](https://github.com/username_1/ElementAPI) the ElementApi and added a config param called `handleNotFound`, which whould be a function, and return whatever you'd like if an element is not found. It is only used when `first` is enabled. If no handler is provided, a 404 will be thrown.  
> **username_0**: Very interesting... thanks for the fork link!  

---

