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.
- package/.medusa/server/src/admin/index.js +0 -10
- package/.medusa/server/src/admin/index.mjs +0 -10
- package/README.md +295 -312
- package/package.json +1 -1
|
@@ -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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
###
|
|
82
|
-
|
|
83
|
-
Add the plugin to your `medusa-config.ts`:
|
|
37
|
+
### Register in `medusa-config.ts`
|
|
84
38
|
|
|
85
|
-
```
|
|
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
|
-
###
|
|
61
|
+
### Database Migrations
|
|
126
62
|
|
|
127
|
-
|
|
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
|
-
|
|
130
|
-
2. Delegates to the universal `UniversalMetadataWidget` component
|
|
131
|
-
3. Automatically detects the entity type at runtime
|
|
65
|
+
---
|
|
132
66
|
|
|
133
|
-
|
|
67
|
+
## Configuration (`config.ts` / plugin options)
|
|
134
68
|
|
|
135
|
-
|
|
69
|
+
Defined in `src/config/metadata-options.ts`.
|
|
136
70
|
|
|
137
|
-
###
|
|
71
|
+
### Root Options
|
|
138
72
|
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
77
|
+
### Entity Metadata Config
|
|
144
78
|
|
|
145
|
-
|
|
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
|
-
|
|
148
|
-
npm run dev
|
|
149
|
-
```
|
|
87
|
+
### Metadata Descriptor Fields
|
|
150
88
|
|
|
151
|
-
|
|
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
|
-
|
|
100
|
+
### Supported `MetadataFieldType` Values
|
|
154
101
|
|
|
155
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
143
|
+
---
|
|
172
144
|
|
|
173
|
-
|
|
145
|
+
## Environment Variables
|
|
174
146
|
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
+
---
|
|
184
156
|
|
|
185
|
-
|
|
157
|
+
## REST APIs / Routes
|
|
186
158
|
|
|
187
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
228
|
+
---
|
|
204
229
|
|
|
205
|
-
|
|
230
|
+
## Services
|
|
206
231
|
|
|
207
|
-
|
|
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
|
-
|
|
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
|
-
|
|
236
|
+
---
|
|
300
237
|
|
|
301
|
-
|
|
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
|
-
|
|
240
|
+
No custom workflows or steps are defined in this plugin source.
|
|
320
241
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
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
|
-
|
|
364
|
-
- `npm run dev` - Run in development/watch mode
|
|
349
|
+
### Save fails with backend error
|
|
365
350
|
|
|
366
|
-
|
|
351
|
+
- **Cause:** invalid payload, endpoint mismatch, or authorization issue.
|
|
352
|
+
- **Fix:** verify resolved endpoint, admin auth/session, and descriptor/value shape.
|
|
367
353
|
|
|
368
|
-
|
|
354
|
+
### File upload fails in metadata field
|
|
369
355
|
|
|
370
|
-
|
|
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
|
-
|
|
359
|
+
### Default and custom metadata sections both visible
|
|
373
360
|
|
|
374
|
-
|
|
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
|
-
|
|
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/)
|