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.
Before diving deeper into the concepts on this page, please ensure you have:
- created a Strapi plugin,
- read and understood the basics of the Server API
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()andstrapi.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:
| Service | Controller | Content-type | Policy | Middleware | Routes | Configuration | |
|---|---|---|---|---|---|---|---|
| Top-level | strapi.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').routes | strapi.plugin('todo').config('featureFlag') |
| Global | strapi.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.
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:
- JavaScript
- TypeScript
'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;
},
});
import type { Context } from 'koa';
import type { Core } from '@strapi/strapi';
type TaskService = {
findAll(): Promise<unknown[]>;
create(data: unknown): Promise<unknown>;
};
export default ({ strapi }: { strapi: Core.Strapi }) => ({
async find(ctx: Context) {
// Narrow cast: plugin services require app-level type augmentation for full typing.
const tasks = await (strapi.plugin('todo').service('task') as TaskService).findAll();
ctx.body = tasks;
},
async create(ctx: Context) {
const task = await (strapi.plugin('todo').service('task') as TaskService).create(
(ctx.request as any).body
);
(ctx as any).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:
- JavaScript
- TypeScript
'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,
});
}
};
import type { Core } from '@strapi/strapi';
type TaskService = {
count(): Promise<number>;
create(data: unknown): Promise<unknown>;
};
export default async ({ strapi }: { strapi: Core.Strapi }) => {
// Narrow cast: plugin services are resolved dynamically unless your project augments Strapi service typings.
const taskService = strapi.plugin('todo').service('task') as TaskService;
const count = await taskService.count();
if (count === 0) {
await taskService.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:
- JavaScript
- TypeScript
'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 };
},
}));
import { factories } from '@strapi/strapi';
type TaskService = {
create(data: unknown): Promise<unknown>;
};
export default factories.createCoreController(
'api::project.project',
({ strapi }) => ({
async create(ctx: any) {
const { data, meta } = await super.create(ctx);
// Narrow cast: this generic documentation cannot infer your app-specific service signatures.
await (strapi.service('plugin::todo.task') as TaskService).create({
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');
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:
- JavaScript
- TypeScript
// 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 }
);
const schema = strapi.contentType('plugin::todo.task'); // access the content-type schema
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 calledtaskand that controller must have a method calledfind. 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 itctxin your code won't cause an error, but treating it as a Koa context (for example, callingctx.bodyorctx.status) will not work as expected. UsepolicyContext.stateto access auth state, and callreturn falseor throw aPolicyErrorto block the request. -
Calling a service at module load time. The
strapiobject 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 fullplugin::todo.taskform. Without the proper namespace, the service call fails or returnsundefinedat runtime.Scope Example UID Plugin service plugin::todo.taskAPI service api::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 UIDplugin::todo.taskmakes 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.