medusa-manual-product-suggestion
v0.0.3
Published
A starter for Medusa plugins.
Downloads
25
Maintainers
Readme
medusa-manual-product-suggestion
Medusa v2 plugin for manually managing per-product “similar” and “also like” product recommendations through admin APIs and admin UI widgets.
Plugin Overview
medusa-manual-product-suggestion adds a dedicated module for product-level manual recommendation mapping.
For each product, it stores:
similar: array of related product IDsalso_like: array of “you may also like” product IDs
It includes:
- database model + migration
- admin CRUD APIs (
/admin/product-suggestions) - admin widget on product details for selecting suggestion products with a picker modal
Problem It Solves
It gives merchandisers/support teams a simple way to curate product recommendations manually (without algorithmic engines), and persist those associations in a normalized plugin-managed table.
Medusa Version
Built for Medusa v2 (uses @medusajs/framework / @medusajs/medusa 2.12.4).
Installation & Setup
Install
npm install medusa-manual-product-suggestionor
yarn add medusa-manual-product-suggestionRegister in medusa-config.ts
import { defineConfig } from "@medusajs/framework/utils"
export default defineConfig({
plugins: [
{
resolve: "medusa-manual-product-suggestion",
options: {},
},
],
})Run Migrations
npx medusa db:migrateThis creates the product_suggestion table and indexes.
Configuration (config.ts / plugin options)
No plugin-specific runtime configuration file or options are consumed by this plugin’s source code.
| Option | Type | Required | Default | Description | |---|---|---|---|---| | None | - | - | - | The plugin currently has no custom options in code. |
Example Plugin Config Block
plugins: [
{
resolve: "medusa-manual-product-suggestion",
options: {},
},
]Environment Variables
No runtime process.env.* usage exists in this plugin source code.
| Variable | Required | Purpose | Example | |---|---|---|---| | None | No | Plugin logic does not read environment variables directly. | - |
⚠️ Note:
src/modules/README.mdcontains an example reference toprocess.env.API_KEY, but it is documentation/example content and not used by runtime plugin code.
REST APIs / Routes
Custom functional routes are admin-scoped under /admin/product-suggestions.
Two additional plugin health endpoints are also present.
Authentication
/admin/product-suggestions/*: Admin JWT/session required (admin API scope)/admin/pluginand/store/plugin: health endpoints returning HTTP 200
GET /admin/product-suggestions
Lists product suggestions with optional filtering and pagination.
Query Params
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| product_id | string | No | - | Filter by product ID. |
| limit | number | No | 20 | Max results (<=100). |
| offset | number | No | 0 | Pagination offset. |
| order | "created_at" \| "updated_at" \| "product_id" | No | created_at | Sort field. |
| order_direction | "ASC" \| "DESC" | No | DESC | Sort direction. |
Response Schema
{
"suggestions": [
{
"id": "psug_...",
"product_id": "prod_...",
"similar": ["prod_..."],
"also_like": ["prod_..."]
}
],
"count": 1,
"offset": 0,
"limit": 20
}POST /admin/product-suggestions
Creates a suggestion record for a product.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| product_id | string | Yes | Product ID owning these suggestions. |
| similar | string[] | No | Similar product IDs (defaults to empty array). |
| also_like | string[] | No | Also-like product IDs (defaults to empty array). |
Response Schema
{
"suggestion": {
"id": "psug_...",
"product_id": "prod_...",
"similar": [],
"also_like": []
}
}GET /admin/product-suggestions/:id
Returns a single suggestion by record ID.
Response Schema
{
"suggestion": {
"id": "psug_...",
"product_id": "prod_...",
"similar": ["prod_..."],
"also_like": ["prod_..."]
}
}PUT /admin/product-suggestions/:id
Partially updates arrays on an existing suggestion.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| similar | string[] | No | Replaces the similar list when provided. |
| also_like | string[] | No | Replaces the also_like list when provided. |
Response Schema
{
"suggestion": {
"id": "psug_...",
"product_id": "prod_...",
"similar": ["prod_..."],
"also_like": ["prod_..."]
}
}DELETE /admin/product-suggestions/:id
Deletes a suggestion record.
Response Schema
{
"id": "psug_...",
"object": "product_suggestion",
"deleted": true
}Health Endpoints
GET /admin/plugin->200GET /store/plugin->200
Important Endpoint Examples
curl -X POST "http://localhost:9000/admin/product-suggestions" \
-H "Authorization: Bearer <admin_jwt>" \
-H "Content-Type: application/json" \
-d '{
"product_id": "prod_01ABC",
"similar": ["prod_01DEF", "prod_01GHI"],
"also_like": ["prod_01JKL"]
}'curl -X GET "http://localhost:9000/admin/product-suggestions?product_id=prod_01ABC&limit=20&offset=0&order=updated_at&order_direction=DESC" \
-H "Authorization: Bearer <admin_jwt>"curl -X PUT "http://localhost:9000/admin/product-suggestions/psug_01XYZ" \
-H "Authorization: Bearer <admin_jwt>" \
-H "Content-Type: application/json" \
-d '{
"similar": ["prod_01DEF"],
"also_like": ["prod_01JKL", "prod_01MNO"]
}'await fetch("/admin/product-suggestions/psug_01XYZ", {
method: "DELETE",
headers: {
Authorization: `Bearer ${adminToken}`,
},
})Services
ProductSuggestionModuleService
Defined in src/modules/product-suggestion/service.ts.
Manages persistence and retrieval of suggestion records, with duplicate protection on product_id.
| Method | Signature | Description |
|---|---|---|
| getByProductId | (product_id: string) => Promise<ProductSuggestionDTO \| null> | Fetches suggestion row by product ID. |
| getSuggestion | (id: string) => Promise<ProductSuggestionDTO> | Retrieves by record ID; throws not found if missing. |
| createSuggestion | (input: CreateProductSuggestionInput) => Promise<ProductSuggestionDTO> | Creates a suggestion; rejects duplicate product_id. |
| updateSuggestion | (id: string, input: UpdateProductSuggestionInput) => Promise<ProductSuggestionDTO> | Partial update for similar / also_like. |
| listSuggestions | (selector?, config?) => Promise<{ suggestions: ProductSuggestionDTO[]; count: number }> | List + count with pagination/order. |
| deleteSuggestion | (id: string) => Promise<void> | Deletes a suggestion after existence check. |
Key DTOs:
ProductSuggestionDTO:{ id, product_id, similar, also_like }CreateProductSuggestionInputUpdateProductSuggestionInput
Workflows & Steps (Medusa v2)
No Medusa workflows (createWorkflow) or custom steps (createStep) are defined in this plugin.
Subscribers / Event Hooks
No event subscribers are defined in this plugin.
Admin UI / Widgets
Product Suggestion Widget
- File:
src/admin/widgets/product-suggestion-widget.tsx - Placement:
zone: ["product.details.after", "product.details.side.after"] - Renders:
- “Product Suggestion” container
- Similar section with count + “Add Similar” button
- Also-like section with count + “Add Also like” button
- Modal picker for selecting product IDs
- User Interactions:
- load current suggestion for the product
- open modal in either mode (
similar/also_like) - save selected IDs (create new suggestion or update existing one)
- Data Consumed:
- widget
data.idasproductId - admin API endpoints:
GET /admin/product-suggestions?product_id=...&limit=1POST /admin/product-suggestionsPUT /admin/product-suggestions/:id
- widget
Product Picker Modal
- File:
src/admin/components/ProductPickerModal.tsx - Renders:
- searchable product list from
/admin/products - checkbox list + select-all support
- Save/Cancel footer actions
- searchable product list from
- Behavior:
- excludes current product from selectable list
- preselects existing chosen IDs
- returns selected product IDs to widget
onSave
Models & Entities
product_suggestion
Defined in src/modules/product-suggestion/models/product-suggestion.ts and created via migration Migration20260220000000.
| Field | Type | Nullable | Description |
|---|---|---|---|
| id | VARCHAR | No | Primary key |
| product_id | VARCHAR | No | Referenced Medusa product ID (stored as text, searchable) |
| similar | jsonb | Yes | Array of suggested similar product IDs |
| also_like | jsonb | Yes | Array of suggested “also like” product IDs |
| created_at | TIMESTAMP WITH TIME ZONE | No | Creation timestamp |
| updated_at | TIMESTAMP WITH TIME ZONE | No | Update timestamp |
| deleted_at | TIMESTAMP WITH TIME ZONE | Yes | Soft-delete timestamp |
Indexes / constraints:
- Unique partial index on
product_idwheredeleted_at IS NULL - Index on
deleted_at
Relationships:
- No explicit ORM relation is declared to Medusa
productmodel; linkage is by storedproduct_idstring.
Use Cases & Examples
Manual cross-sell curation by merchandisers
- Scenario: team wants handpicked “similar products” on PDP.
- Use: admin widget on product details or
POST/PUT /admin/product-suggestions.
Curated “You may also like” rails
- Scenario: marketing sets a different list than strict similarity.
- Use:
also_likefield managed from widget modal.
Internal admin tooling integration
- Scenario: build custom admin pages around suggestions.
- Use:
GET /admin/product-suggestionswith filters + pagination.
Storefront recommendation rendering
- Scenario: frontend loads suggestion IDs and resolves product cards.
- Use: service
getByProductId(productId)in custom route/workflow.
Operational cleanup
- Scenario: remove stale suggestion mappings.
- Use:
DELETE /admin/product-suggestions/:id.
Troubleshooting
Duplicate suggestion creation fails
- Symptom: conflict error when creating a suggestion for an already-mapped product.
- Cause: one active suggestion row per
product_idis enforced. - Fix: use
PUT /admin/product-suggestions/:idto update existing row, or delete old row first.
Validation errors on create/update/list
- Symptom:
400with Zod validation details. - Cause: invalid body/query types (e.g., empty
product_id, invalid arrays, bad pagination/order values). - Fix: match validator contracts in
validators.ts.
Widget not showing in admin product page
- Cause: plugin not registered, admin bundle stale, or server not restarted.
- Fix:
- ensure plugin is in
pluginsarray - rebuild/restart Medusa
- hard refresh admin app
- ensure plugin is in
Suggestion data not loaded in widget
- Cause: admin API request failure or no existing row for product.
- Fix: check
/admin/product-suggestions?product_id=<id>&limit=1response and auth/session.
Missing database table errors
- Cause: migration not executed.
- Fix: run
npx medusa db:migrate.
