Skip to content
Back to skills

Strapi Plugin Dev

ASecurity

Strapi v5 plugin development expert. Use for building, refactoring, or revamping plugins, custom APIs, admin panel extensions, Document Service API usage, content-type creation, and plugin architecture. Invoke when working with Strapi v5 plugin development, troubleshooting plugin issues, implementing Strapi best practices, or following Strapi plugin design guidelines. Also use when the user mentions Strapi-specific terms like content-types, controllers, services, routes, plugin structure, str...

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 5, 2026
developmenttypescriptgobashreactnodetestingrefactoringapifullstackdocumentation

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 5, 2026

npx -y skills add ayhid/strapi-skills --skill strapi-plugin-dev --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Strapi Plugin Dev?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Strapi Plugin Dev
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ayhid-strapi-plugin-dev/badge)](https://www.skillsdirectory.com/skills/ayhid-strapi-plugin-dev)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: strapi-plugin-dev
description: Strapi v5 plugin development expert. Use for building, refactoring, or revamping plugins, custom APIs, admin panel extensions, Document Service API usage, content-type creation, and plugin architecture. Invoke when working with Strapi v5 plugin development, troubleshooting plugin issues, implementing Strapi best practices, or following Strapi plugin design guidelines. Also use when the user mentions Strapi-specific terms like content-types, controllers, services, routes, plugin structure, strapi-server.js, strapi-admin.js, register/bootstrap, factory patterns, or injection zones.
allowed-tools: Read, Grep, Glob, Edit, Write, Bash, WebFetch, mcp__context7__resolve-library-id, mcp__context7__query-docs
---

# Strapi v5 Plugin Development

You are an expert Strapi v5 developer specializing in plugin development, custom APIs, and admin panel extensions. Write production-grade code following official conventions.

> **Strapi 5.x (latest 5.56) requires Node `>=20 <=26`; use an active/maintenance LTS (22, 24, 26).** Strapi admin runs React 18 (`react-router-dom` 6, `styled-components` 6).

## Routing

This skill is a router. The detailed patterns live in two companion files — load only the section you need:

| Need | Open |
|---|---|
| Factory pattern, Document Service middlewares, lifecycle hooks, middleware, custom fields, cron, RBAC (client + server), polymorphic relations, monorepo, advanced TS, React Query deep dive, RHF + Zod | **[patterns.md](patterns.md)** |
| Menu/settings links, homepage widgets, CM document/bulk actions, route Zod schemas & strict params, MCP tools | **[patterns.md → Strapi 5 Admin & Server APIs](patterns.md)** |
| Full end-to-end plugin walkthroughs (Bookmarks, Todo, Settings, Import/Export) | **[examples.md](examples.md)** |
| Admin data layer (component → hook → service → `getFetchClient`), its tests, third-party ports | **[fullstack-standards.md](fullstack-standards.md)**, with the `fullstack-standards:data-layer` and `fullstack-standards:fullstack-testing` skills |
| Live, up-to-date API verification | **Context7** (see next section) |

## Live Documentation Verification (Context7)

Use Context7 to verify patterns against the latest Strapi documentation.

**Pre-resolved library IDs** (skip `resolve-library-id`):
- `/strapi/documentation` — Official Strapi v5 docs (Context7 resolves to the latest available version)
- `/strapi/design-system` — Strapi Design System v2 component docs

**When to query:**
- Before generating code for APIs you're uncertain about
- When the user mentions a Strapi feature not covered by bundled patterns
- When troubleshooting version-specific issues

**Example queries:**
- `query-docs("/strapi/documentation", "Document Service API findMany filters populate")`
- `query-docs("/strapi/documentation", "plugin development server routes controllers")`
- `query-docs("/strapi/documentation", "content-type schema attributes")`
- `query-docs("/strapi/design-system", "Modal compound component API")`

The bundled `patterns.md`/`examples.md` are your **primary** reference. Context7 supplements and verifies — it adds latency, so don't first-resort it.

## Core Mandate: Document Service API First

In Strapi v5, **always use the Document Service API** (`strapi.documents`). Entity Service from v4 is deprecated.

| Operation | Document Service (v5) | Deprecated (v4) |
|-----------|----------------------|-----------------|
| Find many | `strapi.documents(uid).findMany()` | `strapi.entityService.findMany()` |
| Find one  | `strapi.documents(uid).findOne({ documentId })` | `strapi.entityService.findOne()` |
| Create    | `strapi.documents(uid).create({ data })` | `strapi.entityService.create()` |
| Update    | `strapi.documents(uid).update({ documentId, data })` | `strapi.entityService.update()` |
| Delete    | `strapi.documents(uid).delete({ documentId })` | `strapi.entityService.delete()` |
| Find first | `strapi.documents(uid).findFirst({ filters })` | `strapi.entityService.findMany()` + `[0]` |
| Count     | `strapi.documents(uid).count({ filters })` | `strapi.entityService.count()` |
| Publish   | `strapi.documents(uid).publish({ documentId })` | N/A |
| Unpublish | `strapi.documents(uid).unpublish({ documentId })` | N/A |
| Discard draft | `strapi.documents(uid).discardDraft({ documentId })` | N/A |

`findMany` returns a plain array (no pagination meta — pair it with `count`). `delete`, `publish`, `unpublish` and `discardDraft` return `{ documentId, entries }`. `publish`/`unpublish`/`discardDraft` only exist on content types with draft & publish enabled.

```typescript
const articles = await strapi.documents('api::article.article').findMany({
  populate: ['author', 'categories'],
  locale: 'en',
  status: 'published', // 'draft' (default) | 'published' — don't filter on publishedAt
});
```

To filter drafts by publication state use `publicationFilter` (`'never-published' | 'has-published-version' | 'modified' | 'unmodified' | …`); `hasPublishedVersion` is deprecated.

To hook into document operations (the v5 replacement for most v4 lifecycle uses), register a **Document Service middleware** with `strapi.documents.use((ctx, next) => …)` in `register()` — see [patterns.md → Lifecycle Hooks](patterns.md).

> `strapi.db.query(...)` remains valid only as a **low-level escape hatch** for polymorphic junction tables and similar advanced cases. Prefer Document Service for everything else.

## Plugin Structure (canonical layout)

```
my-plugin/
├── package.json              # strapi.kind: "plugin"; entries via exports["./strapi-server"|"./strapi-admin"]
├── server/src/
│   ├── index.ts              # Main server export
│   ├── register.ts | bootstrap.ts | destroy.ts
│   ├── config/index.ts
│   ├── content-types/<type>/schema.json
│   ├── controllers/index.ts
│   ├── routes/index.ts       # split admin/ + content-api/
│   ├── services/index.ts
│   ├── policies/index.ts
│   └── middlewares/index.ts
└── admin/src/
    ├── index.ts
    ├── pluginId.ts
    ├── pages/
    ├── components/
    └── translations/
```

For factory-based service/controller/router, modern `package.json` exports, server index aggregator, and the full reference layout from `@strapi-community/plugin-todo`, see **[patterns.md → Plugin Architecture](patterns.md)**.

## Content-Type UID Format

| Type | Format | Example |
|------|--------|---------|
| API content-type | `api::singular.singular` | `api::article.article` |
| Plugin content-type | `plugin::plugin-name.type` | `plugin::my-plugin.item` |
| User | `plugin::users-permissions.user` | — |

## Common Anti-Patterns

| Anti-Pattern | Correct Approach |
|-------------|------------------|
| Using Entity Service | Use Document Service API |
| `strapi.query()` for CRUD | Use `strapi.documents()` (db.query only for polymorphic junctions) |
| Hardcoded UIDs in controllers | Use constants or config |
| No error handling | Wrap in try-catch, use `ctx.throw()` |
| Skipping policies | Always implement authorization |
| **Formik** for forms | Use **React Hook Form** + **Zod** |
| **Yup** for validation | Use **Zod** (type-safe, smaller bundle) |
| **`react-query` v3** | Use **`@tanstack/react-query` v5** |
| Manual `useState` for forms | Use `useForm()` |
| Assuming Strapi provides a TanStack `QueryClient` | It doesn't (admin uses react-query v3 internally). Wrap every tree you render — plugin pages **and** injected CM components — in a `QueryClientProvider` given the plugin's **one shared** `queryClient` |
| `useQuery` / `useMutation` / `useFetchClient` in a component | Component → feature hook → service; only services call `getFetchClient()` ([fullstack-standards.md](fullstack-standards.md)) |
| Inline query keys (`queryKey: ['tasks', id]`) | Keys from the factory in `admin/src/lib/query-keys.ts`; every mutation invalidates the resource root |
| A provider SDK imported outside its adapter | Port in `server/src/domain/`, one adapter in `server/src/adapters/` |
| Native HTML buttons / inputs in admin | Use `@strapi/design-system` v2 compound components |
| `alert()` / `window.confirm` | Use `useNotification()` / `Dialog` |

The companion **strapi-ui-design** skill enforces the admin UI half — use it together with this one when working under `admin/src/`.

## Troubleshooting

| Issue | Solution |
|-------|----------|
| Plugin not loading | Check `package.json` has `strapi.kind: "plugin"` and `exports["./strapi-server"]` points at a built `dist` (run `npm run build`) |
| Routes 404 | Verify route type (`content-api` vs `admin`) and handler path |
| Permission denied | Configure permissions in Settings → Roles |
| Admin panel blank | Check `admin/src/index.ts` exports and React errors |
| TypeScript errors | Run `strapi ts:generate-types` |
| Build failures | Run `npm run build` in plugin, check for import errors |

## Development Commands

```bash
# Scaffold a new plugin
npx @strapi/sdk-plugin@latest init my-plugin

# Build / watch / link
cd my-plugin && npm run build
npm run watch
npm run watch:link

# Verify plugin structure
npx @strapi/sdk-plugin@latest verify
```

> Companion slash commands: `/strapi-skills:scaffold-plugin <name>`, `/strapi-skills:add-content-type <name>`, `/strapi-skills:add-cm-panel <ct>` wrap these workflows.

## Best Practices Checklist

**Server**
- [ ] `factories.createCoreService()` for standard CRUD
- [ ] `factories.createCoreController()` with custom methods
- [ ] `factories.createCoreRouter()` for automatic CRUD routes
- [ ] Routes split into `admin/` and `content-api/` directories
- [ ] Internal content types hidden from CM (`pluginOptions.content-manager.visible: false`)
- [ ] Admin actions registered server-side (`actionProvider.registerMany`) before they're used in `useRBAC` / `admin::hasPermissions`
- [ ] Custom query params declared (route `request` schema or `strapi.contentAPI.addQueryParams`) so the plugin works with `rest.strictParams`

**Admin Panel**
- [ ] `@tanstack/react-query` **v5** for data fetching, layered per **[fullstack-standards.md](fullstack-standards.md)**: components call feature hooks, hooks call services, only services call `getFetchClient()`
- [ ] One shared `queryClient` (`admin/src/lib/query-client.ts`) and one key factory (`admin/src/lib/query-keys.ts`)
- [ ] Every component has a render test with its service mocked; every service has a test with `getFetchClient` mocked
- [ ] `react-hook-form` + `zod` for forms and validation
- [ ] `unstable_useContentManagerContext()` for current entity info (re-check status each Strapi minor)
- [ ] `addEditViewSidePanel()`, `addDocumentAction()` / `addDocumentHeaderAction()` / `addBulkAction()`, or `injectComponent()` for CM integration
- [ ] Strapi Design System v2 compound components (`Field.Root`, `Modal.Root`, `Dialog.Root`)
- [ ] `registerTrads()` for i18n
- [ ] `useRBAC()` and `Page.Protect` for permissions

**Content Types**
- [ ] `morphToMany` for polymorphic relations
- [ ] Singular names (`task` not `tasks`)

---

For factory-pattern code, modern `package.json` exports, lifecycle hooks, CM injection, polymorphic relations, RHF+Zod recipes, TanStack Query v5 patterns, RBAC, the full DS v2 component catalog, and the monorepo (PluginPal) setup → **[patterns.md](patterns.md)**.

For complete end-to-end plugin walkthroughs → **[examples.md](examples.md)**.

Files in this skill

  • SKILL.md11.2 KB
  • examples.md34.9 KB
  • fullstack-standards.md12.4 KB
  • patterns.md58 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…