Plugin-Specific Handling in the Strapi JavaScript Client#
Overview#
The Strapi JavaScript client provides built-in support for plugin-specific handling, allowing developers to interact with plugin content types while accommodating different API contracts. This document covers how plugin configuration affects route prefixes and data wrapping, with special focus on the users-permissions plugin.
Plugin Configuration System#
Basic Configuration#
Plugins are configured through the ContentTypeManagerOptions interface when creating collection or single-type managers:
interface ContentTypeManagerOptions {
resource: string;
path?: string;
plugin?: {
name: string; // Plugin name (e.g., 'blog', 'users-permissions')
prefix?: string; // Optional route prefix override
};
}
Usage Examples#
Plugin with default prefix:
const posts = client.collection('posts', {
plugin: { name: 'blog' }
});
// Routes will be /blog/posts
Plugin with custom prefix:
const articles = client.collection('articles', {
plugin: { name: 'content-manager', prefix: 'custom' }
});
// Routes will be /custom/articles
Plugin with no prefix:
const users = client.collection('users', {
plugin: { name: 'users-permissions', prefix: '' }
});
// Routes will be /users (no prefix)
Route Prefix Handling#
Prefix Resolution Logic#
The _pluginPrefix property implements the prefix resolution:
- If
prefixis explicitly set (including empty string''), use that value - If no explicit prefix but plugin name is specified, default to the plugin name
- If no plugin, no prefix is applied
Route Generation#
Route generation uses the _rootPath getter with a three-tier priority:
protected get _rootPath() {
if (this._path) {
return this._path; // 1. Custom path overrides everything
}
const prefix = this._pluginPrefix;
if (prefix) {
return `/${prefix}/${this._resource}`; // 2. Plugin-prefixed path
}
return `/${this._resource}`; // 3. Standard path
}
Examples from tests:
- Plugin with default prefix:
{ plugin: { name: 'blog' } }→ routes to/blog/posts - Plugin with no prefix:
{ plugin: { name: 'users-permissions', prefix: '' } }→ routes to/users - Plugin with custom prefix:
{ plugin: { name: 'content-manager', prefix: 'custom' } }→ routes to/custom/articles
Data Wrapping Behavior#
Plugin-Specific API Contracts#
Different plugins have different data wrapping requirements. The shouldWrapData() function determines wrapping behavior:
export function shouldWrapData(pluginName: string | undefined): boolean {
if (pluginName === undefined) {
return true; // Regular content-types wrap data
}
// Check if this plugin is known to not wrap data
const knownPlugin = Object.values({
...WELL_KNOWN_COLLECTIONS,
...WELL_KNOWN_SINGLES,
}).find((config) => config.plugin.name === pluginName);
return knownPlugin?.wrapsData ?? true; // Default to true if plugin not in registry
}
Wrapping Rules#
- Regular content-types (no plugin): Wrap payloads as
{ data: {...} } - Most plugins: Wrap payloads as
{ data: {...} }(default behavior) - users-permissions plugin: Send raw payloads without wrapping (special case)
Usage in Managers#
Both CollectionTypeManager and SingleTypeManager use this in create/update operations:
// In create/update operations
JSON.stringify(this.shouldWrapDataBodyAttribute() ? { data } : data)
Example:
// Regular content-type wraps data
const articles = client.collection('articles');
await articles.create({ title: 'Test' });
// Sends: { data: { title: 'Test' } }
// users-permissions does NOT wrap data
const users = client.collection('users');
await users.create({ username: 'john', email: 'john@example.com' });
// Sends: { username: 'john', email: 'john@example.com' }
Auto-Detection of Well-Known Resources#
The Well-Known Resources Registry#
The client maintains a registry of well-known resources with special API contracts:
const WELL_KNOWN_COLLECTIONS: Record<string, WellKnownResourceConfig> = {
users: {
plugin: {
name: 'users-permissions',
prefix: '', // No plugin prefix in routes
},
wrapsData: false, // Special API contract
},
};
Auto-Detection in Action#
When calling client.collection('users'), the library automatically applies the plugin configuration from the well-known registry:
// Auto-detection - no explicit plugin config needed
const users = client.collection('users');
// The client automatically:
// 1. Associates with users-permissions plugin
// 2. Uses empty prefix (routes to /users)
// 3. Disables data wrapping
The 'users' Resource Special Handling#
The users-permissions plugin receives extensive built-in support:
- Automatic plugin association: The 'users' resource is automatically recognized and associated with the users-permissions plugin
- Empty route prefix: Routes use
/usersinstead of/users-permissions/users - No data wrapping: Payloads are sent raw without the
{ data: {...} }wrapper - Specialized manager class: The
UsersPermissionsUsersManagerclass provides type overloads to accept numeric IDs
The client automatically instantiates the specialized manager when detecting 'users':
if (resource === 'users' && effectivePlugin?.name === 'users-permissions') {
return new UsersPermissionsUsersManager(/*...*/);
}
Overriding Plugin Options#
Overriding Auto-Detection#
You can override auto-detected configuration by providing explicit plugin options:
// Override the auto-detected users-permissions configuration
const users = client.collection('users', {
plugin: { name: 'custom-plugin', prefix: 'custom' }
});
// Routes will now use /custom/users instead of /users
Disabling Auto-Detection#
To use a well-known resource name without its default plugin configuration:
// Use 'users' as a regular content-type (not from users-permissions)
const users = client.collection('users', {
plugin: undefined // Explicitly disable plugin handling
});
// Routes to /users with standard data wrapping
Combining Options#
Plugin options work alongside other configuration options:
// Custom path overrides plugin-based routing
const users = client.collection('users', {
plugin: { name: 'users-permissions' },
path: '/api/custom-users' // Takes precedence over plugin prefix
});
// Routes to /api/custom-users
Interacting with Plugin Content Types#
Basic CRUD Operations#
// Auto-detected users-permissions plugin
const users = client.collection('users');
// Create a user (raw payload, no wrapping)
const newUser = await users.create({
username: 'john_doe',
email: 'john@example.com',
password: 'SecurePass123!'
});
// Find users (routes to /users)
const allUsers = await users.find();
// Find specific user by ID
const user = await users.findOne('1');
// Update user
await users.update('1', {
username: 'john_updated'
});
// Delete user
await users.delete('1');
Custom Plugin Content Types#
// Blog plugin with custom content types
const blogPosts = client.collection('posts', {
plugin: { name: 'blog' }
});
// Create a blog post (wrapped in { data: {...} })
const post = await blogPosts.create({
title: 'My First Post',
content: 'Hello World!'
});
// Sends: { data: { title: 'My First Post', content: 'Hello World!' } }
// Routes to /blog/posts
const posts = await blogPosts.find();
Single-Type Plugin Resources#
Single-types also support plugin configuration:
const settings = client.single('settings', {
plugin: { name: 'my-plugin' }
});
// Find the single-type resource
const currentSettings = await settings.find();
// Update the single-type resource
await settings.update({
maintenanceMode: true
});
// Routes to /my-plugin/settings
Complete Example: Working with Users-Permissions#
Example from the Node.js TypeScript demo:
import { strapiClient } from '@strapi/client';
const client = strapiClient({
baseURL: 'http://localhost:1337/api',
auth: {
strategy: 'users-permissions',
options: {
identifier: 'admin@example.com',
password: 'Admin123!'
}
}
});
// Auto-detected users-permissions plugin
const users = client.collection('users');
// Create a new user
const newUser = await users.create({
username: 'demo_user',
email: 'demo@example.com',
password: 'Password123!',
role: 1
});
console.log('Created user:', newUser);
// List all users
const allUsers = await users.find();
console.log('All users:', allUsers);
// Update user
await users.update(newUser.id, {
username: 'demo_user_updated'
});
// Delete user
await users.delete(newUser.id);
Summary#
The Strapi JavaScript client provides:
- Flexible plugin configuration through the
pluginoption withnameand optionalprefixproperties - Smart route prefixing with configurable prefixes (default to plugin name, customizable, or disabled)
- Plugin-aware data wrapping that respects different API contracts
- Auto-detection of well-known resources like 'users' from users-permissions
- Override capabilities to customize or disable auto-detected configurations
- Specialized handling for users-permissions with dedicated manager class and authentication provider
This design allows the client to handle plugin-specific API contracts transparently while maintaining a consistent developer experience.