How to Create a Custom API Endpoint in Strapi

Strapi provides a set of powerful APIs to let the developers create custom API end-points. In this article, we will be creating a custom API endpoint from scratch.

This is a companion discussion topic for the original entry at

This is useful, is there a way I can lock this down so it’s only accessible by people logged in to the admin area?

Not sure if this is the “proper” way, but my solution for this was to send the strapi jwt with the API request and then validating it on the server side in a custom policy.

Hi Chrift, thanks for the question.

  • for limiting the route to authenticated users, we don’t really need to write a custom policy. From the Roles & permissions UI, instead of public, we can select the roles to be one of the authenticated roles.

  • yes, once the access is restricted to authenticated roles, we need to make an authenticated request.

I hope you find the links useful. In case of queries, please feel free to ask.

Update: We have a plugin now that attempts to give us a UI for some of the use cases:

Can you share the source code? I try to create a custom endpoint with a media type.When I get all return url images,



Thank you for this great tutorial. How do I change the design of the API endpoint from

*here are all the custom results*

to this structure:

"results": [ 
*here are all the custom results*

Looking forward to your answer!

from where did we get ```
console.log(‘pages-report’, strapi.service(‘api::pages-report.pages-report’)

should not this be 


I am getting the output in results, pagination format. Now I want to transform this to the default data, attributes format since the output of custom API is different from others.

this.transformResponse is not working in strapi new version.

Looking forward to the solution. I am stuck with this

1 Like

Hi, there is a way to format the custom API response to the format from the content type endpoints that wish to include the data and meta objects for pagination?


data [ {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, … ]
0 Object { id: 1, attributes: {…} }
1 Object { id: 2, attributes: {…} }
2 Object { id: 3, attributes: {…} }
3 Object { id: 4, attributes: {…} }
4 Object { id: 5, attributes: {…} }
5 Object { id: 6, attributes: {…} }
6 Object { id: 7, attributes: {…} }
7 Object { id: 8, attributes: {…} }
8 Object { id: 9, attributes: {…} }
9 Object { id: 10, attributes: {…} }
10 Object { id: 11, attributes: {…} }
11 Object { id: 12, attributes: {…} }
12 Object { id: 13, attributes: {…} }
13 Object { id: 14, attributes: {…} }
14 Object { id: 15, attributes: {…} }
15 Object { id: 16, attributes: {…} }
16 Object { id: 17, attributes: {…} }
17 Object { id: 54, attributes: {…} }
18 Object { id: 55, attributes: {…} }
meta Object { pagination: {…} }
pagination Object { page: 1, pageSize: 25, pageCount: 1, … }

Preferred to the extent the controller core from strapi factory or calling any object of function in strap not by plugins.

Thanks in advance


Thanks for this tutorial. I’m confused about where the strapi object comes from within the controller. For example, in this snippet, on line 4:

    module.exports = {
      async postsReport(ctx, next) {
        try {
          const data = await strapi // where'd this come from?
          console.log(data, "data");
          ctx.body = data;
        } catch (err) {
          ctx.badRequest("Post report controller error", { moreDetails: err });

At first I thought this was a typo in the code snippet as strapi appears to be an undefined global variable. But then I saw the same thing repeated in other docs and realized that, lo and behold, this just works:

export default {
  async index(ctx, next) {
    console.log(strapi); // Strapi { ... }
    // called by GET /hello
    ctx.body = "Hello World!"; // we could also send a JSON

Can somebody point me to any documentation for this magic global object?

I am just diving into Strapi and this really confused me. Especially since strapi is provided as an argument to the callback function when you use a factory to create a core controller – is that the same strapi that I logged above? why is it provided in a callback for core controllers but not custom ones?

Hi @vivmagarwal I was searching for creating custom api on meilisearch. Thankfully today I got this thread and checked your plugin. Is it possible to integrate custom api with meilisearch, so that instead of using meilisearch sdk native, all data communication happens via api layer.

Outside that is magically available globally, not much I can add. But if you wanted to see all the methods that it has, you can do so by running Strapi via console command as follows.

  yarn strapi console

Thaw will start Strapi in “interactive console” mode

Then you can type strapi and hit enter. It will spit out all methods that are available to you. A great way to explore the strapi object.

I believe it the future version of Strapi they are going to change it not to be a global object.