Dosu LogoDosu Logo
Ask
Join our Discord
StrapiPublic
Strapi
DocumentsStrapi
services
services
Type
External
Status
Published
Created
Mar 5, 2026
Updated
Mar 11, 2026
Updated by
Dosu Bot
Source
View

Services#

Services store reusable functions to keep controllers concise and follow DRY principles. This documentation explains generating or extending services with `createCoreService` and organizing them for APIs or plugins.

Services are a set of reusable functions. They are particularly useful to respect the "don’t repeat yourself" (DRY) programming concept and to simplify controllers logic.

Simplified Strapi backend diagram with services highlighted The diagram represents a simplified version of how a request travels through the Strapi back end, with services highlighted. The backend customization introduction page includes a complete, interactive diagram.

Implementation#

Services can be generated or added manually. Strapi provides a createCoreService factory function that automatically generates core services and allows building custom ones or extend or replace the generated services.

Adding a new service#

A new service can be implemented:

  • with the interactive CLI command strapi generate
  • or manually by creating a JavaScript file in the appropriate folder (see project structure):
    • ./src/api/[api-name]/services/ for API services
    • or ./src/plugins/[plugin-name]/services/ for plugin services.

To manually create a service, export a factory function that returns the service implementation (i.e. an object with methods). This factory function receives the strapi instance:


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

module.exports = createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
  // Method 1: Creating an entirely new custom service
  async exampleService(...args) {
    let response = { okay: true }

    if (response.okay === false) {
      return { response, error: true }
    }

    return response
  },

  // Method 2: Wrapping a core service (leaves core logic in place)
  async find(...args) { 
    // Calling the default core controller
    const { results, pagination } = await super.find(...args);

    // some custom logic
    results.forEach(result => {
      result.counter = 1;
    });

    return { results, pagination };
  },

  // Method 3: Replacing a core service
  async findOne(documentId, params = {}) {
    return strapi.documents('api::restaurant.restaurant').findOne({
        documentId, 
        // Use super to keep core fetch parameter formatting
        ...super.getFetchParams(params),
     });
  }
}));

import { factories } from '@strapi/strapi'; 

export default factories.createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
  // Method 1: Creating an entirely custom service
  async exampleService(...args) {
    let response = { okay: true }

    if (response.okay === false) {
      return { response, error: true }
    }

    return response
  },

  // Method 2: Wrapping a core service (leaves core logic in place)
  async find(...args) { 
    // Calling the default core controller
    const { results, pagination } = await super.find(...args);

    // some custom logic
    results.forEach(result => {
      result.counter = 1;
    });

    return { results, pagination };
  },

  // Method 3: Replacing a core service
  async findOne(documentId, params = {}) {
    return strapi.documents('api::restaurant.restaurant').findOne({
       documentId,
       // Use super to keep core fetch parameter formatting
       ...super.getFetchParams(params) }) as any;
  }
}));

:::strapi Document Service API
To get started creating your own services, see Strapi's built-in functions in the Document Service API documentation.
:::

Example of a custom email service (using Nodemailer)

The goal of a service is to store reusable functions. A sendNewsletter service could be useful to send emails from different functions in our codebase that have a specific purpose:


const { createCoreService } = require('@strapi/strapi').factories;
const nodemailer = require('nodemailer'); // Requires nodemailer to be installed (npm install nodemailer)

// Create reusable transporter object using SMTP transport.
const transporter = nodemailer.createTransport({
  service: 'Gmail',
  auth: {
    user: 'user@gmail.com',
    pass: 'password',
  },
});

module.exports = createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
  sendNewsletter(from, to, subject, text) {
    // Setup e-mail data.
    const options = {
      from,
      to,
      subject,
      text,
    };

    // Return a promise of the function that sends the email.
    return transporter.sendMail(options);
  },
}));

import { factories } from '@strapi/strapi'; 
const nodemailer = require('nodemailer'); // Requires nodemailer to be installed (npm install nodemailer)

// Create reusable transporter object using SMTP transport.
const transporter = nodemailer.createTransport({
  service: 'Gmail',
  auth: {
    user: 'user@gmail.com',
    pass: 'password',
  },
});

export default factories.createCoreService('api::restaurant.restaurant', ({ strapi }) => ({
  sendNewsletter(from, to, subject, text) {
    // Setup e-mail data. 
    const options = {
      from,
      to,
      subject,
      text,
    };

    // Return a promise of the function that sends the email.
    return transporter.sendMail(options);
  },
}));

The service is now available through the strapi.service('api::restaurant.restaurant').sendNewsletter(...args) global variable. It can be used in another part of the codebase, like in the following controller:


module.exports = createCoreController('api::restaurant.restaurant', ({ strapi }) => ({
  // GET /hello
  async signup(ctx) {
    const { userData } = ctx.body;

    // Store the new user in database.
    const user = await strapi.service('plugin::users-permissions.user').add(userData);

    // Send an email to validate his subscriptions.
    strapi.service('api::restaurant.restaurant').sendNewsletter('welcome@mysite.com', user.email, 'Welcome', '...');

    // Send response to the server.
    ctx.send({
      ok: true,
    });
  },
}));

export default factories.createCoreController('api::restaurant.restaurant', ({ strapi }) => ({
  // GET /hello
  async signup(ctx) {
    const { userData } = ctx.body;

    // Store the new user in database.
    const user = await strapi.service('plugin::users-permissions.user').add(userData);

    // Send an email to validate his subscriptions.
    strapi.service('api::restaurant.restaurant').sendNewsletter('welcome@mysite.com', user.email, 'Welcome', '...');

    // Send response to the server.
    ctx.send({
      ok: true,
    });
  },
}));
Info

When a new content-type is created, Strapi builds a generic service with placeholder code, ready to be customized.

Extending core services#

Core services are created for each content-type and could be used by controllers to execute reusable logic through a Strapi project. Core services can be customized to implement your own logic. The following code examples should help you get started.

Tip

A core service can be replaced entirely by creating a custom service and naming it the same as the core service (e.g. find, findOne, create, update, or delete).

Collection type examples
async find(params) {
  // some logic here
  const { results, pagination } = await super.find(params);
  // some more logic

  return { results, pagination };
}
async findOne(documentId, params) {
  // some logic here
  const result = await super.findOne(documentId, params);
  // some more logic

  return result;
}
async create(params) {
  // some logic here
  const result = await super.create(params);
  // some more logic

  return result;
}
async update(documentId, params) {
  // some logic here
  const result = await super.update(documentId, params);
  // some more logic

  return result;
}
async delete(documentId, params) {
  // some logic here
  const result = await super.delete(documentId, params);
  // some more logic

  return result;
}
Single type examples
async find(params) {
  // some logic here
  const document = await super.find(params);
  // some more logic

  return document;
}
async createOrUpdate({ data, ...params }) {
  // some logic here
  const document = await super.createOrUpdate({ data, ...params });
  // some more logic

  return document;
}
async delete(params) {
  // some logic here
  const document = await super.delete(params);
  // some more logic

  return document;
}

Usage#

Once a service is created, it's accessible from controllers or from other services:

// access an API service
strapi.service('api::apiName.serviceName').FunctionName();
// access a plugin service
strapi.service('plugin::pluginName.serviceName').FunctionName();

In the syntax examples above, serviceName is the name of the service file for API services or the name used to export the service file to services/index.js for plugin services.

Tip

To list all the available services, run yarn strapi services:list.

Core service methods#

Services generated with createCoreService inherit methods that wrap the Document Service API. The available methods depend on the content-type:

Collection types#

MethodDescription
find(params)Wrapper for findMany; returns a paginated list of documents.
findOne(documentId, params)Wrapper for findOne; returns a single document by its documentId.
create(params)Wrapper for create; creates a new document.
update(documentId, params)Wrapper for update; updates an existing document.
delete(documentId, params)Wrapper for delete; removes a document.
count(params)Wrapper for count; returns the number of matching documents.
publish(documentId, params)Wrapper for publish; publishes a draft document.
unpublish(documentId, params)Wrapper for unpublish; unpublishes a document.
discardDraft(documentId, params)Wrapper for discardDraft; deletes the draft copy.

Single types#

MethodDescription
find(params)Returns the single document (uses findFirst internally).
createOrUpdate({ data, ...params })Creates the document if it doesn't exist or updates it (uses update).
delete(params)Deletes the document (uses delete).
count(params)Counts documents matching the filters (uses count).
publish(params)Publishes a draft document (uses publish).
unpublish(params)Unpublishes the document (uses unpublish).
discardDraft(params)Deletes the draft copy (uses discardDraft).

Parameters and default behavior#

Core service methods accept the same parameters as their underlying Document Service API calls, such as fields, filters, sort, pagination, populate, locale, and status. When no status is provided, Strapi automatically sets status: 'published' so only published content is returned. To query draft documents, explicitly pass status: 'draft' or another value supported by the Document Service.

The createCoreService factory also exposes a getFetchParams(params) helper that converts a controller's query object into the parameter format expected by these methods. This helper can be reused when overriding core methods to forward sanitized parameters to strapi.documents().

Documents
Admin Modal Management
Admin Panel Locale Management
admin-panel
faq
setting-up-admin-panel
Admin User Management
Blocks Editor
Clone Sanitizer Field Permissions
CodeMirror Integration
Content API Input Validation
breaking-changes
populate-select
rest
status
Content Manager Homepage
admin-panel
faq
quick-start
rest
Content Manager i18n Preview
Content Manager Layout Synchronization
Content Manager Preview
Content Manager URL & Filter State
Content Type Builder
admin-panel-api
controllers
create-components-for-plugins
populate-creator-fields
quick-start
store-and-access-data
Custom Field Prop Spreading
Date Field Serialization
Discard-Drafts Migration
Document ID Migration
breaking-changes
Draft & Publish Relation Synchronization
do-not-update-repeatable-components-with-document-service-api
document-service
lifecycle-hooks-document-service
populate
publishedat-always-set-when-dandp-disabled
relations
rest
DynamicZone Stability
components-and-dynamic-zones-do-not-return-id
components-dynamic-zones
no-shared-population-strategy-components-dynamic-zones
Edit View Layout Configuration
admin-panel
admin-panel-api
edit-view-layout-and-list-view-layout-rewritten
EnumerationInput Component
GraphQL Context Propagation
breaking-changes
document-service
draft-and-publish
graphql
i18n Locale Validation
breaking-changes
locale
i18n Locale-Scoped Operations
database-columns
document
document-service
draft-and-publish
i18n-content-manager-locale
locale
locale
locale
no-locale-all
parameters
populate
relations
rest
Monorepo Module Resolution
create-a-plugin
Nested Relation Modal Navigation
Polymorphic Relations
faq
Relation Field Validation
Relation Modal State Management
relations
Sharp Image Processing
media-library
Strapi Package Version Management
Strapi Project Scaffolding
create-a-plugin
How can I create my first Strapi project on a brand new MacBook with no prior coding experience, including all necessary terminal commands and setup steps?
quick-start
Strapi v5 Plugin API
admin-panel-rbac-store-updated
admin-permissions-for-plugins
breaking-changes
controllers
extension
faq
get-where-removed
helper-plugin
helper-plugin-deprecated
inject-content-manager-component
introduction
introduction-and-faq
model-config-path-uses-uid
pass-data-from-server-to-admin
plugins-migration
rbac
redux-content-manager-app-state
step-by-step
strapi-imports
Users-Permissions Plugin i18n
users-permissions
Vite Build Configuration
admin-panel
introduction-and-faq
vite
webpack-aliases-removed
Zod Schema Validation
document-service
Content Manager Mobile and Tablet UI Improvements
FilesManager API Reference
Focal Point Picker in Strapi Media Library
Nested Route File Structure for Strapi Plugins
Plugin-Specific Handling in the Strapi JavaScript Client
Strapi SDK Initialization and Architecture Guide
access-cast-environment-variables
access-configuration-values
adding-support-to-existing-project
admin-panel-customization
advanced-policies
advanced-queries
amazon-s3
api
api-tokens
attributes-and-content-types-names-reserved
audit-logs
auth-zero
authentication
aws-cognito
backend-customization
bulk-operations
bundlers
cas
cli
cli
client
cloudinary
community
configurations
configure-sso
content-api
content-history
content-manager
content-manager-apis
content-type-builder
core-service-methods-use-document-service
cron
crud
custom-fields
customization
data-management
database
database-identifiers-shortened
database-migrations
database-transactions
default-index-removed
default-input-validation
deployment
design-system
developing-plugins
development
discord
discord
docker
documentation
documents-and-entries
email
email-custom-providers
email-nodemailer
entity-service
entity-service-deprecated
environment
error-handling
examples
facebook
favicon
features
fetch
fields
filter
filtering
filters
filters
from-entity-service-to-document-service
functions
github
github
google
google
graphql
graphql-api-updated
guides
homepage
host-port-path
instagram
installation
installing-plugins-via-marketplace
interactive-query-builder
internationalization
intro
intro
is-supported-image-removed
keycloak
keycloak
koa-body-v6
license-only
linkedin
local-upload
locales-translations
logos
mailgun-provider-variables
media-library-providers
microsoft
middlewares
middlewares
middlewares
middlewares
models
mysql5-unsupported
new-provider-guide
new-response-format
no-find-page-in-document-service
no-upload-at-entry-creation
okta
only-better-sqlite3-for-sqlite
only-mysql2-package-for-mysql
openapi
order-pagination
order-pagination
patreon
plugin-sdk
plugin-structure
plugins
plugins-extension
policies
policies
populate
populating
preview
project-structure
publication-state-removed
query-engine
rbac
react-router-dom-6
reddit
register-allowed-fields
releases
remove-webhook-populate-relations
removed-support-for-some-env-options
requests-responses
review-workflows
routes
routes
sentry
server
server-api
server-default-log-level
server-proxy
services
services-and-controllers
setup-deployment
single-operations
sort-by-id
sort-pagination
sort-pagination
sso
status
strapi-container
strapi-utils-refactored
strict-requirements-config-files
templates
templates
testing
theme-extension
twitch
twitter
typescript
typescript
understanding-populate
upgrade-to-apollov4
upgrade-tool
upgrades
upload
usage-information
use-document-id
users-and-permissions-providers
vk
webhooks
wysiwyg-editor
yarn-not-default