Building a Custom Content Publisher Plugin for Strapi 5
Strapi's plugin system lets you extend the admin panel and the server with your own features, without forking the core. In this post we walk through the pieces of a custom content publisher plugin: a plugin that lists your posts, lets you search and paginate through them, and gives editors a one-click way to publish or delete an entry.
Why a custom plugin?
The Content Manager is deliberately generic. Once your editorial team has a workflow of its own — approve, schedule, syndicate — you end up wanting a screen that speaks their language instead of the CMS's. A plugin is the right seam for that: it ships as its own package, has its own routes, controllers and services on the server side, and its own React app in the admin panel.
Anatomy of the plugin
A generated plugin has two halves:
-
server/— routes, controllers, services, and the content types the plugin owns. -
admin/— the React entry point, pages, and the menu link registered viaapp.addMenuLink().
The strapi-server.js and strapi-admin.js files at the package root are what Strapi loads. Everything else is yours to organise.
Fetching data the Strapi way
On the server, the Document Service API is the entry point for reading and writing content:
const posts = await strapi.documents('plugin::content-publisher.post').findMany({
filters: { title: { $containsi: query } },
sort: { createdAt: 'desc' },
page,
pageSize,
});
Wrapping that in a plugin service keeps the controller thin and makes the logic reusable from lifecycles or cron jobs later.
Search and pagination in the admin
On the admin side, useFetchClient gives you an authenticated axios instance that already points at your Strapi backend, so calling your plugin route is a one-liner. Keep the search term and the current page in the URL query string — editors bookmark and share these screens more often than you would expect, and it makes the browser back button behave.
Publishing and deleting
Destructive actions deserve a confirmation dialog. Strapi's Design System ships one, so there is no reason to hand-roll it. After a successful mutation, refetch the list rather than mutating local state — it keeps the UI honest when someone else is editing at the same time.
Wrapping up
A plugin is not much more than a well-organised folder plus two entry files. Start with the generator, replace the sample route with something real, and grow it from there. The result is a screen your editors actually want to use, living alongside the rest of your Strapi app.
Top comments (0)