medusa-dynamic-metadata 0.0.1
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 +1729 -0
- package/.medusa/server/src/admin/index.mjs +1730 -0
- package/.medusa/server/src/api/admin/metadata-config/route.js +22 -0
- package/.medusa/server/src/config/entity-registry.js +469 -0
- package/.medusa/server/src/config/metadata-options.js +83 -0
- package/.medusa/server/src/config/zone-resolver.js +418 -0
- package/.medusa/server/src/index.js +41 -0
- package/.medusa/server/src/shared/metadata/types.js +5 -0
- package/.medusa/server/src/shared/metadata/utils.js +153 -0
- package/README.md +292 -0
- package/package.json +71 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeMetadataDescriptors = normalizeMetadataDescriptors;
|
|
4
|
+
exports.getDescriptorMap = getDescriptorMap;
|
|
5
|
+
exports.buildInitialFormState = buildInitialFormState;
|
|
6
|
+
exports.buildMetadataPayload = buildMetadataPayload;
|
|
7
|
+
exports.hasMetadataChanges = hasMetadataChanges;
|
|
8
|
+
exports.validateValueForDescriptor = validateValueForDescriptor;
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
const VALID_FIELD_TYPES = new Set(types_1.METADATA_FIELD_TYPES);
|
|
11
|
+
const BOOLEAN_TRUES = new Set(["true", "1", "yes", "y", "on"]);
|
|
12
|
+
const BOOLEAN_FALSES = new Set(["false", "0", "no", "n", "off"]);
|
|
13
|
+
function normalizeMetadataDescriptors(input) {
|
|
14
|
+
if (!Array.isArray(input))
|
|
15
|
+
return [];
|
|
16
|
+
const seenKeys = new Set();
|
|
17
|
+
return input
|
|
18
|
+
.filter((item) => item && typeof item === "object")
|
|
19
|
+
.map(item => {
|
|
20
|
+
const key = normalizeKey(item.key);
|
|
21
|
+
const type = normalizeType(item.type);
|
|
22
|
+
const label = normalizeLabel(item.label);
|
|
23
|
+
const filterable = !!(item.filterable);
|
|
24
|
+
return { key, type, label, filterable };
|
|
25
|
+
})
|
|
26
|
+
.filter(({ key, type }) => key && type && !seenKeys.has(key))
|
|
27
|
+
.map(({ key, type, label, filterable }) => {
|
|
28
|
+
seenKeys.add(key);
|
|
29
|
+
return {
|
|
30
|
+
key: key,
|
|
31
|
+
type: type,
|
|
32
|
+
...(label && { label }),
|
|
33
|
+
...(filterable && { filterable: true })
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function getDescriptorMap(descriptors) {
|
|
38
|
+
return descriptors.reduce((acc, descriptor) => {
|
|
39
|
+
acc[descriptor.key] = descriptor;
|
|
40
|
+
return acc;
|
|
41
|
+
}, {});
|
|
42
|
+
}
|
|
43
|
+
function buildInitialFormState(descriptors, metadata) {
|
|
44
|
+
const base = metadata && typeof metadata === "object" ? metadata : {};
|
|
45
|
+
return descriptors.reduce((acc, descriptor) => {
|
|
46
|
+
acc[descriptor.key] = normalizeFormValue(descriptor, base[descriptor.key]);
|
|
47
|
+
return acc;
|
|
48
|
+
}, {});
|
|
49
|
+
}
|
|
50
|
+
function buildMetadataPayload({ descriptors, values, originalMetadata, }) {
|
|
51
|
+
const base = originalMetadata && typeof originalMetadata === "object"
|
|
52
|
+
? { ...originalMetadata }
|
|
53
|
+
: {};
|
|
54
|
+
descriptors.forEach((descriptor) => {
|
|
55
|
+
const coerced = coerceMetadataValue(descriptor, values[descriptor.key]);
|
|
56
|
+
if (coerced === undefined) {
|
|
57
|
+
delete base[descriptor.key];
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
base[descriptor.key] = coerced;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return base;
|
|
64
|
+
}
|
|
65
|
+
function hasMetadataChanges({ descriptors, values, originalMetadata, }) {
|
|
66
|
+
const next = buildMetadataPayload({ descriptors, values, originalMetadata });
|
|
67
|
+
const prev = originalMetadata && typeof originalMetadata === "object"
|
|
68
|
+
? originalMetadata
|
|
69
|
+
: {};
|
|
70
|
+
return descriptors.some(({ key }) => !isDeepEqual(prev[key], next[key]));
|
|
71
|
+
}
|
|
72
|
+
function validateValueForDescriptor(descriptor, value) {
|
|
73
|
+
if (descriptor.type === "number") {
|
|
74
|
+
if (value == null || value === "")
|
|
75
|
+
return undefined;
|
|
76
|
+
const num = typeof value === "number" ? value : Number(String(value).trim());
|
|
77
|
+
return isNaN(num) ? "Enter a valid number" : undefined;
|
|
78
|
+
}
|
|
79
|
+
if (descriptor.type === "file") {
|
|
80
|
+
if (!value)
|
|
81
|
+
return undefined;
|
|
82
|
+
try {
|
|
83
|
+
new URL(String(value).trim());
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return "Enter a valid URL";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
function normalizeFormValue(descriptor, currentValue) {
|
|
93
|
+
if (descriptor.type === "bool")
|
|
94
|
+
return Boolean(currentValue);
|
|
95
|
+
if ((descriptor.type === "number" || descriptor.type === "text") && typeof currentValue === "number") {
|
|
96
|
+
return currentValue.toString();
|
|
97
|
+
}
|
|
98
|
+
if (typeof currentValue === "string" || typeof currentValue === "number") {
|
|
99
|
+
return String(currentValue);
|
|
100
|
+
}
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
function coerceMetadataValue(descriptor, value) {
|
|
104
|
+
if (value == null || value === "")
|
|
105
|
+
return undefined;
|
|
106
|
+
if (descriptor.type === "bool") {
|
|
107
|
+
if (typeof value === "boolean")
|
|
108
|
+
return value;
|
|
109
|
+
if (typeof value === "number")
|
|
110
|
+
return value !== 0;
|
|
111
|
+
const normalized = String(value).trim().toLowerCase();
|
|
112
|
+
if (!normalized)
|
|
113
|
+
return undefined;
|
|
114
|
+
if (BOOLEAN_TRUES.has(normalized))
|
|
115
|
+
return true;
|
|
116
|
+
if (BOOLEAN_FALSES.has(normalized))
|
|
117
|
+
return false;
|
|
118
|
+
return Boolean(value);
|
|
119
|
+
}
|
|
120
|
+
if (descriptor.type === "number") {
|
|
121
|
+
const num = typeof value === "number" ? value : Number(String(value).trim());
|
|
122
|
+
return isNaN(num) ? undefined : num;
|
|
123
|
+
}
|
|
124
|
+
// For text and file types, trim and check if empty
|
|
125
|
+
const trimmed = String(value).trim();
|
|
126
|
+
if (!trimmed)
|
|
127
|
+
return undefined;
|
|
128
|
+
return trimmed;
|
|
129
|
+
}
|
|
130
|
+
function normalizeKey(value) {
|
|
131
|
+
return typeof value === "string" ? value.trim() || undefined : undefined;
|
|
132
|
+
}
|
|
133
|
+
function normalizeType(value) {
|
|
134
|
+
if (typeof value !== "string")
|
|
135
|
+
return undefined;
|
|
136
|
+
const type = value.trim().toLowerCase();
|
|
137
|
+
return VALID_FIELD_TYPES.has(type) ? type : undefined;
|
|
138
|
+
}
|
|
139
|
+
function normalizeLabel(value) {
|
|
140
|
+
return typeof value === "string" ? value.trim() || undefined : undefined;
|
|
141
|
+
}
|
|
142
|
+
function isDeepEqual(a, b) {
|
|
143
|
+
if (a === b)
|
|
144
|
+
return true;
|
|
145
|
+
if (!a || !b || typeof a !== "object" || typeof b !== "object")
|
|
146
|
+
return false;
|
|
147
|
+
const aKeys = Object.keys(a);
|
|
148
|
+
const bKeys = Object.keys(b);
|
|
149
|
+
if (aKeys.length !== bKeys.length)
|
|
150
|
+
return false;
|
|
151
|
+
return aKeys.every(key => isDeepEqual(a[key], b[key]));
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvc2hhcmVkL21ldGFkYXRhL3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBY0Esb0VBeUJDO0FBRUQsNENBS0M7QUFFRCxzREFVQztBQUVELG9EQXdCQztBQUVELGdEQWVDO0FBRUQsZ0VBdUJDO0FBOUhELG1DQUtnQjtBQUtoQixNQUFNLGlCQUFpQixHQUFHLElBQUksR0FBRyxDQUFvQiw0QkFBb0IsQ0FBQyxDQUFBO0FBQzFFLE1BQU0sYUFBYSxHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsTUFBTSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUE7QUFDOUQsTUFBTSxjQUFjLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxPQUFPLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQTtBQUVoRSxTQUFnQiw0QkFBNEIsQ0FBQyxLQUFjO0lBQ3pELElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUFFLE9BQU8sRUFBRSxDQUFBO0lBRXBDLE1BQU0sUUFBUSxHQUFHLElBQUksR0FBRyxFQUFVLENBQUE7SUFDbEMsT0FBTyxLQUFLO1NBQ1QsTUFBTSxDQUFDLENBQUMsSUFBSSxFQUFtQyxFQUFFLENBQ2hELElBQUksSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLENBQ2pDO1NBQ0EsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFO1FBQ1YsTUFBTSxHQUFHLEdBQUcsWUFBWSxDQUFFLElBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQTtRQUMzQyxNQUFNLElBQUksR0FBRyxhQUFhLENBQUUsSUFBWSxDQUFDLElBQUksQ0FBQyxDQUFBO1FBQzlDLE1BQU0sS0FBSyxHQUFHLGNBQWMsQ0FBRSxJQUFZLENBQUMsS0FBSyxDQUFDLENBQUE7UUFD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
|
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
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
|
+
- **Flexible Schema**: Define metadata fields with types (text, number, bool, file) and filterability
|
|
49
|
+
- **Zero Code Changes**: Add metadata to new entities by updating configuration only
|
|
50
|
+
|
|
51
|
+
## Supported Entities
|
|
52
|
+
|
|
53
|
+
This plugin supports metadata configuration for the following entities that have valid admin detail pages:
|
|
54
|
+
|
|
55
|
+
1. **products** - Widget zone: `product.details.after`
|
|
56
|
+
2. **orders** - Widget zone: `order.details.after`
|
|
57
|
+
3. **categories** - Widget zone: `product_category.details.after`
|
|
58
|
+
4. **collections** - Widget zone: `product_collection.details.after`
|
|
59
|
+
5. **customers** - Widget zone: `customer.details.after`
|
|
60
|
+
6. **regions** - Widget zone: `region.details.after`
|
|
61
|
+
7. **sales_channels** - Widget zone: `sales_channel.details.after`
|
|
62
|
+
8. **stores** - Widget zone: `store.details.after`
|
|
63
|
+
9. **promotions** - Widget zone: `promotion.details.after`
|
|
64
|
+
10. **campaigns** - Widget zone: `campaign.details.after`
|
|
65
|
+
11. **price_lists** - Widget zone: `price_list.details.after`
|
|
66
|
+
12. **shipping_profiles** - Widget zone: `shipping_profile.details.after`
|
|
67
|
+
13. **inventory_items** - Widget zone: `inventory_item.details.after`
|
|
68
|
+
14. **product_variants** - Widget zone: `product_variant.details.after`
|
|
69
|
+
|
|
70
|
+
> **Note**: Only entities with valid admin detail pages can be configured. The plugin includes pre-built widget files for all 14 supported entities above.
|
|
71
|
+
|
|
72
|
+
## Getting Started
|
|
73
|
+
|
|
74
|
+
### Installation
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install medusa-dynamic-metadata
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Configuration
|
|
81
|
+
|
|
82
|
+
Add the plugin to your `medusa-config.ts`:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { defineConfig } from "@medusajs/framework"
|
|
86
|
+
|
|
87
|
+
export default defineConfig({
|
|
88
|
+
// ... other config
|
|
89
|
+
plugins: [
|
|
90
|
+
{
|
|
91
|
+
resolve: "medusa-dynamic-metadata",
|
|
92
|
+
options: {
|
|
93
|
+
entities: {
|
|
94
|
+
products: {
|
|
95
|
+
descriptors: [
|
|
96
|
+
{ key: "brand", label: "Brand", type: "text", filterable: true },
|
|
97
|
+
{ key: "warranty_years", label: "Warranty (Years)", type: "number" },
|
|
98
|
+
],
|
|
99
|
+
expose_client_helpers: true,
|
|
100
|
+
filterable: true,
|
|
101
|
+
},
|
|
102
|
+
orders: {
|
|
103
|
+
descriptors: [
|
|
104
|
+
{ key: "source", label: "Source", type: "text" },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Widget Files
|
|
115
|
+
|
|
116
|
+
The plugin includes minimal wrapper widget files for all 14 supported entities listed above. Each wrapper:
|
|
117
|
+
|
|
118
|
+
1. Specifies the widget zone (required by Medusa's widget system)
|
|
119
|
+
2. Delegates to the universal `UniversalMetadataWidget` component
|
|
120
|
+
3. Automatically detects the entity type at runtime
|
|
121
|
+
|
|
122
|
+
**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/widgets/generic-metadata-widget.template.tsx`.
|
|
123
|
+
|
|
124
|
+
### Building the Plugin
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm run build
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This builds the plugin with all extensions.
|
|
131
|
+
|
|
132
|
+
### Development
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm run dev
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
This runs the plugin in watch mode for development.
|
|
139
|
+
|
|
140
|
+
## How It Works
|
|
141
|
+
|
|
142
|
+
### Architecture Flow
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
Configuration (medusa-config.ts)
|
|
146
|
+
↓
|
|
147
|
+
Plugin Initialization
|
|
148
|
+
↓
|
|
149
|
+
Widget Files (minimal wrappers with zones)
|
|
150
|
+
↓
|
|
151
|
+
UniversalMetadataWidget (runtime entity detection)
|
|
152
|
+
↓
|
|
153
|
+
MetadataTableWidget (UI component)
|
|
154
|
+
↓
|
|
155
|
+
API Endpoints (save/load metadata)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Universal Widget Architecture
|
|
159
|
+
|
|
160
|
+
The plugin uses a single universal widget component (`UniversalMetadataWidget`) that works for all entity types:
|
|
161
|
+
|
|
162
|
+
1. **Runtime Entity Detection**: The widget automatically detects the entity type using:
|
|
163
|
+
- **Zone-based detection** (primary): Resolves entity type from the widget zone (e.g., `"product.details.after"` → `"products"`)
|
|
164
|
+
- **Data structure detection** (fallback): Pattern matching on data properties (e.g., `handle`, `title` → products; `display_id`, `status` → orders)
|
|
165
|
+
|
|
166
|
+
2. **Configuration Loading**: Fetches metadata descriptors from `/admin/metadata-config?entity={entityType}` based on detected entity type
|
|
167
|
+
|
|
168
|
+
3. **Dynamic Rendering**: Renders appropriate form fields (text, number, bool, file) based on the descriptors
|
|
169
|
+
|
|
170
|
+
4. **Metadata Management**: Saves metadata through standard Medusa entity API endpoints
|
|
171
|
+
|
|
172
|
+
### Widget Structure
|
|
173
|
+
|
|
174
|
+
Widget files are minimal wrappers that:
|
|
175
|
+
- Specify the widget zone (required by Medusa)
|
|
176
|
+
- Pass the zone to `UniversalMetadataWidget`
|
|
177
|
+
- Let the universal component handle all entity-specific logic
|
|
178
|
+
|
|
179
|
+
Example widget file:
|
|
180
|
+
```typescript
|
|
181
|
+
const Widget = ({ data }) => {
|
|
182
|
+
return <UniversalMetadataWidget data={data} zone="product.details.after" />
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export const config = defineWidgetConfig({
|
|
186
|
+
zone: "product.details.after"
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Creating Widgets for New Entities
|
|
191
|
+
|
|
192
|
+
To add metadata support for a new entity:
|
|
193
|
+
|
|
194
|
+
1. **Add configuration** in `medusa-config.ts`:
|
|
195
|
+
```typescript
|
|
196
|
+
entities: {
|
|
197
|
+
customers: {
|
|
198
|
+
descriptors: [
|
|
199
|
+
{ key: "preferred_language", type: "text" }
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
2. **Create a minimal widget file** (if zone doesn't match existing patterns):
|
|
206
|
+
- Copy `src/admin/widgets/generic-metadata-widget.template.tsx`
|
|
207
|
+
- Update the zone to match your entity's admin zone
|
|
208
|
+
- The universal widget will automatically detect the entity type
|
|
209
|
+
|
|
210
|
+
## Configuration Options
|
|
211
|
+
|
|
212
|
+
### Entity Configuration
|
|
213
|
+
|
|
214
|
+
Each entity in the configuration supports:
|
|
215
|
+
|
|
216
|
+
- `descriptors`: Array of metadata field definitions
|
|
217
|
+
- `key`: Unique identifier for the field
|
|
218
|
+
- `label`: Display label (optional, defaults to key)
|
|
219
|
+
- `type`: Field type (`"text"`, `"number"`, `"bool"`, `"file"`)
|
|
220
|
+
- `filterable`: Whether the field can be used for filtering (default: `false`)
|
|
221
|
+
- `expose_client_helpers`: Expose client-side helper functions (default: `false`)
|
|
222
|
+
- `filterable`: Enable filtering for this entity (default: `false`)
|
|
223
|
+
- `widget_zone`: Custom widget zone override (optional)
|
|
224
|
+
- `api_endpoint`: Custom API endpoint override (optional)
|
|
225
|
+
|
|
226
|
+
## API
|
|
227
|
+
|
|
228
|
+
The plugin provides admin API endpoints for metadata management:
|
|
229
|
+
|
|
230
|
+
- `GET /admin/metadata-config?entity={entityType}` - Get metadata descriptors for an entity
|
|
231
|
+
- Metadata is managed through the standard Medusa entity APIs with metadata fields
|
|
232
|
+
|
|
233
|
+
## Examples
|
|
234
|
+
|
|
235
|
+
### Basic Configuration
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
{
|
|
239
|
+
entities: {
|
|
240
|
+
products: {
|
|
241
|
+
descriptors: [
|
|
242
|
+
{ key: "brand", type: "text", filterable: true },
|
|
243
|
+
{ key: "warranty_years", type: "number" },
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Advanced Configuration
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
{
|
|
254
|
+
entities: {
|
|
255
|
+
products: {
|
|
256
|
+
descriptors: [
|
|
257
|
+
{ key: "brand", label: "Brand Name", type: "text", filterable: true },
|
|
258
|
+
{ key: "warranty_years", label: "Warranty Period", type: "number" },
|
|
259
|
+
{ key: "has_warranty", label: "Has Warranty", type: "bool" },
|
|
260
|
+
{ key: "manual_pdf", label: "Manual PDF", type: "file" },
|
|
261
|
+
],
|
|
262
|
+
expose_client_helpers: true,
|
|
263
|
+
filterable: true,
|
|
264
|
+
widget_zone: "product.details.after", // Optional override
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Scripts
|
|
271
|
+
|
|
272
|
+
- `npm run build` - Build the plugin
|
|
273
|
+
- `npm run dev` - Run in development/watch mode
|
|
274
|
+
|
|
275
|
+
## What is Medusa
|
|
276
|
+
|
|
277
|
+
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.
|
|
278
|
+
|
|
279
|
+
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.
|
|
280
|
+
|
|
281
|
+
## Community & Contributions
|
|
282
|
+
|
|
283
|
+
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.
|
|
284
|
+
|
|
285
|
+
Join our [Discord server](https://discord.com/invite/medusajs) to meet other community members.
|
|
286
|
+
|
|
287
|
+
## Other channels
|
|
288
|
+
|
|
289
|
+
- [GitHub Issues](https://github.com/medusajs/medusa/issues)
|
|
290
|
+
- [Twitter](https://twitter.com/medusajs)
|
|
291
|
+
- [LinkedIn](https://www.linkedin.com/company/medusajs)
|
|
292
|
+
- [Medusa Blog](https://medusajs.com/blog/)
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "medusa-dynamic-metadata",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A starter for Medusa plugins.",
|
|
5
|
+
"author": "Medusa (https://medusajs.com)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": [
|
|
8
|
+
".medusa/server"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
"./package.json": "./package.json",
|
|
12
|
+
"./workflows": "./.medusa/server/src/workflows/index.js",
|
|
13
|
+
"./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
|
|
14
|
+
"./modules/*": "./.medusa/server/src/modules/*/index.js",
|
|
15
|
+
"./providers/*": "./.medusa/server/src/providers/*/index.js",
|
|
16
|
+
"./*": "./.medusa/server/src/*.js",
|
|
17
|
+
"./admin": {
|
|
18
|
+
"import": "./.medusa/server/src/admin/index.mjs",
|
|
19
|
+
"require": "./.medusa/server/src/admin/index.js",
|
|
20
|
+
"default": "./.medusa/server/src/admin/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"medusa",
|
|
25
|
+
"plugin",
|
|
26
|
+
"medusa-plugin-other",
|
|
27
|
+
"medusa-plugin",
|
|
28
|
+
"medusa-v2"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "medusa plugin:build",
|
|
32
|
+
"dev": "medusa plugin:develop",
|
|
33
|
+
"prepublishOnly": "medusa plugin:build"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@medusajs/admin-sdk": "2.12.3",
|
|
37
|
+
"@medusajs/cli": "2.12.3",
|
|
38
|
+
"@medusajs/framework": "2.12.3",
|
|
39
|
+
"@medusajs/medusa": "2.12.3",
|
|
40
|
+
"@medusajs/test-utils": "2.12.3",
|
|
41
|
+
"@medusajs/ui": "4.0.25",
|
|
42
|
+
"@medusajs/icons": "2.12.3",
|
|
43
|
+
"@swc/core": "1.5.7",
|
|
44
|
+
"@types/node": "^20.0.0",
|
|
45
|
+
"@types/react": "^18.3.2",
|
|
46
|
+
"@types/react-dom": "^18.2.25",
|
|
47
|
+
"prop-types": "^15.8.1",
|
|
48
|
+
"react": "^18.2.0",
|
|
49
|
+
"react-dom": "^18.2.0",
|
|
50
|
+
"ts-node": "^10.9.2",
|
|
51
|
+
"typescript": "^5.6.2",
|
|
52
|
+
"vite": "^5.2.11",
|
|
53
|
+
"yalc": "^1.0.0-pre.53",
|
|
54
|
+
"zod": "^3.23.8"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"zod": "^3.23.8"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@medusajs/admin-sdk": "2.12.3",
|
|
61
|
+
"@medusajs/cli": "2.12.3",
|
|
62
|
+
"@medusajs/framework": "2.12.3",
|
|
63
|
+
"@medusajs/test-utils": "2.12.3",
|
|
64
|
+
"@medusajs/medusa": "2.12.3",
|
|
65
|
+
"@medusajs/ui": "4.0.25",
|
|
66
|
+
"@medusajs/icons": "2.12.3"
|
|
67
|
+
},
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=20"
|
|
70
|
+
}
|
|
71
|
+
}
|