@matija2209/payload-plugin-admin-feedback
v1.0.4
Published
Floating admin feedback widget plugin for Payload CMS.
Downloads
324
Readme

payload-plugin-admin-feedback
Floating feedback/chat widget plugin for Payload CMS admin and selected Next.js frontend routes.
Features
- Adds
admin-feedbackcollection to Payload - Floating client widget with:
- message input
- current page path capture
- optional CSS selector capture
- current-tab-first screenshot capture, full-screen annotation, clipboard paste, and file upload
- native HTML5 tools for pen, rectangle, arrow, text, undo, redo, clear, and revert
- Email notification on feedback creation through configured Payload email adapter
- Frontend allowlist route matching helper
- Strict media collection validation with fail-fast startup errors
Install
pnpm add @matija2209/payload-plugin-admin-feedbackRequirements
payload^3.84.1(native advanced plugin API withdefinePlugin)react^19
Version Compatibility
| Plugin Version | Payload Version | | -------------- | --------------- | | 1.x.x | ^3.84.1 |
Setup
1. Register the plugin in your Payload config
import { buildConfig } from 'payload'
import { adminFeedbackPlugin } from '@matija2209/payload-plugin-admin-feedback'
export default buildConfig({
// ...
plugins: [
adminFeedbackPlugin({
emailTo: '[email protected]',
fromLabel: 'Store Admin Feedback',
fromName: 'Payload Admin',
fromAddress: '[email protected]',
allowScreenshotUpload: true,
mediaCollectionSlug: 'files',
strictMediaCollection: true,
screenshot: {
enabled: true,
maxFileSizeBytes: 5 * 1024 * 1024,
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
capturePolicy: 'current-tab-first',
},
maxMessageLength: 3000,
frontend: {
enabled: true,
include: ['/marketplace*', '/profile*'],
},
tenant: {
enabled: true,
collectionSlug: 'tenants',
fieldName: 'tenant',
formDataSlugKey: 'tenant',
pathMarkers: ['pisarna', 'narocilnica'],
resolveTenantId: async ({ req, tenantSlug }) => {
// Optional host fallback (admin cookie, subdomain, …)
return null
},
},
}),
],
})This registers the admin-feedback collection with custom endpoints (/submit, /upload, /upload/:id) — no additional API routes are needed.
Multi-tenant media uploads
When tenant.enabled is true, screenshot uploads set the configured relationship field on mediaCollectionSlug. Resolution order:
- Tenant slug in upload
FormData(set automatically whentenantPathMarkersis passed toFrontendFeedbackWidget) - Slug parsed from the request URL via
tenant.pathMarkers tenant.resolveTenantIdcallback for host-specific logic
If your project stores uploads in a collection named files instead of media, set mediaCollectionSlug: 'files' exactly as shown above. The plugin validates that the target collection exists and is upload-enabled during startup.
2. Add the admin panel widget
In your Payload admin layout (src/app/(payload)/layout.tsx):
import { AdminFeedbackWidget } from '@matija2209/payload-plugin-admin-feedback/client'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
{children}
<AdminFeedbackWidget />
</RootLayout>
)
}The admin widget automatically sends authenticated requests (HTTP-only cookies via credentials: 'include').
3. Add the frontend widget
In your Next.js frontend locale layout (src/app/(frontend)/[locale]/layout.tsx):
import { FrontendFeedbackWidget } from '@matija2209/payload-plugin-admin-feedback/client'
export default async function LocaleLayout({ children, params }) {
return (
<html>
<body>
{/* ... */}
<FrontendFeedbackWidget
include={['/marketplace*', '/profile*', '/checkout*']}
locales={['en', 'ru', 'sl']}
/>
{children}
{/* ... */}
</body>
</html>
)
}FrontendFeedbackWidget props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| include | string[] | required | Glob-style route patterns to show the widget on. Supports * wildcard suffixes (e.g. '/profil*' matches /profile, /profile/orders/123). |
| locales | string[] | [] | Supported locale prefixes. The widget strips the locale segment before matching against include patterns. If all your routes are non-prefixed, leave empty. |
| title | string | 'Feedback' | Widget title. |
| submitLabel | string | 'Send' | Submit button label. |
| uploadLabel | string | 'Upload image' | Upload button label. |
| tenantPathMarkers | string[] | [] | Path markers — tenant slug is the segment before each marker; sent on upload. |
| tenantFormDataKey | string | 'tenant' | FormData key for tenant slug. |
How locale normalization works: When locales is ['en', 'ru', 'sl'], the widget strips the first path segment if it matches a locale. For example, /en/marketplace becomes /marketplace before checking the include patterns. This means you write patterns once and they work across all locales.
Plugin Options
enabled?: booleandefaulttrueemailTo: string | string[]required recipient address or addressesfromLabel?: stringlabel used by the widget and email outputfromName?: stringoptional sender name for feedback emailsfromAddress?: stringoptional sender address for feedback emailsemail?: Config['email']optional Payload email config injected only whenconfig.emailis not already setallowScreenshotUpload?: booleandefaulttrue; enables image upload support in the widget, including annotated screenshot uploadsmaxMessageLength?: numberdefault3000mediaCollectionSlug?: stringdefault'media'; slug of the upload-enabled collection used for image uploadsstrictMediaCollection?: booleandefaulttrue; whentrue, startup fails ifmediaCollectionSlugis missing or not upload-enabledscreenshot?: { enabled?: boolean; maxFileSizeBytes?: number; allowedMimeTypes?: string[]; capturePolicy?: 'current-tab-first' | 'strict-current-tab' | 'any-surface' }frontend?: { enabled?: boolean; include?: string[] }frontendRouteMatcher?: (pathname: string) => booleanoptional custom route matcher for frontend display logictenant?: TenantConfigoptional multi-tenant upload scoping (enabled,collectionSlug,fieldName,formDataSlugKey,pathMarkers,resolveTenantId)
Screenshot Capture Behavior
allowScreenshotUpload: falsedisables image uploads from the widget entirely, including saving annotated screenshots.screenshot.enabled: falsedisables browser screenshot capture while still allowing regular file uploads ifallowScreenshotUploadremainstrue.current-tab-firstis the default. The widget prefers the active browser tab and still accepts window or screen capture if the browser returns a broader surface.strict-current-tabrequires the captured surface to resolve as a browser tab and fails otherwise.any-surfaceallows the browser to offer any supported surface without tab-first constraints.- Captured, pasted, and uploaded images all open in the same full-screen HTML5 annotation editor and are exported as a flattened PNG before upload.
Storage Adapter Compatibility
The plugin uploads images through the resolved Payload upload collection, not directly to a specific storage adapter.
- If your upload collection is configured with
@payloadcms/storage-*, uploads are automatically stored there. - If
strictMediaCollectionistrue(default), plugin initialization fails when the configuredmediaCollectionSlugis missing or not upload-enabled. - If
strictMediaCollectionisfalseand the configured slug is missing, the plugin falls back to'media'only when a'media'collection exists and is upload-enabled. - A collection that exists but is not upload-enabled still fails validation.
Custom Frontend Matching
Use frontend.include for simple glob-style path matching. Use frontendRouteMatcher when widget visibility depends on custom logic that cannot be expressed as a simple allowlist.
adminFeedbackPlugin({
emailTo: '[email protected]',
frontend: {
enabled: true,
},
frontendRouteMatcher: (pathname) => pathname.startsWith('/app') && !pathname.startsWith('/app/embed'),
})Migration Notes
- The plugin uses native Payload advanced plugin metadata (
slug,order,options) viadefinePlugin. - Existing usage with
adminFeedbackPlugin({ ...options })remains unchanged.
License
MIT
