@empty-complete-org/medusa-product-attributes 0.14.1 → 1.0.0

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.
Files changed (36) hide show
  1. package/.medusa/server/src/admin/index.js +441 -202
  2. package/.medusa/server/src/admin/index.mjs +442 -203
  3. package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/[id]/apply/route.js +1 -1
  4. package/.medusa/server/src/api/admin/attribute-templates/[id]/apply/route.js.map +1 -0
  5. package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/route.js +6 -6
  6. package/.medusa/server/src/api/admin/attribute-templates/route.js.map +1 -0
  7. package/.medusa/server/src/api/admin/category/[categoryId]/custom-attributes/route.js +1 -1
  8. package/.medusa/server/src/api/admin/category/[categoryId]/custom-attributes/route.js.map +1 -1
  9. package/.medusa/server/src/api/admin/global-attributes/route.d.ts +4 -0
  10. package/.medusa/server/src/api/admin/global-attributes/route.js +30 -0
  11. package/.medusa/server/src/api/admin/global-attributes/route.js.map +1 -0
  12. package/.medusa/server/src/api/admin/product/[productId]/attribute-schema/route.d.ts +2 -0
  13. package/.medusa/server/src/api/admin/product/[productId]/attribute-schema/route.js +34 -0
  14. package/.medusa/server/src/api/admin/product/[productId]/attribute-schema/route.js.map +1 -0
  15. package/.medusa/server/src/modules/product-attributes/index.d.ts +5 -5
  16. package/.medusa/server/src/modules/product-attributes/migrations/Migration20260407120000.d.ts +5 -0
  17. package/.medusa/server/src/modules/product-attributes/migrations/Migration20260407120000.js +58 -0
  18. package/.medusa/server/src/modules/product-attributes/migrations/Migration20260407120000.js.map +1 -0
  19. package/.medusa/server/src/modules/product-attributes/models/{attribute-preset.d.ts → attribute-template.d.ts} +3 -3
  20. package/.medusa/server/src/modules/product-attributes/models/{attribute-preset.js → attribute-template.js} +3 -3
  21. package/.medusa/server/src/modules/product-attributes/models/attribute-template.js.map +1 -0
  22. package/.medusa/server/src/modules/product-attributes/models/category-custom-attribute.d.ts +2 -1
  23. package/.medusa/server/src/modules/product-attributes/models/category-custom-attribute.js +2 -1
  24. package/.medusa/server/src/modules/product-attributes/models/category-custom-attribute.js.map +1 -1
  25. package/.medusa/server/src/modules/product-attributes/models/product-custom-attribute.d.ts +2 -1
  26. package/.medusa/server/src/modules/product-attributes/service.d.ts +56 -17
  27. package/.medusa/server/src/modules/product-attributes/service.js +44 -18
  28. package/.medusa/server/src/modules/product-attributes/service.js.map +1 -1
  29. package/README.md +96 -312
  30. package/README.ru.md +85 -0
  31. package/package.json +1 -1
  32. package/.medusa/server/src/api/admin/attribute-presets/[id]/apply/route.js.map +0 -1
  33. package/.medusa/server/src/api/admin/attribute-presets/route.js.map +0 -1
  34. package/.medusa/server/src/modules/product-attributes/models/attribute-preset.js.map +0 -1
  35. /package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/[id]/apply/route.d.ts +0 -0
  36. /package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/route.d.ts +0 -0
@@ -7,11 +7,11 @@ const utils_1 = require("@medusajs/framework/utils");
7
7
  const transliteration_1 = require("transliteration");
8
8
  const category_custom_attribute_1 = __importDefault(require("./models/category-custom-attribute"));
9
9
  const product_custom_attribute_1 = __importDefault(require("./models/product-custom-attribute"));
10
- const attribute_preset_1 = __importDefault(require("./models/attribute-preset"));
10
+ const attribute_template_1 = __importDefault(require("./models/attribute-template"));
11
11
  const models = {
12
12
  CategoryCustomAttribute: category_custom_attribute_1.default,
13
13
  ProductCustomAttribute: product_custom_attribute_1.default,
14
- AttributePreset: attribute_preset_1.default,
14
+ AttributeTemplate: attribute_template_1.default,
15
15
  };
16
16
  // @ts-ignore - MedusaService dynamically generates methods based on models
17
17
  class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
@@ -20,25 +20,42 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
20
20
  return await this.listCategoryCustomAttributes({
21
21
  category_id: categoryId,
22
22
  deleted_at: null,
23
+ is_global: false,
23
24
  });
24
25
  }
25
- async getAttributesByCategoryIds(categoryIds) {
26
+ async getAttributesByCategoryIds(categoryIds, opts = { includeGlobals: true }) {
27
+ // Always include global attributes (applies to every product) unless disabled
28
+ let globalsMapped = [];
29
+ if (opts.includeGlobals) {
30
+ // @ts-ignore - method generated by MedusaService
31
+ const globals = await this.listCategoryCustomAttributes({
32
+ is_global: true,
33
+ deleted_at: null,
34
+ });
35
+ globalsMapped = globals.map((attr) => ({
36
+ ...attr,
37
+ inherited: true,
38
+ source_category_id: null,
39
+ source: "global",
40
+ }));
41
+ }
26
42
  if (!categoryIds || categoryIds.length === 0) {
27
- return [];
43
+ return globalsMapped;
28
44
  }
29
45
  // @ts-ignore - method generated by MedusaService
30
46
  const attributes = await this.listCategoryCustomAttributes({
31
47
  category_id: categoryIds,
48
+ is_global: false,
32
49
  deleted_at: null,
33
50
  });
34
- // First id in the list is the leaf category — its attributes are "own",
35
- // everything else is inherited from an ancestor.
36
51
  const leafId = categoryIds[0];
37
- return attributes.map((attr) => ({
52
+ const categoryMapped = attributes.map((attr) => ({
38
53
  ...attr,
39
54
  inherited: attr.category_id !== leafId,
40
55
  source_category_id: attr.category_id,
56
+ source: "category",
41
57
  }));
58
+ return [...globalsMapped, ...categoryMapped];
42
59
  }
43
60
  async createCategoryAttribute(data) {
44
61
  const key = this.generateKey(data.label);
@@ -47,7 +64,9 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
47
64
  ...data,
48
65
  key,
49
66
  unit: data.unit ?? null,
67
+ category_id: data.category_id ?? null,
50
68
  is_standard: data.is_standard ?? false,
69
+ is_global: data.is_global ?? false,
51
70
  sort_order: data.sort_order ?? 0,
52
71
  });
53
72
  }
@@ -59,6 +78,13 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
59
78
  // @ts-ignore - method generated by MedusaService
60
79
  return await this.updateCategoryCustomAttributes([{ id, ...updateData }]);
61
80
  }
81
+ async listGlobalAttributes() {
82
+ // @ts-ignore - generated
83
+ return await this.listCategoryCustomAttributes({
84
+ is_global: true,
85
+ deleted_at: null,
86
+ });
87
+ }
62
88
  async createProductAttribute(data) {
63
89
  // @ts-ignore - method generated by MedusaService
64
90
  return await this.createProductCustomAttributes(data);
@@ -76,14 +102,14 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
76
102
  relations: ["category_custom_attribute"],
77
103
  });
78
104
  }
79
- async listPresets() {
105
+ async listTemplates() {
80
106
  // @ts-ignore - generated
81
- return await this.listAttributePresets({ deleted_at: null });
107
+ return await this.listAttributeTemplates({ deleted_at: null });
82
108
  }
83
- async createPreset(data) {
109
+ async createTemplate(data) {
84
110
  const key = this.generateKey(data.label);
85
111
  // @ts-ignore - generated
86
- return await this.createAttributePresets({
112
+ return await this.createAttributeTemplates({
87
113
  label: data.label,
88
114
  key,
89
115
  type: data.type || "text",
@@ -91,21 +117,21 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
91
117
  description: data.description ?? null,
92
118
  });
93
119
  }
94
- async updatePreset(id, data) {
120
+ async updateTemplate(id, data) {
95
121
  const updateData = { ...data };
96
122
  if (data.label) {
97
123
  updateData.key = this.generateKey(data.label);
98
124
  }
99
125
  // @ts-ignore - generated
100
- return await this.updateAttributePresets([{ id, ...updateData }]);
126
+ return await this.updateAttributeTemplates([{ id, ...updateData }]);
101
127
  }
102
- async applyPresetToCategory(presetId, categoryId) {
128
+ async applyTemplateToCategory(templateId, categoryId) {
103
129
  // @ts-ignore - generated
104
- const preset = await this.retrieveAttributePreset(presetId);
130
+ const template = await this.retrieveAttributeTemplate(templateId);
105
131
  return await this.createCategoryAttribute({
106
- label: preset.label,
107
- type: preset.type,
108
- unit: preset.unit,
132
+ label: template.label,
133
+ type: template.type,
134
+ unit: template.unit,
109
135
  category_id: categoryId,
110
136
  is_standard: true,
111
137
  });
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../../src/modules/product-attributes/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../../src/modules/product-attributes/service.ts"],"names":[],"mappings":"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"}
package/README.md CHANGED
@@ -1,367 +1,151 @@
1
1
  <div align="center">
2
- <img src="https://raw.githubusercontent.com/empty-complete/medusa-product-attributes/main/assets/logo_medusa-custom-attributes.png" alt="logo medusa product attributes module repo" width="120">
3
- <h1>Product Attributes Module for Medusa v2</h1>
4
- <p><i>Extend your store's capabilities with a flexible attribute system for products and categories</i></p>
5
-
2
+ <img src="./assets/logo.png" alt="medusa-product-attributes logo" width="120">
3
+ <h1>Medusa Product Attributes</h1>
4
+ <p><i>A flexible attribute system for Medusa v2 text, number, file, boolean, with units and category inheritance</i></p>
5
+
6
6
  <p>
7
7
  <a href="https://github.com/empty-complete/medusa-product-attributes/blob/main/LICENSE">
8
- <img src="https://img.shields.io/github/license/empty-complete/medusa-product-attributes" alt="License">
8
+ <img src="https://img.shields.io/npm/l/@empty-complete-org/medusa-product-attributes" alt="License">
9
+ </a>
10
+ <a href="https://www.npmjs.com/package/@empty-complete-org/medusa-product-attributes">
11
+ <img src="https://img.shields.io/npm/v/@empty-complete-org/medusa-product-attributes" alt="npm version">
9
12
  </a>
10
- <a href="https://www.npmjs.com/package/@empty-complete/medusa-product-attributes">
11
- <img src="https://img.shields.io/npm/v/@empty-complete/medusa-product-attributes" alt="npm version">
13
+ <a href="https://www.npmjs.com/package/@empty-complete-org/medusa-product-attributes">
14
+ <img src="https://img.shields.io/npm/dm/@empty-complete-org/medusa-product-attributes" alt="npm downloads">
12
15
  </a>
13
16
  <a href="https://github.com/empty-complete/medusa-product-attributes/actions/workflows/ci.yml">
14
17
  <img src="https://github.com/empty-complete/medusa-product-attributes/actions/workflows/ci.yml/badge.svg" alt="CI Status">
15
18
  </a>
16
- <a href="https://github.com/empty-complete/medusa-product-attributes/commits/main">
17
- <img src="https://img.shields.io/github/commit-activity/m/empty-complete/medusa-product-attributes" alt="GitHub commit activity">
18
- </a>
19
- <a href="https://www.npmjs.com/package/@empty-complete/medusa-product-attributes">
20
- <img src="https://img.shields.io/npm/dm/@empty-complete/medusa-product-attributes" alt="npm downloads">
21
- </a>
22
- </p>
23
-
24
- <p>
25
- <a href="https://medusajs.com/">
26
- <img src="https://img.shields.io/badge/Medusa-v2-000000" alt="Medusa v2">
27
- </a>
28
- <a href="https://www.typescriptlang.org/">
29
- <img src="https://img.shields.io/badge/TypeScript-5.0+-3178C6" alt="TypeScript">
30
- </a>
31
- <a href="https://nodejs.org/">
32
- <img src="https://img.shields.io/badge/Node.js-%3E=20-339933" alt="Node.js">
33
- </a>
34
19
  </p>
35
-
20
+
36
21
  <p>
37
- <a href="https://t.me/kinesis_lab">
38
- <img src="https://img.shields.io/badge/Telegram-follow-229ED9?logo=telegram" alt="Telegram">
39
- </a>
22
+ <a href="https://medusajs.com/"><img src="https://img.shields.io/badge/Medusa-v2-000000" alt="Medusa v2"></a>
23
+ <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.0+-3178C6" alt="TypeScript"></a>
24
+ <a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-%3E=20-339933" alt="Node.js"></a>
40
25
  </p>
26
+
27
+ <p><a href="./README.ru.md">Русская версия</a></p>
41
28
  </div>
42
29
 
43
30
  ---
44
31
 
45
- ## Features
32
+ ## Features
46
33
 
47
- | Feature | Description |
48
- |---------|----------|
49
- | 📝 **Text Attributes** | Arbitrary text values for products |
50
- | 🔢 **Numeric Attributes** | Numbers with support for units (kg, m, pcs) |
51
- | 📎 **File Attributes** | File uploads: certificates, 3D models, manuals |
52
- | 🏷️ **Standard Keys** | Pre-defined attributes: `certificates`, `3dmodel`, `manual` |
53
- | 📂 **Category Templates** | Attribute inheritance from category to products |
54
- | 🗑️ **Soft Delete** | Safe deletion with recovery capability |
34
+ - **4 attribute types**: text, number (with units), file, boolean
35
+ - **Category inheritance**: attributes defined on a parent category are automatically inherited by all subcategories
36
+ - **Global attributes**: applied to every product in the store
37
+ - **Templates**: reusable attribute blueprints apply to any category in one click
38
+ - **Automatic slug generation**: labels are transliterated (works with Russian, Chinese, any script)
39
+ - **Smart file naming**: uploaded images are renamed to `{product_handle}_{attr_key}.ext`
40
+ - **Admin UI included**: category widget, product widget, two Settings pages
41
+ - **Ships with migrations**: just run `npx medusa db:migrate`
55
42
 
56
- ---
43
+ ## Screenshots
57
44
 
58
- ### Requirements
45
+ ### Category widget with inheritance
46
+ ![Category widget](./assets/category-widget.png)
59
47
 
60
- | Dependency | Version |
61
- |------------|---------|
62
- | Medusa | ^2.0.0 |
63
- | Node.js | >=20 |
64
- | TypeScript | ^5.0.0 |
48
+ ### Product attribute values
49
+ ![Product widget](./assets/product-widget.png)
65
50
 
66
- ---
51
+ ### Global attributes settings
52
+ ![Global attributes](./assets/global-attributes.png)
67
53
 
68
- ## 🚀 Quick Start
54
+ ### Attribute templates settings
55
+ ![Templates](./assets/templates.png)
69
56
 
70
- ### 📦 Installation
57
+ ## Installation
71
58
 
72
59
  ```bash
73
- # Via npm
74
- npm install @empty-complete/medusa-product-attributes
75
-
76
- # Via pnpm
77
- pnpm add @empty-complete/medusa-product-attributes
78
-
79
- # Via yarn
80
- yarn add @empty-complete/medusa-product-attributes
81
-
82
- # Via GitHub Packages (requires authentication)
83
- npm install @empty-complete/medusa-product-attributes --registry=https://npm.pkg.github.com
60
+ pnpm add @empty-complete-org/medusa-product-attributes
61
+ # or
62
+ npm install @empty-complete-org/medusa-product-attributes
63
+ # or
64
+ yarn add @empty-complete-org/medusa-product-attributes
84
65
  ```
85
66
 
86
- ### 1. Add the module to configuration
87
-
88
- ```typescript
89
- // medusa-config.ts
90
- import { defineConfig } from '@medusajs/framework/utils'
67
+ Add to `medusa-config.ts`:
91
68
 
92
- export default defineConfig({
93
- modules: [
69
+ ```ts
70
+ module.exports = defineConfig({
71
+ plugins: [
94
72
  {
95
- resolve: '@empty-complete/medusa-product-attributes',
73
+ resolve: "@empty-complete-org/medusa-product-attributes",
74
+ options: {},
96
75
  },
97
76
  ],
98
77
  })
99
78
  ```
100
79
 
101
- ### 2. Run migrations
80
+ Run migrations:
102
81
 
103
82
  ```bash
104
- pnpm medusa db:migrate
105
- ```
106
-
107
- ### 3. Done!
108
-
109
- The module will automatically create the necessary tables and register services.
110
-
111
- ---
112
-
113
- ## 💡 Usage
114
-
115
- ### Creating a category attribute
116
-
117
- ```typescript
118
- import { CUSTOM_ATTRIBUTE_MODULE } from '@empty-complete/medusa-product-attributes'
119
-
120
- const module = container.resolve(CUSTOM_ATTRIBUTE_MODULE)
121
-
122
- // Create a "Weight" attribute with unit
123
- await module.createCategoryAttribute({
124
- label: 'Weight',
125
- type: 'number',
126
- unit: 'kg',
127
- category_id: 'pcat_123',
128
- sort_order: 0,
129
- })
130
-
131
- // Create a 3D model attribute
132
- await module.createCategoryAttribute({
133
- label: '3D Model',
134
- type: 'file',
135
- category_id: 'pcat_123',
136
- is_standard: true,
137
- })
138
- ```
139
-
140
- ### Adding attributes to a product
141
-
142
- ```typescript
143
- // Get the attribute template
144
- const attributes = await module.getCategoryAttributes('pcat_123')
145
-
146
- // Add values to the product
147
- await module.createProductAttribute({
148
- product_id: 'prod_456',
149
- category_custom_attribute_id: attributes[0].id,
150
- value_numeric: 5.5,
151
- })
152
- ```
153
-
154
- ### Getting product attributes
155
-
156
- ```typescript
157
- const productAttributes = await module.getProductAttributes('prod_456')
158
-
159
- console.log(productAttributes)
160
- // [
161
- // {
162
- // id: 'pat_...',
163
- // value: 'Premium quality',
164
- // value_numeric: 5.5,
165
- // value_file: null,
166
- // category_custom_attribute: {
167
- // label: 'Weight',
168
- // unit: 'kg',
169
- // type: 'number'
170
- // }
171
- // }
172
- // ]
173
- ```
174
-
175
- ---
176
-
177
- ## 📚 API Reference
178
-
179
- ### CategoryCustomAttribute
180
-
181
- | Method | Description | Parameters |
182
- |--------|-------------|------------|
183
- | `getCategoryAttributes(categoryId)` | Get all category attributes | `categoryId: string` |
184
- | `createCategoryAttribute(data)` | Create a new attribute | See below |
185
- | `updateCategoryAttribute(id, data)` | Update an attribute | `id: string`, `data: Partial<Attribute>` |
186
-
187
- #### createCategoryAttribute
188
-
189
- ```typescript
190
- {
191
- label: string // Display name
192
- type: 'text' | 'number' | 'file' | 'boolean'
193
- unit?: string | null // Unit of measurement (optional)
194
- category_id: string // Category ID
195
- is_standard?: boolean // Standard attribute flag
196
- sort_order?: number // Sort order
197
- }
198
- ```
199
-
200
- ### ProductCustomAttribute
201
-
202
- | Method | Description | Parameters |
203
- |--------|-------------|------------|
204
- | `getProductAttributes(productId)` | Get product attributes | `productId: string` |
205
- | `createProductAttribute(data)` | Create attribute value | See below |
206
- | `updateProductAttribute(id, data)` | Update value | `id: string`, `data: Partial<Value>` |
207
-
208
- #### createProductAttribute
209
-
210
- ```typescript
211
- {
212
- product_id: string
213
- category_custom_attribute_id: string
214
- value?: string
215
- value_numeric?: number | null
216
- value_file?: string | null
217
- }
83
+ npx medusa db:migrate
218
84
  ```
219
85
 
220
- ---
221
-
222
- ## 🗄️ Data Structure
223
-
224
- ### CategoryCustomAttribute
225
-
226
- ```
227
- ┌─────────────────────────────────────────┐
228
- │ CategoryCustomAttribute │
229
- ├─────────────────────────────────────────┤
230
- │ id: string (PK) │
231
- │ key: string │
232
- │ label: string │
233
- │ type: 'text' | 'number' | 'file' │
234
- │ unit: string | null │
235
- │ sort_order: number │
236
- │ is_standard: boolean │
237
- │ category_id: string │
238
- │ created_at: timestamp │
239
- │ updated_at: timestamp │
240
- │ deleted_at: timestamp | null │
241
- └─────────────────────────────────────────┘
242
- ```
86
+ ## Usage
243
87
 
244
- ### ProductCustomAttribute
88
+ ### Admin
245
89
 
246
- ```
247
- ┌─────────────────────────────────────────┐
248
- ProductCustomAttribute │
249
- ├─────────────────────────────────────────┤
250
- │ id: string (PK) │
251
- │ product_id: string │
252
- │ value: string | null │
253
- │ value_numeric: number | null │
254
- │ value_file: string | null │
255
- │ category_custom_attribute_id: FK │
256
- │ created_at: timestamp │
257
- │ updated_at: timestamp │
258
- │ deleted_at: timestamp | null │
259
- └─────────────────────────────────────────┘
260
- ```
90
+ 1. **Category attributes** — open any category in admin, the "Атрибуты" widget appears below the details. Add custom attributes (inherited by all subcategories), or pick one from a template.
91
+ 2. **Global attributes** — Settings → *Global Attributes*. Applied to every product automatically.
92
+ 3. **Templates** — Settings → *Attribute Templates*. Create reusable blueprints.
93
+ 4. **Product values** — open any product, the "Характеристики" widget shows all inherited + global attributes with inputs for their values.
261
94
 
262
- ---
95
+ ### API
263
96
 
264
- ## 📝 Examples
97
+ ```ts
98
+ // List all attributes for a product (globals + category chain)
99
+ GET /admin/product/:productId/attribute-schema
265
100
 
266
- ### Example 1: Creating attributes for electronics
101
+ // List/create/update attributes for a category
102
+ GET /admin/category/:categoryId/custom-attributes
103
+ POST /admin/category/:categoryId/custom-attributes
104
+ PATCH /admin/category/:categoryId/custom-attributes
267
105
 
268
- ```typescript
269
- // Attributes for "Smartphones" category
270
- await module.createCategoryAttribute({
271
- label: 'Screen Size',
272
- type: 'number',
273
- unit: 'inch',
274
- category_id: 'pcat_phones',
275
- sort_order: 1,
276
- })
106
+ // Global attributes
107
+ GET /admin/global-attributes
108
+ POST /admin/global-attributes
109
+ PATCH /admin/global-attributes
277
110
 
278
- await module.createCategoryAttribute({
279
- label: 'Storage Capacity',
280
- type: 'number',
281
- unit: 'GB',
282
- category_id: 'pcat_phones',
283
- sort_order: 2,
284
- })
111
+ // Templates
112
+ GET /admin/attribute-templates
113
+ POST /admin/attribute-templates
114
+ PATCH /admin/attribute-templates
115
+ POST /admin/attribute-templates/:id/apply { category_id }
285
116
 
286
- await module.createCategoryAttribute({
287
- label: 'Certificate',
288
- type: 'file',
289
- category_id: 'pcat_phones',
290
- is_standard: true,
291
- sort_order: 3,
292
- })
117
+ // Product values
118
+ GET /admin/product/:productId/custom-attributes
119
+ POST /admin/product/:productId/custom-attributes
293
120
  ```
294
121
 
295
- ### Example 2: Adding attribute values to a product
296
-
297
- ```typescript
298
- // iPhone 15 Pro
299
- await module.createProductAttribute({
300
- product_id: 'prod_iphone15pro',
301
- category_custom_attribute_id: 'attr_screen_size',
302
- value_numeric: 6.1,
303
- })
304
-
305
- await module.createProductAttribute({
306
- product_id: 'prod_iphone15pro',
307
- category_custom_attribute_id: 'attr_storage',
308
- value_numeric: 256,
309
- })
310
- ```
122
+ ## Attribute types
311
123
 
312
- ### Example 3: Store API getting attributes for storefront
124
+ | Type | Admin input | Stored in |
125
+ |------|-------------|-----------|
126
+ | `text` | text input | `value` |
127
+ | `number` | number input + unit badge | `value`, `value_numeric` |
128
+ | `file` | upload button | `value` (URL), `value_file` |
129
+ | `boolean` | select Yes/No | `value` |
313
130
 
314
- ```typescript
315
- // GET /store/attribute-values?product_id=prod_iphone15pro
316
- // Response:
317
- {
318
- "attribute_values": [
319
- {
320
- "id": "pat_...",
321
- "name": "Screen Size",
322
- "type": "number",
323
- "unit": "inch",
324
- "numeric_value": 6.1,
325
- "is_standard": false
326
- },
327
- {
328
- "id": "pat_...",
329
- "name": "Certificate",
330
- "type": "file",
331
- "file_value": "/files/cert_iphone15pro.pdf",
332
- "is_standard": true
333
- }
334
- ]
335
- }
336
- ```
131
+ ## Peer dependencies
337
132
 
338
- ---
133
+ - `@medusajs/framework` ^2.13
134
+ - `@medusajs/medusa` ^2.13
135
+ - `@medusajs/admin-sdk` ^2.13
136
+ - `@medusajs/ui` ^4
137
+ - `@tanstack/react-query` ^5
138
+ - `react` ^18 || ^19
339
139
 
340
- ## Project Structure
140
+ ## Development
341
141
 
342
- ```
343
- medusa-product-attributes/
344
- ├── src/
345
- │ ├── models/
346
- │ │ ├── category-custom-attribute.ts
347
- │ │ └── product-custom-attribute.ts
348
- │ ├── index.ts
349
- │ └── service.ts
350
- ├── assets/
351
- │ └── *.png
352
- ├── dist/
353
- ├── package.json
354
- ├── tsconfig.json
355
- ├── LICENSE
356
- └── README.md
142
+ ```bash
143
+ pnpm install
144
+ pnpm test
145
+ pnpm run build # builds to .medusa/server/
146
+ pnpm run dev # medusa plugin:develop
357
147
  ```
358
148
 
359
- ---
149
+ ## License
360
150
 
361
- <div align="center">
362
- <h2>🤝 Contributing</h2>
363
- <p>Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.</p>
364
- <p>
365
- <sub>Made with ❤️ for the Medusa community</sub>
366
- </p>
367
- </div>
151
+ MIT © empty-complete