Custom Services in Strapi
A service in Strapi is a business logic layer called from controllers or other services. Standard services (find, findOne, create, update, delete) are generated automatically. A custom service adds methods that encapsulate complex logic and are reused in multiple places.
Basic Custom Service
// src/api/order/services/order.ts
import { factories } from '@strapi/strapi'
export default factories.createCoreService('api::order.order', ({ strapi }) => ({
// Override create — add business logic
async create(params) {
const order = await super.create(params)
// Send confirmation
await strapi.plugin('email').service('email').send({
to: order.customerEmail,
from: '[email protected]',
subject: `Order #${order.orderNumber} accepted`,
html: `<p>Your order accepted. Number: ${order.orderNumber}</p>`,
})
// Create CRM record
await strapi.service('api::crm.crm').createDeal(order)
return order
},
// Custom method
async processPayment(orderId: number, paymentData: any) {
const order = await strapi.entityService.findOne('api::order.order', orderId, {
populate: ['items', 'items.product'],
})
if (!order) throw new Error('Order not found')
if (order.status !== 'pending') throw new Error('Order is not pending')
// Process payment via payment gateway
const paymentResult = await this.chargeCard(order.total, paymentData)
if (paymentResult.success) {
await strapi.entityService.update('api::order.order', orderId, {
data: {
status: 'paid',
paymentId: paymentResult.transactionId,
paidAt: new Date().toISOString(),
},
})
// Decrement stock
await this.decrementStock(order.items)
return { success: true, orderId }
} else {
await strapi.entityService.update('api::order.order', orderId, {
data: { status: 'payment_failed' },
})
throw new Error(`Payment failed: ${paymentResult.error}`)
}
},
async decrementStock(items: any[]) {
await Promise.all(
items.map(async (item) => {
const product = await strapi.entityService.findOne(
'api::product.product',
item.product.id
)
const newStock = Math.max(0, product.stock - item.quantity)
await strapi.entityService.update('api::product.product', item.product.id, {
data: { stock: newStock },
})
})
)
},
async chargeCard(amount: number, paymentData: any) {
// Integration with payment gateway
const response = await fetch('https://api.payment-gateway.com/charge', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.PAYMENT_SECRET}` },
body: JSON.stringify({ amount, ...paymentData }),
})
return response.json()
},
// Get order analytics
async getOrderStats(startDate: Date, endDate: Date) {
const orders = await strapi.entityService.findMany('api::order.order', {
filters: {
createdAt: { $gte: startDate.toISOString(), $lte: endDate.toISOString() },
status: { $in: ['paid', 'shipped', 'delivered'] },
},
})
const total = orders.reduce((sum: number, o: any) => sum + (o.total || 0), 0)
const count = orders.length
const avgOrder = count > 0 ? total / count : 0
return { total, count, avgOrder, orders }
},
}))
Standalone Service (Not Tied to Content Type)
// src/api/email-notifications/services/email-notifications.ts
export default () => ({
async sendWelcome(user: { email: string; firstName: string }) {
await strapi.plugin('email').service('email').send({
to: user.email,
subject: `Welcome, ${user.firstName}!`,
html: await strapi.service('api::email-templates.email-templates')
.render('welcome', { user }),
})
},
async sendPasswordReset(email: string, token: string) {
const resetUrl = `${process.env.FRONTEND_URL}/reset-password?token=${token}`
await strapi.plugin('email').service('email').send({
to: email,
subject: 'Password Reset',
html: `<a href="${resetUrl}">Reset Password</a>`,
})
},
})
Calling Service from Controller
// src/api/order/controllers/order.ts
async checkout(ctx) {
const { items, paymentData } = ctx.request.body
// Create order
const order = await strapi.service('api::order.order').create({
data: {
items,
customer: ctx.state.user.id,
status: 'pending',
},
})
// Process payment
const result = await strapi.service('api::order.order').processPayment(
order.id,
paymentData
)
return result
}
Service with Caching
// src/api/catalog/services/catalog.ts
const cache = new Map<string, { data: any; ts: number }>()
const TTL = 60_000 // 1 minute
export default () => ({
async getCategories() {
const cacheKey = 'categories'
const cached = cache.get(cacheKey)
if (cached && Date.now() - cached.ts < TTL) {
return cached.data
}
const data = await strapi.entityService.findMany('api::category.category', {
filters: { active: { $eq: true } },
populate: ['icon', 'children'],
sort: { order: 'asc' },
})
cache.set(cacheKey, { data, ts: Date.now() })
return data
},
})
Timeline
Developing business services for e-commerce (orders, payments, inventory) takes 3–5 days.







