Skip to main content

Server API: Getters & usage

Page summary:

Access plugin resources through top-level getters (strapi.plugin('my-plugin').service('name')) or global getters (strapi.service('plugin::my-plugin.name')). Both return the same object. Use top-level getters inside your own plugin, and global getters from application code or other plugins. Routes have no global getter equivalent. Configuration uses dedicated configuration APIs.

Plugin server resources, such as controllers, services, policies, middlewares, and content-types, are accessible from any server-side location through the strapi instance: other plugins, lifecycle hooks, application controllers, or custom scripts. Routes and configuration use dedicated APIs — see the getter reference below.

Prerequisites

Before diving deeper into the concepts on this page, please ensure you have:

Getter styles

Strapi exposes 2 styles for accessing plugin resources. Both return the same underlying object, the difference is purely syntactic.

Top-level getters chain through the plugin name:

strapi.plugin('plugin-name').service('service-name')
strapi.plugin('plugin-name').controller('controller-name')

Global getters use the full UID directly on the strapi instance:

strapi.service('plugin::plugin-name.service-name')
strapi.controller('plugin::plugin-name.controller-name')

The choice is a matter of context and readability:

  • Inside your own plugin, top-level getters are more concise and make the plugin boundary explicit.
  • From application code or another plugin, global getters read more naturally alongside api:: UIDs.

2 resources are exceptions:

  • routes (strapi.plugin('plugin-name').routes) have no global getter equivalent,
  • and configuration uses dedicated config APIs (strapi.plugin('plugin-name').config() and strapi.config.get(...)) rather than resource getters.

Full getter reference

The following table lists all available getters for a plugin named todo with a resource named task:

ServiceControllerContent-typePolicyMiddlewareRoutesConfiguration
Top-levelstrapi.plugin('todo').service('task')strapi.plugin('todo').controller('task')strapi.plugin('todo').contentType('task')strapi.plugin('todo').policy('is-owner')strapi.plugin('todo').middleware('audit-log')strapi.plugin('todo').routesstrapi.plugin('todo').config('featureFlag')
Globalstrapi.service('plugin::todo.task')strapi.controller('plugin::todo.task')strapi.contentType('plugin::todo.task')strapi.policy('plugin::todo.is-owner')strapi.middleware('plugin::todo.audit-log')strapi.config.get('plugin::todo.featureFlag')

Both styles return the same underlying object. Routes have no global getter equivalent. Configuration uses dedicated config APIs rather than resource getters, both forms read the same merged value.

Tip

Run yarn strapi console or npm run strapi console to inspect the strapi object in a live console and explore available plugins and their resources interactively.

Usage examples

Calling a plugin service from a controller

The most common pattern: a controller delegates to its own plugin's service:

/src/plugins/todo/server/src/controllers/task.js
'use strict';

module.exports = ({ strapi }) => ({
async find(ctx) {
const tasks = await strapi.plugin('todo').service('task').findAll(); // top-level getter: preferred inside your own plugin
ctx.body = tasks;
},

async create(ctx) {
const task = await strapi
.plugin('todo')
.service('task')
.create(ctx.request.body);
ctx.status = 201;
ctx.body = task;
},
});

Calling a plugin service from bootstrap

Services called in bootstrap() have access to the full strapi instance, including other plugins' services:

/src/plugins/todo/server/src/bootstrap.js
'use strict';

module.exports = async ({ strapi }) => {
// Call own plugin service to seed initial data
const count = await strapi.plugin('todo').service('task').count();

if (count === 0) {
await strapi.plugin('todo').service('task').create({
title: 'Welcome task',
done: false,
});
}
};

Calling across plugins or from application code

From application-level controllers or services (outside the plugin), or when calling from another plugin, global getters using the full UID are often clearer:

/src/api/project/controllers/project.js
'use strict';

const { createCoreController } = require('@strapi/strapi').factories;

module.exports = createCoreController('api::project.project', ({ strapi }) => ({
async create(ctx) {
const { data, meta } = await super.create(ctx);

await strapi.service('plugin::todo.task').create({ // global getter: preferred in application code
title: `Review project: ${data.attributes.name}`,
done: false,
});

return { data, meta };
},
}));

Reading plugin configuration at runtime

// Read a single key
const maxItems = strapi.plugin('todo').config('maxItems');
// Read the full config object
const todoConfig = strapi.config.get('plugin::todo');
// Read a nested key
const endpoint = strapi.config.get('plugin::todo.endpoint');
Note

strapi.plugin('my-plugin').config('key') reads the merged configuration (user overrides applied on top of plugin defaults). It is the recommended way to read config inside plugin code. See Server configuration for how plugin configuration is declared and merged.

Accessing a content-type schema

Use the content-type getter when you need the schema object, for example to pass it to the sanitization API:

// Access the content-type schema
const schema = strapi.contentType('plugin::todo.task');

const sanitizedOutput = await strapi.contentAPI.sanitize.output(
data,
schema,
{ auth: ctx.state.auth }
);

Common errors

  • Naming mismatch between route handler and controller key. If your route declares handler: 'task.find', your controllers index must export a key called task and that controller must have a method called find. A mismatch throws a runtime error when the route is matched.

  • Misusing the policy context argument. The first argument to a policy function is a policy context object, not a raw Koa ctx. It wraps the request context but exposes a different interface. Naming it ctx in your code won't cause an error, but treating it as a Koa context (for example, calling ctx.body or ctx.status) will not work as expected. Use policyContext.state to access auth state, and call return false or throw a PolicyError to block the request.

  • Calling a service at module load time. The strapi object is not initialized when modules are first loaded. Always call getters inside a function body. Never call them at the top level of a module file.

  • Using an incomplete UID in global getters. strapi.service('todo.task') is not a valid plugin UID. Use the full plugin::todo.task form. Without the proper namespace, the service call fails or returns undefined at runtime.

    ScopeExample UID
    Plugin serviceplugin::todo.task
    API serviceapi::project.project

Best practices

  • Prefer top-level getters inside your own plugin. strapi.plugin('my-plugin').service('task') is more readable than the global form when both are inside the same plugin.

  • Use global getters in application code and cross-plugin calls. When calling from src/api/ or from another plugin, the full UID plugin::todo.task makes the dependency explicit and is easier to search for.

  • Access services in services, not at declaration time. Avoid capturing service references in closures at module initialization. Always resolve them at call time using the getter, to ensure Strapi is fully loaded.