medusa-dynamic-metadata 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1728,17 +1728,10 @@ const UniversalMetadataWidget = ({ data, zone }) => {
1728
1728
  try {
1729
1729
  return resolveEntityType(zone, data);
1730
1730
  } catch (err) {
1731
- console.error("[UniversalMetadataWidget] Entity type resolution failed:", err);
1732
1731
  return void 0;
1733
1732
  }
1734
1733
  }, [zone, data]);
1735
1734
  if (!entityType) {
1736
- if (process.env.NODE_ENV !== "production") {
1737
- console.warn("[UniversalMetadataWidget] Could not resolve entity type.", {
1738
- zone,
1739
- dataKeys: data ? Object.keys(data) : []
1740
- });
1741
- }
1742
1735
  return null;
1743
1736
  }
1744
1737
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -1755,9 +1748,6 @@ const MetadataWidgetLoader = ({ data, entityType, queryKey }) => {
1755
1748
  entity: entityType,
1756
1749
  enabled: true
1757
1750
  });
1758
- if (process.env.NODE_ENV !== "production") {
1759
- console.debug("[UniversalMetadataWidget]", { entityType, descriptors: descriptors.length, isPending, queryKey });
1760
- }
1761
1751
  if (!isPending && descriptors.length === 0) {
1762
1752
  return null;
1763
1753
  }
@@ -1727,17 +1727,10 @@ const UniversalMetadataWidget = ({ data, zone }) => {
1727
1727
  try {
1728
1728
  return resolveEntityType(zone, data);
1729
1729
  } catch (err) {
1730
- console.error("[UniversalMetadataWidget] Entity type resolution failed:", err);
1731
1730
  return void 0;
1732
1731
  }
1733
1732
  }, [zone, data]);
1734
1733
  if (!entityType) {
1735
- if (process.env.NODE_ENV !== "production") {
1736
- console.warn("[UniversalMetadataWidget] Could not resolve entity type.", {
1737
- zone,
1738
- dataKeys: data ? Object.keys(data) : []
1739
- });
1740
- }
1741
1734
  return null;
1742
1735
  }
1743
1736
  return /* @__PURE__ */ jsx(
@@ -1754,9 +1747,6 @@ const MetadataWidgetLoader = ({ data, entityType, queryKey }) => {
1754
1747
  entity: entityType,
1755
1748
  enabled: true
1756
1749
  });
1757
- if (process.env.NODE_ENV !== "production") {
1758
- console.debug("[UniversalMetadataWidget]", { entityType, descriptors: descriptors.length, isPending, queryKey });
1759
- }
1760
1750
  if (!isPending && descriptors.length === 0) {
1761
1751
  return null;
1762
1752
  }
package/README.md CHANGED
@@ -1,92 +1,45 @@
1
- <p align="center">
2
- <a href="https://www.medusajs.com">
3
- <picture>
4
- <source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/59018053/229103275-b5e482bb-4601-46e6-8142-244f531cebdb.svg">
5
- <source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/59018053/229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg">
6
- <img alt="Medusa logo" src="https://user-images.githubusercontent.com/59018053/229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg">
7
- </picture>
8
- </a>
9
- </p>
10
- <h1 align="center">
11
- Medusa Dynamic Metadata
12
- </h1>
13
-
14
- <h4 align="center">
15
- <a href="https://docs.medusajs.com">Documentation</a> |
16
- <a href="https://www.medusajs.com">Website</a>
17
- </h4>
18
-
19
- <p align="center">
20
- Building blocks for digital commerce
21
- </p>
22
- <p align="center">
23
- <a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
24
- <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
25
- </a>
26
- <a href="https://www.producthunt.com/posts/medusa"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-%23DA552E" alt="Product Hunt"></a>
27
- <a href="https://discord.gg/xpCwq3Kfn8">
28
- <img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
29
- </a>
30
- <a href="https://twitter.com/intent/follow?screen_name=medusajs">
31
- <img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
32
- </a>
33
- </p>
34
-
35
- ## Compatibility
36
-
37
- This plugin is compatible with versions >= 2.4.0 of `@medusajs/medusa`.
38
-
39
- ## Overview
40
-
41
- `medusa-dynamic-metadata` is a flexible, configuration-driven plugin that enables metadata management for any Medusa entity type. Simply configure entities in your `medusa-config.ts` file, and the plugin provides a universal widget component that automatically detects entity types and renders the appropriate metadata fields.
42
-
43
- ### Key Features
44
-
45
- - **Configuration-Driven**: Enable metadata for any entity type through simple configuration
46
- - **Universal Widget**: Single widget component works for all entities with runtime entity detection
47
- - **Type-Safe**: Full TypeScript support with proper type definitions
48
- - **Enterprise Field Types**: 27+ field types including text, textarea, richtext, markdown, number, integer, float, bool, date/time/datetime, select/multiselect/radio/checkbox, image/video/audio/file, url/email/phone, color, json/object/array, relation
49
- - **Schema Validation**: Support for `required`, `default_value`, and `validation` (min, max, regex) per field
50
- - **Zero Code Changes**: Add metadata to new entities by updating configuration only
51
-
52
- ## Supported Entities
53
-
54
- This plugin supports metadata configuration for the following entities that have valid admin detail pages:
55
-
56
- 1. **products** - Widget zone: `product.details.after`
57
- 2. **orders** - Widget zone: `order.details.after`
58
- 3. **categories** - Widget zone: `product_category.details.after`
59
- 4. **collections** - Widget zone: `product_collection.details.after`
60
- 5. **customers** - Widget zone: `customer.details.after`
61
- 6. **regions** - Widget zone: `region.details.after`
62
- 7. **sales_channels** - Widget zone: `sales_channel.details.after`
63
- 8. **stores** - Widget zone: `store.details.after`
64
- 9. **promotions** - Widget zone: `promotion.details.after`
65
- 10. **campaigns** - Widget zone: `campaign.details.after`
66
- 11. **price_lists** - Widget zone: `price_list.details.after`
67
- 12. **shipping_profiles** - Widget zone: `shipping_profile.details.after`
68
- 13. **inventory_items** - Widget zone: `inventory_item.details.after`
69
- 14. **product_variants** - Widget zone: `product_variant.details.after`
70
-
71
- > **Note**: Only entities with valid admin detail pages can be configured. The plugin includes pre-built widget files for all 14 supported entities above.
72
-
73
- ## Getting Started
74
-
75
- ### Installation
1
+ # medusa-dynamic-metadata
2
+ > Configuration-driven Medusa v2 plugin for rendering and persisting dynamic metadata fields across admin entity detail pages.
3
+
4
+ ## Plugin Overview
5
+
6
+ `medusa-dynamic-metadata` lets you define metadata descriptors per entity in plugin options, then automatically renders a typed metadata editor in Medusa Admin widgets for those entity detail pages.
7
+
8
+ ### What It Does
9
+
10
+ - Resolves metadata configuration from `medusa-config.ts` (`entities` map).
11
+ - Detects entity type in admin widgets (zone first, data-shape fallback).
12
+ - Renders a universal metadata form supporting 27 field types (`text`, `number`, `select`, `json`, `relation`, media URL/file upload, etc.).
13
+ - Validates descriptor-driven constraints (`required`, `validation.min/max/regex`, options validation).
14
+ - Saves metadata to entity-specific admin APIs (or custom endpoint override).
15
+ - Provides admin API for retrieving resolved descriptors by entity.
16
+
17
+ ### Problem It Solves
18
+
19
+ It removes the need to hand-build custom metadata UI and persistence logic per entity. Teams can add/modify metadata fields through plugin configuration instead of repeated frontend/backend boilerplate.
20
+
21
+ ### Medusa Version
22
+
23
+ Built for **Medusa v2** (`@medusajs/framework` / `@medusajs/medusa` `2.12.3`).
24
+
25
+ ---
26
+
27
+ ## Installation & Setup
28
+
29
+ ### Install
76
30
 
77
31
  ```bash
78
32
  npm install medusa-dynamic-metadata
33
+ # or
34
+ yarn add medusa-dynamic-metadata
79
35
  ```
80
36
 
81
- ### Configuration
82
-
83
- Add the plugin to your `medusa-config.ts`:
37
+ ### Register in `medusa-config.ts`
84
38
 
85
- ```typescript
39
+ ```ts
86
40
  import { defineConfig } from "@medusajs/framework"
87
41
 
88
42
  export default defineConfig({
89
- // ... other config
90
43
  plugins: [
91
44
  {
92
45
  resolve: "medusa-dynamic-metadata",
@@ -96,23 +49,6 @@ export default defineConfig({
96
49
  descriptors: [
97
50
  { key: "brand", label: "Brand", type: "text", filterable: true },
98
51
  { key: "warranty_years", label: "Warranty (Years)", type: "number" },
99
- {
100
- key: "status",
101
- label: "Status",
102
- type: "select",
103
- options: [
104
- { value: "draft", label: "Draft" },
105
- { value: "active", label: "Active" },
106
- { value: "archived", label: "Archived" },
107
- ],
108
- },
109
- ],
110
- expose_client_helpers: true,
111
- filterable: true,
112
- },
113
- orders: {
114
- descriptors: [
115
- { key: "source", label: "Source", type: "text" },
116
52
  ],
117
53
  },
118
54
  },
@@ -122,262 +58,309 @@ export default defineConfig({
122
58
  })
123
59
  ```
124
60
 
125
- ### Widget Files
61
+ ### Database Migrations
126
62
 
127
- The plugin includes minimal wrapper widget files for all 14 supported entities listed above. Each wrapper:
63
+ No custom module/entity migrations are present in this plugin source. It uses existing Medusa entity metadata columns and admin endpoints.
128
64
 
129
- 1. Specifies the widget zone (required by Medusa's widget system)
130
- 2. Delegates to the universal `UniversalMetadataWidget` component
131
- 3. Automatically detects the entity type at runtime
65
+ ---
132
66
 
133
- **Note:** If an entity is not configured in your `medusa-config.ts`, the admin hook will treat that as "no descriptors" and the widget will not render for that entity (no error is shown). This prevents widgets from appearing for entities you don't intend to manage metadata for, such as `regions`.
67
+ ## Configuration (`config.ts` / plugin options)
134
68
 
135
- **For entities not in the supported list**: Only entities with valid admin detail pages can have metadata widgets. If you need to add support for a new entity that has a detail page in Medusa's admin, create a minimal wrapper widget file using the template in `src/admin/widget-templates/generic-metadata-widget.template.tsx` (copy it into `src/admin/widgets/`; do not leave the template inside `widgets/` or it will register as a duplicate widget).
69
+ Defined in `src/config/metadata-options.ts`.
136
70
 
137
- ### Building the Plugin
71
+ ### Root Options
138
72
 
139
- ```bash
140
- npm run build
141
- ```
73
+ | Option | Type | Required | Default | Description |
74
+ |---|---|---|---|---|
75
+ | `entities` | `Record<string, EntityMetadataConfig>` | Optional | `{}` | Per-entity metadata configuration keyed by entity type (for example `products`, `orders`). |
142
76
 
143
- This builds the plugin with all extensions.
77
+ ### Entity Metadata Config
144
78
 
145
- ### Development
79
+ | Option | Type | Required | Default | Description |
80
+ |---|---|---|---|---|
81
+ | `descriptors` | `MetadataDescriptor[]` | Optional | `[]` | Field definitions to render and persist. |
82
+ | `expose_client_helpers` | `boolean` | Optional | `false` | Flag stored in normalized config (helper exposure toggle). |
83
+ | `filterable` | `boolean` | Optional | `false` | Entity-level filterability flag in config. |
84
+ | `widget_zone` | `string` | Optional | Auto-resolved from entity mapping or `{singular}.details.after` | Admin widget zone override. |
85
+ | `api_endpoint` | `string` | Optional | Derived from entity registry | Admin update endpoint override for saving metadata. |
146
86
 
147
- ```bash
148
- npm run dev
149
- ```
87
+ ### Metadata Descriptor Fields
150
88
 
151
- This runs the plugin in watch mode for development.
89
+ | Field | Type | Required | Default | Description |
90
+ |---|---|---|---|---|
91
+ | `key` | `string` | Yes | - | Metadata key on entity. |
92
+ | `type` | `MetadataFieldType` | Yes | - | Input type and coercion/validation behavior. |
93
+ | `label` | `string` | Optional | `key` | Display label. |
94
+ | `filterable` | `boolean` | Optional | `false` | Per-field filterable marker. |
95
+ | `required` | `boolean` | Optional | `false` | Required validation. |
96
+ | `default_value` | `unknown` | Optional | `undefined` | Default form value when metadata is missing. |
97
+ | `validation` | `{ min?: number; max?: number; regex?: string }` | Optional | `undefined` | Numeric/regex constraints. |
98
+ | `options` | `{ value: string; label?: string }[]` | Optional | `undefined` | Used for `select`, `multiselect`, `radio`, `checkbox`. |
152
99
 
153
- ## How It Works
100
+ ### Supported `MetadataFieldType` Values
154
101
 
155
- ### Architecture Flow
102
+ `text`, `textarea`, `richtext`, `markdown`, `number`, `integer`, `float`, `bool`, `date`, `time`, `datetime`, `select`, `multiselect`, `radio`, `checkbox`, `image`, `video`, `audio`, `file`, `url`, `email`, `phone`, `color`, `json`, `object`, `array`, `relation`
156
103
 
157
- ```
158
- Configuration (medusa-config.ts)
159
-
160
- Plugin Initialization
161
-
162
- Widget Files (minimal wrappers with zones)
163
-
164
- UniversalMetadataWidget (runtime entity detection)
165
-
166
- MetadataTableWidget (UI component)
167
-
168
- API Endpoints (save/load metadata)
104
+ ### Complete Example Config
105
+
106
+ ```ts
107
+ {
108
+ resolve: "medusa-dynamic-metadata",
109
+ options: {
110
+ entities: {
111
+ products: {
112
+ descriptors: [
113
+ { key: "brand", label: "Brand", type: "text", filterable: true, required: true },
114
+ { key: "warranty_years", label: "Warranty (Years)", type: "integer", validation: { min: 0, max: 20 } },
115
+ {
116
+ key: "status",
117
+ label: "Status",
118
+ type: "select",
119
+ options: [
120
+ { value: "draft", label: "Draft" },
121
+ { value: "active", label: "Active" },
122
+ ],
123
+ },
124
+ { key: "care_guide", label: "Care Guide", type: "file" },
125
+ { key: "specs", label: "Specs", type: "json" },
126
+ ],
127
+ expose_client_helpers: false,
128
+ filterable: true,
129
+ widget_zone: "product.details.after",
130
+ api_endpoint: "/admin/products/{id}",
131
+ },
132
+ orders: {
133
+ descriptors: [
134
+ { key: "source", label: "Source", type: "text" },
135
+ { key: "priority", label: "Priority", type: "radio", options: [{ value: "normal" }, { value: "high" }] },
136
+ ],
137
+ },
138
+ },
139
+ },
140
+ }
169
141
  ```
170
142
 
171
- ### Universal Widget Architecture
143
+ ---
172
144
 
173
- The plugin uses a single universal widget component (`UniversalMetadataWidget`) that works for all entity types:
145
+ ## Environment Variables
174
146
 
175
- 1. **Runtime Entity Detection**: The widget automatically detects the entity type using:
176
- - **Zone-based detection** (primary): Resolves entity type from the widget zone (e.g., `"product.details.after"` → `"products"`)
177
- - **Data structure detection** (fallback): Pattern matching on data properties (e.g., `handle`, `title` → products; `display_id`, `status` → orders)
147
+ Environment variable usage found in plugin source:
178
148
 
179
- 2. **Configuration Loading**: Fetches metadata descriptors from `/admin/metadata-config?entity={entityType}` based on detected entity type
149
+ | Variable | Where | Purpose | Required | Example |
150
+ |---|---|---|---|---|
151
+ | `NODE_ENV` | `src/admin/components/universal-metadata-widget.tsx` | Enables/disables debug/warn logs in non-production mode. | Optional | `production` |
180
152
 
181
- 3. **Dynamic Rendering**: Renders appropriate form fields based on the descriptors (text, number, date, select, multiselect, color, JSON, etc.)
153
+ > ⚠️ Note: `src/modules/README.md` contains example text referencing `process.env.API_KEY`, but it is documentation content, not runtime plugin code.
182
154
 
183
- 4. **Metadata Management**: Saves metadata through standard Medusa entity API endpoints
155
+ ---
184
156
 
185
- ### Widget Structure
157
+ ## REST APIs / Routes
186
158
 
187
- Widget files are minimal wrappers that:
188
- - Specify the widget zone (required by Medusa)
189
- - Pass the zone to `UniversalMetadataWidget`
190
- - Let the universal component handle all entity-specific logic
159
+ ### 1) `GET /admin/metadata-config`
191
160
 
192
- Example widget file:
193
- ```typescript
194
- const Widget = ({ data }) => {
195
- return <UniversalMetadataWidget data={data} zone="product.details.after" />
196
- }
161
+ - **Auth:** Admin (admin route namespace)
162
+ - **Query params:**
163
+ - `entity` (`string`, optional, default: `"products"`)
164
+ - **Response:**
165
+ ```json
166
+ {
167
+ "metadataDescriptors": [
168
+ {
169
+ "key": "brand",
170
+ "type": "text",
171
+ "label": "Brand"
172
+ }
173
+ ]
174
+ }
175
+ ```
176
+ - **Behavior:**
177
+ - Reads plugin config with `resolveDynamicMetadataOptions`.
178
+ - Returns empty descriptor array when entity is not configured (no 404).
179
+
180
+ ### 2) `POST /admin/product-variants/:id`
181
+
182
+ - **Auth:** Admin (admin route namespace)
183
+ - **Path params:**
184
+ - `id` (`string`, required): product variant ID.
185
+ - **Body:** passthrough payload with optional `metadata` object plus other variant update fields.
186
+ - **Response:**
187
+ ```json
188
+ {
189
+ "product_variant": {
190
+ "id": "variant_...",
191
+ "metadata": {}
192
+ }
193
+ }
194
+ ```
195
+ - **Behavior:**
196
+ - Executes `updateProductVariantsWorkflow` with selector by ID and update payload.
197
+ - Returns first updated variant as `product_variant`.
197
198
 
198
- export const config = defineWidgetConfig({
199
- zone: "product.details.after"
199
+ ### Important Endpoint Examples
200
+
201
+ ```bash
202
+ # Get metadata descriptors for products
203
+ curl -X GET "http://localhost:9000/admin/metadata-config?entity=products" \
204
+ -H "Authorization: Bearer <admin_token>"
205
+ ```
206
+
207
+ ```bash
208
+ # Update metadata on a product variant
209
+ curl -X POST "http://localhost:9000/admin/product-variants/variant_123" \
210
+ -H "Authorization: Bearer <admin_token>" \
211
+ -H "Content-Type: application/json" \
212
+ -d '{
213
+ "metadata": {
214
+ "fabric": "cotton",
215
+ "is_limited": true
216
+ }
217
+ }'
218
+ ```
219
+
220
+ ```ts
221
+ // fetch example for metadata config
222
+ const res = await fetch("/admin/metadata-config?entity=orders", {
223
+ credentials: "include",
200
224
  })
225
+ const data = await res.json()
201
226
  ```
202
227
 
203
- ### Creating Widgets for New Entities
228
+ ---
204
229
 
205
- To add metadata support for a new entity:
230
+ ## Services
206
231
 
207
- 1. **Add configuration** in `medusa-config.ts`:
208
- ```typescript
209
- entities: {
210
- customers: {
211
- descriptors: [
212
- { key: "preferred_language", type: "text" }
213
- ]
214
- }
215
- }
216
- ```
232
+ No custom backend service classes are defined in this plugin source.
217
233
 
218
- 2. **Create a minimal widget file** (if zone doesn't match existing patterns):
219
- - Copy `src/admin/widget-templates/generic-metadata-widget.template.tsx` into `src/admin/widgets/`
220
- - Update the zone to match your entity's admin zone
221
- - The universal widget will automatically detect the entity type
222
-
223
- ## Configuration Options
224
-
225
- ### Entity Configuration
226
-
227
- Each entity in the configuration supports:
228
-
229
- - `descriptors`: Array of metadata field definitions
230
- - `key`: Unique identifier for the field
231
- - `label`: Display label (optional, defaults to key)
232
- - `type`: Field type (see [Field Type Reference](#field-type-reference) below)
233
- - `required`: Whether the field must have a value (default: `false`)
234
- - `default_value`: Default value when no value is set (optional)
235
- - `validation`: Validation rules — `{ min?: number, max?: number, regex?: string }` (optional)
236
- - `options`: For `select`, `multiselect`, `radio`, `checkbox` — array of `{ value: string, label?: string }`
237
- - `filterable`: Whether the field can be used for filtering (default: `false`)
238
- - `expose_client_helpers`: Expose client-side helper functions (default: `false`)
239
- - `filterable`: Enable filtering for this entity (default: `false`)
240
- - `widget_zone`: Custom widget zone override (optional)
241
- - `api_endpoint`: Custom API endpoint override (optional)
242
-
243
- ### Field Type Reference
244
-
245
- | Type | Use Case | Options/Validation |
246
- |------|-----------|-------------------|
247
- | `text` | Short single-line text | — |
248
- | `textarea` | Multi-line text (product description) | — |
249
- | `richtext` | Formatted content (HTML) | — |
250
- | `markdown` | Markdown content | — |
251
- | `number` | Decimal numbers | min, max, regex |
252
- | `integer` | Whole numbers | min, max, regex |
253
- | `float` | Decimal numbers | min, max, regex |
254
- | `bool` | Boolean toggle | — |
255
- | `date` | Date only (sale start) | — |
256
- | `time` | Time only | — |
257
- | `datetime` | Date and time (scheduled publish) | — |
258
- | `select` | Single option dropdown | options required |
259
- | `multiselect` | Multiple options (tags) | options required |
260
- | `radio` | Single choice (UX alternative to select) | options required |
261
- | `checkbox` | Multiple checkboxes | options required |
262
- | `image` | Image URL (product badge) | — |
263
- | `video` | Video URL (demo video) | — |
264
- | `audio` | Audio URL | — |
265
- | `file` | Generic file URL | — |
266
- | `url` | Valid URL | — |
267
- | `email` | Email address | — |
268
- | `phone` | Phone number | — |
269
- | `color` | Color (hex or rgb) | — |
270
- | `json` | Arbitrary JSON | — |
271
- | `object` | JSON object | — |
272
- | `array` | JSON array | — |
273
- | `relation` | Entity ID reference | — |
274
-
275
- ## API
276
-
277
- The plugin provides admin API endpoints for metadata management:
278
-
279
- - `GET /admin/metadata-config?entity={entityType}` - Get metadata descriptors for an entity
280
- - Metadata is managed through the standard Medusa entity APIs with metadata fields
281
-
282
- ## Examples
283
-
284
- ### Basic Configuration
285
-
286
- ```typescript
287
- {
288
- entities: {
289
- products: {
290
- descriptors: [
291
- { key: "brand", type: "text", filterable: true },
292
- { key: "warranty_years", type: "number" },
293
- ],
294
- },
295
- },
296
- }
297
- ```
234
+ > ⚠️ Note: Most logic is implemented as configuration utilities (`src/config/*`) and shared metadata helpers (`src/shared/metadata/utils.ts`) consumed by admin UI.
298
235
 
299
- ### Advanced Configuration
236
+ ---
300
237
 
301
- ```typescript
302
- {
303
- entities: {
304
- products: {
305
- descriptors: [
306
- { key: "brand", label: "Brand Name", type: "text", filterable: true },
307
- { key: "warranty_years", label: "Warranty Period", type: "number" },
308
- { key: "has_warranty", label: "Has Warranty", type: "bool" },
309
- { key: "manual_pdf", label: "Manual PDF", type: "file" },
310
- ],
311
- expose_client_helpers: true,
312
- filterable: true,
313
- widget_zone: "product.details.after", // Optional override
314
- },
315
- },
316
- }
317
- ```
238
+ ## Workflows & Steps (Medusa v2)
318
239
 
319
- ### Commerce-Focused Examples
240
+ No custom workflows or steps are defined in this plugin source.
320
241
 
321
- ```typescript
322
- {
323
- entities: {
324
- products: {
325
- descriptors: [
326
- // Sale dates
327
- { key: "sale_start", label: "Sale Start", type: "datetime", required: true },
328
- { key: "sale_end", label: "Sale End", type: "datetime" },
329
- // Multiselect tags
330
- {
331
- key: "tags",
332
- label: "Tags",
333
- type: "multiselect",
334
- options: [
335
- { value: "bestseller", label: "Bestseller" },
336
- { value: "new", label: "New Arrival" },
337
- { value: "sale", label: "On Sale" },
338
- ],
339
- },
340
- // Color customization
341
- { key: "badge_color", label: "Badge Color", type: "color", default_value: "#ff0000" },
342
- // Structured data
343
- {
344
- key: "dimensions",
345
- label: "Dimensions (JSON)",
346
- type: "object",
347
- default_value: { width: 0, height: 0, depth: 0 },
348
- },
349
- // Contact/URL fields
350
- { key: "support_url", label: "Support URL", type: "url" },
351
- { key: "support_email", label: "Support Email", type: "email" },
352
- { key: "support_phone", label: "Support Phone", type: "phone" },
353
- // Relation to another product
354
- { key: "related_product_id", label: "Related Product", type: "relation" },
355
- ],
356
- },
357
- },
358
- }
359
- ```
242
+ The plugin calls Medusa core workflow `updateProductVariantsWorkflow` in `POST /admin/product-variants/:id`.
243
+
244
+ ---
245
+
246
+ ## Subscribers / Event Hooks
247
+
248
+ No runtime subscribers/event handlers are defined in this plugin source (only template/readme scaffolding under `src/subscribers`).
249
+
250
+ ---
251
+
252
+ ## Admin UI / Widgets
253
+
254
+ ### Universal Components
255
+
256
+ - **`UniversalMetadataWidget` (`src/admin/components/universal-metadata-widget.tsx`)**
257
+ - Detects entity type from widget zone; falls back to data-shape signals.
258
+ - Loads descriptors via `useMetadataConfig`.
259
+ - Renders `MetadataTableWidget` only when descriptors exist.
260
+
261
+ - **`MetadataTableWidget` (`src/admin/components/metadata-table.tsx`)**
262
+ - Renders typed controls per descriptor.
263
+ - Performs descriptor-based validation.
264
+ - Uploads media/file fields through `POST /admin/uploads` and stores URL in metadata value.
265
+ - Saves metadata by resolving entity endpoint (`resolveApiEndpoint`) and calling `POST`.
266
+ - Invalidates/refetches React Query cache using resolved query keys.
267
+
268
+ ### Registered Metadata Widgets (wrappers)
269
+
270
+ Each file is a minimal wrapper around `UniversalMetadataWidget` with a fixed zone:
271
+
272
+ | Entity | Zone |
273
+ |---|---|
274
+ | `products` | `product.details.after` |
275
+ | `orders` | `order.details.after` |
276
+ | `categories` | `product_category.details.after` |
277
+ | `collections` | `product_collection.details.after` |
278
+ | `customers` | `customer.details.after` |
279
+ | `regions` | `region.details.after` |
280
+ | `sales_channels` | `sales_channel.details.after` |
281
+ | `stores` | `store.details.after` |
282
+ | `promotions` | `promotion.details.after` |
283
+ | `campaigns` | `campaign.details.after` |
284
+ | `price_lists` | `price_list.details.after` |
285
+ | `shipping_profiles` | `shipping_profile.details.after` |
286
+ | `inventory_items` | `inventory_item.details.after` |
287
+ | `product_variants` | `product_variant.details.after` |
288
+
289
+ ### Additional Widget
290
+
291
+ - **`hide-default-metadata` (`src/admin/widgets/hide-default-metadata.tsx`)**
292
+ - **Zone:** `product.details.side.before`
293
+ - **Purpose:** DOM-level hiding of default Medusa metadata panel to avoid UI duplication.
294
+ - **Interaction:** none (automatic MutationObserver behavior).
295
+
296
+ > ⚠️ Note: This widget hides DOM elements by structure/class heuristics and may need adjustment if Admin DOM structure changes in future Medusa releases.
297
+
298
+ ---
299
+
300
+ ## Models & Entities
301
+
302
+ No custom database models/entities are defined in this plugin source.
303
+
304
+ The plugin operates on existing Medusa entities by reading/writing their `metadata` field via admin APIs.
305
+
306
+ ---
307
+
308
+ ## Use Cases & Examples
309
+
310
+ 1. **Product enrichment without code changes**
311
+ - Add new product metadata fields (brand, care instructions, support URLs) via plugin options.
312
+ - Use: `entities.products.descriptors` + `product.details.after` widget.
313
+
314
+ 2. **Operational order annotations**
315
+ - Add structured metadata to orders (source channel, routing note, internal tags).
316
+ - Use: `entities.orders.descriptors` + metadata table UI.
317
+
318
+ 3. **Per-entity metadata governance**
319
+ - Make fields required and validated (`min/max/regex`) to standardize admin data entry.
320
+ - Use: descriptor `required` and `validation`.
321
+
322
+ 4. **Variant-level custom attributes**
323
+ - Persist metadata on product variants and update through variant route workflow.
324
+ - Use: `POST /admin/product-variants/:id`.
325
+
326
+ 5. **File/media metadata links**
327
+ - Upload file/image/video/audio in metadata form and store uploaded URL in metadata.
328
+ - Use: file/media descriptor types in metadata table.
329
+
330
+ ---
331
+
332
+ ## Troubleshooting
333
+
334
+ ### Metadata widget doesn’t appear
335
+
336
+ - **Cause:** no descriptors configured for that entity.
337
+ - **Fix:** add `entities.<entityType>.descriptors` in plugin options; rebuild/restart app.
338
+
339
+ ### “Unable to load metadata configuration”
340
+
341
+ - **Cause:** admin can’t reach `/admin/metadata-config` or plugin not loaded.
342
+ - **Fix:** verify plugin registration in `medusa-config.ts`, rebuild plugin, restart Medusa server.
343
+
344
+ ### “No API endpoint configured for <entityType>”
360
345
 
361
- ## Scripts
346
+ - **Cause:** entity type lacks mapping in registry and no `api_endpoint` override configured.
347
+ - **Fix:** set `api_endpoint` (and optionally `widget_zone`) for that entity in plugin options.
362
348
 
363
- - `npm run build` - Build the plugin
364
- - `npm run dev` - Run in development/watch mode
349
+ ### Save fails with backend error
365
350
 
366
- ## What is Medusa
351
+ - **Cause:** invalid payload, endpoint mismatch, or authorization issue.
352
+ - **Fix:** verify resolved endpoint, admin auth/session, and descriptor/value shape.
367
353
 
368
- Medusa is a set of commerce modules and tools that allow you to build rich, reliable, and performant commerce applications without reinventing core commerce logic. The modules can be customized and used to build advanced ecommerce stores, marketplaces, or any product that needs foundational commerce primitives. All modules are open-source and freely available on npm.
354
+ ### File upload fails in metadata field
369
355
 
370
- Learn more about [Medusa’s architecture](https://docs.medusajs.com/learn/introduction/architecture) and [commerce modules](https://docs.medusajs.com/learn/fundamentals/modules/commerce-modules) in the Docs.
356
+ - **Cause:** `/admin/uploads` request fails or no URL returned.
357
+ - **Fix:** confirm uploads endpoint availability and permissions; inspect server response message shown in toast.
371
358
 
372
- ## Community & Contributions
359
+ ### Default and custom metadata sections both visible
373
360
 
374
- The community and core team are available in [GitHub Discussions](https://github.com/medusajs/medusa/discussions), where you can ask for support, discuss roadmap, and share ideas.
361
+ - **Cause:** hide widget not active for current zone/page or DOM structure mismatch.
362
+ - **Fix:** verify `hide-default-metadata` widget registration and adjust selector logic if Admin markup changed.
375
363
 
376
- Join our [Discord server](https://discord.com/invite/medusajs) to meet other community members.
364
+ ---
377
365
 
378
- ## Other channels
379
366
 
380
- - [GitHub Issues](https://github.com/medusajs/medusa/issues)
381
- - [Twitter](https://twitter.com/medusajs)
382
- - [LinkedIn](https://www.linkedin.com/company/medusajs)
383
- - [Medusa Blog](https://medusajs.com/blog/)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medusa-dynamic-metadata",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "A starter for Medusa plugins.",
5
5
  "author": "Medusa (https://medusajs.com)",
6
6
  "license": "MIT",