medusa-dynamic-metadata 0.0.9 → 0.0.11
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
|
}
|
|
@@ -1770,25 +1760,25 @@ const MetadataWidgetLoader = ({ data, entityType, queryKey }) => {
|
|
|
1770
1760
|
}
|
|
1771
1761
|
);
|
|
1772
1762
|
};
|
|
1773
|
-
const Widget$
|
|
1763
|
+
const Widget$f = ({ data }) => {
|
|
1774
1764
|
return /* @__PURE__ */ jsxRuntime.jsx(UniversalMetadataWidget, { data, zone: "campaign.details.after" });
|
|
1775
1765
|
};
|
|
1776
1766
|
adminSdk.defineWidgetConfig({
|
|
1777
1767
|
zone: "campaign.details.after"
|
|
1778
1768
|
});
|
|
1779
|
-
const Widget$
|
|
1769
|
+
const Widget$e = ({ data }) => {
|
|
1780
1770
|
return /* @__PURE__ */ jsxRuntime.jsx(UniversalMetadataWidget, { data, zone: "product_category.details.after" });
|
|
1781
1771
|
};
|
|
1782
1772
|
adminSdk.defineWidgetConfig({
|
|
1783
1773
|
zone: "product_category.details.after"
|
|
1784
1774
|
});
|
|
1785
|
-
const Widget$
|
|
1775
|
+
const Widget$d = ({ data }) => {
|
|
1786
1776
|
return /* @__PURE__ */ jsxRuntime.jsx(UniversalMetadataWidget, { data, zone: "product_collection.details.after" });
|
|
1787
1777
|
};
|
|
1788
1778
|
adminSdk.defineWidgetConfig({
|
|
1789
1779
|
zone: "product_collection.details.after"
|
|
1790
1780
|
});
|
|
1791
|
-
const Widget$
|
|
1781
|
+
const Widget$c = ({ data }) => {
|
|
1792
1782
|
return /* @__PURE__ */ jsxRuntime.jsx(UniversalMetadataWidget, { data, zone: "customer.details.after" });
|
|
1793
1783
|
};
|
|
1794
1784
|
adminSdk.defineWidgetConfig({
|
|
@@ -1850,24 +1840,36 @@ const ProductHideDefaultMetadata = () => {
|
|
|
1850
1840
|
adminSdk.defineWidgetConfig({
|
|
1851
1841
|
zone: "product.details.side.before"
|
|
1852
1842
|
});
|
|
1853
|
-
const Widget$
|
|
1843
|
+
const Widget$b = ({ data }) => {
|
|
1854
1844
|
return /* @__PURE__ */ jsxRuntime.jsx(UniversalMetadataWidget, { data, zone: "inventory_item.details.after" });
|
|
1855
1845
|
};
|
|
1856
1846
|
adminSdk.defineWidgetConfig({
|
|
1857
1847
|
zone: "inventory_item.details.after"
|
|
1858
1848
|
});
|
|
1859
|
-
const Widget$
|
|
1849
|
+
const Widget$a = ({ data }) => {
|
|
1860
1850
|
return /* @__PURE__ */ jsxRuntime.jsx(UniversalMetadataWidget, { data, zone: "order.details.after" });
|
|
1861
1851
|
};
|
|
1862
1852
|
adminSdk.defineWidgetConfig({
|
|
1863
1853
|
zone: "order.details.after"
|
|
1864
1854
|
});
|
|
1865
|
-
const Widget$
|
|
1855
|
+
const Widget$9 = ({ data }) => {
|
|
1866
1856
|
return /* @__PURE__ */ jsxRuntime.jsx(UniversalMetadataWidget, { data, zone: "price_list.details.after" });
|
|
1867
1857
|
};
|
|
1868
1858
|
adminSdk.defineWidgetConfig({
|
|
1869
1859
|
zone: "price_list.details.after"
|
|
1870
1860
|
});
|
|
1861
|
+
const Widget$8 = ({ data }) => {
|
|
1862
|
+
return /* @__PURE__ */ jsxRuntime.jsx(UniversalMetadataWidget, { data, zone: "product_tag.details.after" });
|
|
1863
|
+
};
|
|
1864
|
+
adminSdk.defineWidgetConfig({
|
|
1865
|
+
zone: "product_tag.details.after"
|
|
1866
|
+
});
|
|
1867
|
+
const Widget$7 = ({ data }) => {
|
|
1868
|
+
return /* @__PURE__ */ jsxRuntime.jsx(UniversalMetadataWidget, { data, zone: "product_type.details.after" });
|
|
1869
|
+
};
|
|
1870
|
+
adminSdk.defineWidgetConfig({
|
|
1871
|
+
zone: "product_type.details.after"
|
|
1872
|
+
});
|
|
1871
1873
|
const Widget$6 = ({ data }) => {
|
|
1872
1874
|
return /* @__PURE__ */ jsxRuntime.jsx(UniversalMetadataWidget, { data, zone: "product_variant.details.after" });
|
|
1873
1875
|
};
|
|
@@ -1912,19 +1914,19 @@ adminSdk.defineWidgetConfig({
|
|
|
1912
1914
|
});
|
|
1913
1915
|
const widgetModule = { widgets: [
|
|
1914
1916
|
{
|
|
1915
|
-
Component: Widget$
|
|
1917
|
+
Component: Widget$f,
|
|
1916
1918
|
zone: ["campaign.details.after"]
|
|
1917
1919
|
},
|
|
1918
1920
|
{
|
|
1919
|
-
Component: Widget$
|
|
1921
|
+
Component: Widget$e,
|
|
1920
1922
|
zone: ["product_category.details.after"]
|
|
1921
1923
|
},
|
|
1922
1924
|
{
|
|
1923
|
-
Component: Widget$
|
|
1925
|
+
Component: Widget$d,
|
|
1924
1926
|
zone: ["product_collection.details.after"]
|
|
1925
1927
|
},
|
|
1926
1928
|
{
|
|
1927
|
-
Component: Widget$
|
|
1929
|
+
Component: Widget$c,
|
|
1928
1930
|
zone: ["customer.details.after"]
|
|
1929
1931
|
},
|
|
1930
1932
|
{
|
|
@@ -1932,17 +1934,25 @@ const widgetModule = { widgets: [
|
|
|
1932
1934
|
zone: ["product.details.side.before"]
|
|
1933
1935
|
},
|
|
1934
1936
|
{
|
|
1935
|
-
Component: Widget$
|
|
1937
|
+
Component: Widget$b,
|
|
1936
1938
|
zone: ["inventory_item.details.after"]
|
|
1937
1939
|
},
|
|
1938
1940
|
{
|
|
1939
|
-
Component: Widget$
|
|
1941
|
+
Component: Widget$a,
|
|
1940
1942
|
zone: ["order.details.after"]
|
|
1941
1943
|
},
|
|
1942
1944
|
{
|
|
1943
|
-
Component: Widget$
|
|
1945
|
+
Component: Widget$9,
|
|
1944
1946
|
zone: ["price_list.details.after"]
|
|
1945
1947
|
},
|
|
1948
|
+
{
|
|
1949
|
+
Component: Widget$8,
|
|
1950
|
+
zone: ["product_tag.details.after"]
|
|
1951
|
+
},
|
|
1952
|
+
{
|
|
1953
|
+
Component: Widget$7,
|
|
1954
|
+
zone: ["product_type.details.after"]
|
|
1955
|
+
},
|
|
1946
1956
|
{
|
|
1947
1957
|
Component: Widget$6,
|
|
1948
1958
|
zone: ["product_variant.details.after"]
|
|
@@ -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
|
}
|
|
@@ -1769,25 +1759,25 @@ const MetadataWidgetLoader = ({ data, entityType, queryKey }) => {
|
|
|
1769
1759
|
}
|
|
1770
1760
|
);
|
|
1771
1761
|
};
|
|
1772
|
-
const Widget$
|
|
1762
|
+
const Widget$f = ({ data }) => {
|
|
1773
1763
|
return /* @__PURE__ */ jsx(UniversalMetadataWidget, { data, zone: "campaign.details.after" });
|
|
1774
1764
|
};
|
|
1775
1765
|
defineWidgetConfig({
|
|
1776
1766
|
zone: "campaign.details.after"
|
|
1777
1767
|
});
|
|
1778
|
-
const Widget$
|
|
1768
|
+
const Widget$e = ({ data }) => {
|
|
1779
1769
|
return /* @__PURE__ */ jsx(UniversalMetadataWidget, { data, zone: "product_category.details.after" });
|
|
1780
1770
|
};
|
|
1781
1771
|
defineWidgetConfig({
|
|
1782
1772
|
zone: "product_category.details.after"
|
|
1783
1773
|
});
|
|
1784
|
-
const Widget$
|
|
1774
|
+
const Widget$d = ({ data }) => {
|
|
1785
1775
|
return /* @__PURE__ */ jsx(UniversalMetadataWidget, { data, zone: "product_collection.details.after" });
|
|
1786
1776
|
};
|
|
1787
1777
|
defineWidgetConfig({
|
|
1788
1778
|
zone: "product_collection.details.after"
|
|
1789
1779
|
});
|
|
1790
|
-
const Widget$
|
|
1780
|
+
const Widget$c = ({ data }) => {
|
|
1791
1781
|
return /* @__PURE__ */ jsx(UniversalMetadataWidget, { data, zone: "customer.details.after" });
|
|
1792
1782
|
};
|
|
1793
1783
|
defineWidgetConfig({
|
|
@@ -1849,24 +1839,36 @@ const ProductHideDefaultMetadata = () => {
|
|
|
1849
1839
|
defineWidgetConfig({
|
|
1850
1840
|
zone: "product.details.side.before"
|
|
1851
1841
|
});
|
|
1852
|
-
const Widget$
|
|
1842
|
+
const Widget$b = ({ data }) => {
|
|
1853
1843
|
return /* @__PURE__ */ jsx(UniversalMetadataWidget, { data, zone: "inventory_item.details.after" });
|
|
1854
1844
|
};
|
|
1855
1845
|
defineWidgetConfig({
|
|
1856
1846
|
zone: "inventory_item.details.after"
|
|
1857
1847
|
});
|
|
1858
|
-
const Widget$
|
|
1848
|
+
const Widget$a = ({ data }) => {
|
|
1859
1849
|
return /* @__PURE__ */ jsx(UniversalMetadataWidget, { data, zone: "order.details.after" });
|
|
1860
1850
|
};
|
|
1861
1851
|
defineWidgetConfig({
|
|
1862
1852
|
zone: "order.details.after"
|
|
1863
1853
|
});
|
|
1864
|
-
const Widget$
|
|
1854
|
+
const Widget$9 = ({ data }) => {
|
|
1865
1855
|
return /* @__PURE__ */ jsx(UniversalMetadataWidget, { data, zone: "price_list.details.after" });
|
|
1866
1856
|
};
|
|
1867
1857
|
defineWidgetConfig({
|
|
1868
1858
|
zone: "price_list.details.after"
|
|
1869
1859
|
});
|
|
1860
|
+
const Widget$8 = ({ data }) => {
|
|
1861
|
+
return /* @__PURE__ */ jsx(UniversalMetadataWidget, { data, zone: "product_tag.details.after" });
|
|
1862
|
+
};
|
|
1863
|
+
defineWidgetConfig({
|
|
1864
|
+
zone: "product_tag.details.after"
|
|
1865
|
+
});
|
|
1866
|
+
const Widget$7 = ({ data }) => {
|
|
1867
|
+
return /* @__PURE__ */ jsx(UniversalMetadataWidget, { data, zone: "product_type.details.after" });
|
|
1868
|
+
};
|
|
1869
|
+
defineWidgetConfig({
|
|
1870
|
+
zone: "product_type.details.after"
|
|
1871
|
+
});
|
|
1870
1872
|
const Widget$6 = ({ data }) => {
|
|
1871
1873
|
return /* @__PURE__ */ jsx(UniversalMetadataWidget, { data, zone: "product_variant.details.after" });
|
|
1872
1874
|
};
|
|
@@ -1911,19 +1913,19 @@ defineWidgetConfig({
|
|
|
1911
1913
|
});
|
|
1912
1914
|
const widgetModule = { widgets: [
|
|
1913
1915
|
{
|
|
1914
|
-
Component: Widget$
|
|
1916
|
+
Component: Widget$f,
|
|
1915
1917
|
zone: ["campaign.details.after"]
|
|
1916
1918
|
},
|
|
1917
1919
|
{
|
|
1918
|
-
Component: Widget$
|
|
1920
|
+
Component: Widget$e,
|
|
1919
1921
|
zone: ["product_category.details.after"]
|
|
1920
1922
|
},
|
|
1921
1923
|
{
|
|
1922
|
-
Component: Widget$
|
|
1924
|
+
Component: Widget$d,
|
|
1923
1925
|
zone: ["product_collection.details.after"]
|
|
1924
1926
|
},
|
|
1925
1927
|
{
|
|
1926
|
-
Component: Widget$
|
|
1928
|
+
Component: Widget$c,
|
|
1927
1929
|
zone: ["customer.details.after"]
|
|
1928
1930
|
},
|
|
1929
1931
|
{
|
|
@@ -1931,17 +1933,25 @@ const widgetModule = { widgets: [
|
|
|
1931
1933
|
zone: ["product.details.side.before"]
|
|
1932
1934
|
},
|
|
1933
1935
|
{
|
|
1934
|
-
Component: Widget$
|
|
1936
|
+
Component: Widget$b,
|
|
1935
1937
|
zone: ["inventory_item.details.after"]
|
|
1936
1938
|
},
|
|
1937
1939
|
{
|
|
1938
|
-
Component: Widget$
|
|
1940
|
+
Component: Widget$a,
|
|
1939
1941
|
zone: ["order.details.after"]
|
|
1940
1942
|
},
|
|
1941
1943
|
{
|
|
1942
|
-
Component: Widget$
|
|
1944
|
+
Component: Widget$9,
|
|
1943
1945
|
zone: ["price_list.details.after"]
|
|
1944
1946
|
},
|
|
1947
|
+
{
|
|
1948
|
+
Component: Widget$8,
|
|
1949
|
+
zone: ["product_tag.details.after"]
|
|
1950
|
+
},
|
|
1951
|
+
{
|
|
1952
|
+
Component: Widget$7,
|
|
1953
|
+
zone: ["product_type.details.after"]
|
|
1954
|
+
},
|
|
1945
1955
|
{
|
|
1946
1956
|
Component: Widget$6,
|
|
1947
1957
|
zone: ["product_variant.details.after"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GET = GET;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
const metadata_options_1 = require("../../../config/metadata-options");
|
|
6
|
+
async function GET(req, res) {
|
|
7
|
+
const configModule = req.scope.resolve(utils_1.ContainerRegistrationKeys.CONFIG_MODULE);
|
|
8
|
+
const options = (0, metadata_options_1.resolveDynamicMetadataOptions)(configModule);
|
|
9
|
+
const entity = req.query?.entity;
|
|
10
|
+
if (typeof entity === "string" && entity.length > 0) {
|
|
11
|
+
const entityConfig = options.entities[entity];
|
|
12
|
+
if (!entityConfig || !entityConfig.expose_client_helpers) {
|
|
13
|
+
res.json({
|
|
14
|
+
metadataDescriptors: [],
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
res.json({
|
|
19
|
+
metadataDescriptors: entityConfig.descriptors,
|
|
20
|
+
});
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const metadataDescriptorsByEntity = Object.fromEntries(Object.entries(options.entities)
|
|
24
|
+
.filter(([, config]) => config.expose_client_helpers)
|
|
25
|
+
.map(([entityType, config]) => [entityType, config.descriptors]));
|
|
26
|
+
res.json({
|
|
27
|
+
metadataDescriptorsByEntity,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL3N0b3JlL21ldGFkYXRhLWNvbmZpZy9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUtBLGtCQWlDQztBQXJDRCxxREFBcUU7QUFFckUsdUVBQWdGO0FBRXpFLEtBQUssVUFBVSxHQUFHLENBQUMsR0FBa0IsRUFBRSxHQUFtQjtJQUMvRCxNQUFNLFlBQVksR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FDcEMsaUNBQXlCLENBQUMsYUFBYSxDQUN4QyxDQUFBO0lBRUQsTUFBTSxPQUFPLEdBQUcsSUFBQSxnREFBNkIsRUFBQyxZQUFZLENBQUMsQ0FBQTtJQUMzRCxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQTtJQUVoQyxJQUFJLE9BQU8sTUFBTSxLQUFLLFFBQVEsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ3BELE1BQU0sWUFBWSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUE7UUFFN0MsSUFBSSxDQUFDLFlBQVksSUFBSSxDQUFDLFlBQVksQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1lBQ3pELEdBQUcsQ0FBQyxJQUFJLENBQUM7Z0JBQ1AsbUJBQW1CLEVBQUUsRUFBRTthQUN4QixDQUFDLENBQUE7WUFDRixPQUFNO1FBQ1IsQ0FBQztRQUVELEdBQUcsQ0FBQyxJQUFJLENBQUM7WUFDUCxtQkFBbUIsRUFBRSxZQUFZLENBQUMsV0FBVztTQUM5QyxDQUFDLENBQUE7UUFDRixPQUFNO0lBQ1IsQ0FBQztJQUVELE1BQU0sMkJBQTJCLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FDcEQsTUFBTSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO1NBQzdCLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLHFCQUFxQixDQUFDO1NBQ3BELEdBQUcsQ0FBQyxDQUFDLENBQUMsVUFBVSxFQUFFLE1BQU0sQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLFVBQVUsRUFBRSxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FDbkUsQ0FBQTtJQUVELEdBQUcsQ0FBQyxJQUFJLENBQUM7UUFDUCwyQkFBMkI7S0FDNUIsQ0FBQyxDQUFBO0FBQ0osQ0FBQyJ9
|
|
@@ -46,6 +46,26 @@ exports.DEFAULT_DYNAMIC_METADATA_OPTIONS = {
|
|
|
46
46
|
entities: {},
|
|
47
47
|
};
|
|
48
48
|
const PLUGIN_NAME = "medusa-dynamic-metadata";
|
|
49
|
+
/**
|
|
50
|
+
* True when this plugin entry refers to medusa-dynamic-metadata, including
|
|
51
|
+
* absolute/relative paths and monorepo layouts (`.../medusa-dynamic-metadata/...`).
|
|
52
|
+
* Strict `resolve === "medusa-dynamic-metadata"` misses `file:` and `require.resolve()` paths.
|
|
53
|
+
*/
|
|
54
|
+
function isDynamicMetadataPluginResolve(resolve) {
|
|
55
|
+
if (resolve === PLUGIN_NAME) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const norm = resolve
|
|
59
|
+
.replace(/\\/g, "/")
|
|
60
|
+
.split("?")[0]
|
|
61
|
+
?.split("#")[0] ?? "";
|
|
62
|
+
if (!norm.length) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const segments = norm.split("/").filter(Boolean);
|
|
66
|
+
return (segments.includes(PLUGIN_NAME) ||
|
|
67
|
+
segments[segments.length - 1] === PLUGIN_NAME);
|
|
68
|
+
}
|
|
49
69
|
function normalizeDynamicMetadataOptions(input) {
|
|
50
70
|
const parsed = DynamicMetadataOptionsSchema.parse(input ?? {});
|
|
51
71
|
const normalizedEntities = {};
|
|
@@ -83,14 +103,14 @@ function resolveDynamicMetadataOptions(configModule) {
|
|
|
83
103
|
for (const plugin of plugins) {
|
|
84
104
|
if ((0, utils_1.isString)(plugin)) {
|
|
85
105
|
if (plugin === PLUGIN_NAME) {
|
|
86
|
-
|
|
106
|
+
return normalizeDynamicMetadataOptions({});
|
|
87
107
|
}
|
|
88
108
|
continue;
|
|
89
109
|
}
|
|
90
|
-
if (plugin?.resolve
|
|
110
|
+
if (plugin?.resolve && isDynamicMetadataPluginResolve(plugin.resolve)) {
|
|
91
111
|
return normalizeDynamicMetadataOptions(plugin.options);
|
|
92
112
|
}
|
|
93
113
|
}
|
|
94
114
|
return exports.DEFAULT_DYNAMIC_METADATA_OPTIONS;
|
|
95
115
|
}
|
|
96
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
116
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0YWRhdGEtb3B0aW9ucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9jb25maWcvbWV0YWRhdGEtb3B0aW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUE2R0EsMEVBMkNDO0FBRUQsc0VBbUJDO0FBN0tELHFEQUFvRDtBQUNwRCw2QkFBdUI7QUFFdkIsb0RBSWlDO0FBQ2pDLG9EQUF1RTtBQUN2RSxtREFBZ0Y7QUFDaEYsdURBQXlEO0FBRXpELE1BQU0sa0JBQWtCLEdBQUcsT0FBQyxDQUFDLE1BQU0sQ0FBQztJQUNsQyxLQUFLLEVBQUUsT0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDeEIsS0FBSyxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxRQUFRLEVBQUU7Q0FDcEMsQ0FBQyxDQUFBO0FBRUYsTUFBTSx3QkFBd0IsR0FBRyxPQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3hDLEdBQUcsRUFBRSxPQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0lBQzFCLEdBQUcsRUFBRSxPQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0lBQzFCLEtBQUssRUFBRSxPQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQzdCLENBQUMsQ0FBQTtBQUVGLE1BQU0sd0JBQXdCLEdBQUcsT0FBQyxDQUFDLE1BQU0sQ0FBQztJQUN4QyxHQUFHLEVBQUUsT0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDdEIsS0FBSyxFQUFFLE9BQUM7U0FDTCxNQUFNLEVBQUU7U0FDUixJQUFJLEVBQUU7U0FDTixTQUFTLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMs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
|
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,353 @@ 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) `GET /store/metadata-config`
|
|
181
|
+
|
|
182
|
+
- **Auth:** Storefront (store route namespace)
|
|
183
|
+
- **Query params:**
|
|
184
|
+
- `entity` (`string`, optional)
|
|
185
|
+
- **Response (when `entity` is provided and exposed):**
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"metadataDescriptors": [
|
|
189
|
+
{
|
|
190
|
+
"key": "brand",
|
|
191
|
+
"type": "text",
|
|
192
|
+
"label": "Brand"
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
- **Response (when `entity` is omitted):**
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"metadataDescriptorsByEntity": {
|
|
201
|
+
"products": [
|
|
202
|
+
{
|
|
203
|
+
"key": "brand",
|
|
204
|
+
"type": "text",
|
|
205
|
+
"label": "Brand"
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
- **Behavior:**
|
|
212
|
+
- Returns only entities with `expose_client_helpers: true`.
|
|
213
|
+
- Returns empty descriptor array when the requested entity is hidden/not configured.
|
|
214
|
+
|
|
215
|
+
### 3) `POST /admin/product-variants/:id`
|
|
216
|
+
|
|
217
|
+
- **Auth:** Admin (admin route namespace)
|
|
218
|
+
- **Path params:**
|
|
219
|
+
- `id` (`string`, required): product variant ID.
|
|
220
|
+
- **Body:** passthrough payload with optional `metadata` object plus other variant update fields.
|
|
221
|
+
- **Response:**
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"product_variant": {
|
|
225
|
+
"id": "variant_...",
|
|
226
|
+
"metadata": {}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
- **Behavior:**
|
|
231
|
+
- Executes `updateProductVariantsWorkflow` with selector by ID and update payload.
|
|
232
|
+
- Returns first updated variant as `product_variant`.
|
|
233
|
+
|
|
234
|
+
### Important Endpoint Examples
|
|
197
235
|
|
|
198
|
-
|
|
199
|
-
|
|
236
|
+
```bash
|
|
237
|
+
# Get metadata descriptors for products
|
|
238
|
+
curl -X GET "http://localhost:9000/admin/metadata-config?entity=products" \
|
|
239
|
+
-H "Authorization: Bearer <admin_token>"
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Update metadata on a product variant
|
|
244
|
+
curl -X POST "http://localhost:9000/admin/product-variants/variant_123" \
|
|
245
|
+
-H "Authorization: Bearer <admin_token>" \
|
|
246
|
+
-H "Content-Type: application/json" \
|
|
247
|
+
-d '{
|
|
248
|
+
"metadata": {
|
|
249
|
+
"fabric": "cotton",
|
|
250
|
+
"is_limited": true
|
|
251
|
+
}
|
|
252
|
+
}'
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
// fetch example for metadata config
|
|
257
|
+
const res = await fetch("/admin/metadata-config?entity=orders", {
|
|
258
|
+
credentials: "include",
|
|
200
259
|
})
|
|
260
|
+
const data = await res.json()
|
|
201
261
|
```
|
|
202
262
|
|
|
203
|
-
|
|
263
|
+
---
|
|
204
264
|
|
|
205
|
-
|
|
265
|
+
## Services
|
|
206
266
|
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
entities: {
|
|
210
|
-
customers: {
|
|
211
|
-
descriptors: [
|
|
212
|
-
{ key: "preferred_language", type: "text" }
|
|
213
|
-
]
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
```
|
|
267
|
+
No custom backend service classes are defined in this plugin source.
|
|
217
268
|
|
|
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
|
-
```
|
|
269
|
+
> ⚠️ Note: Most logic is implemented as configuration utilities (`src/config/*`) and shared metadata helpers (`src/shared/metadata/utils.ts`) consumed by admin UI.
|
|
298
270
|
|
|
299
|
-
|
|
271
|
+
---
|
|
300
272
|
|
|
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
|
-
```
|
|
273
|
+
## Workflows & Steps (Medusa v2)
|
|
318
274
|
|
|
319
|
-
|
|
275
|
+
No custom workflows or steps are defined in this plugin source.
|
|
320
276
|
|
|
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
|
-
|
|
277
|
+
The plugin calls Medusa core workflow `updateProductVariantsWorkflow` in `POST /admin/product-variants/:id`.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Subscribers / Event Hooks
|
|
282
|
+
|
|
283
|
+
No runtime subscribers/event handlers are defined in this plugin source (only template/readme scaffolding under `src/subscribers`).
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Admin UI / Widgets
|
|
288
|
+
|
|
289
|
+
### Universal Components
|
|
290
|
+
|
|
291
|
+
- **`UniversalMetadataWidget` (`src/admin/components/universal-metadata-widget.tsx`)**
|
|
292
|
+
- Detects entity type from widget zone; falls back to data-shape signals.
|
|
293
|
+
- Loads descriptors via `useMetadataConfig`.
|
|
294
|
+
- Renders `MetadataTableWidget` only when descriptors exist.
|
|
295
|
+
|
|
296
|
+
- **`MetadataTableWidget` (`src/admin/components/metadata-table.tsx`)**
|
|
297
|
+
- Renders typed controls per descriptor.
|
|
298
|
+
- Performs descriptor-based validation.
|
|
299
|
+
- Uploads media/file fields through `POST /admin/uploads` and stores URL in metadata value.
|
|
300
|
+
- Saves metadata by resolving entity endpoint (`resolveApiEndpoint`) and calling `POST`.
|
|
301
|
+
- Invalidates/refetches React Query cache using resolved query keys.
|
|
302
|
+
|
|
303
|
+
### Registered Metadata Widgets (wrappers)
|
|
304
|
+
|
|
305
|
+
Each file is a minimal wrapper around `UniversalMetadataWidget` with a fixed zone:
|
|
306
|
+
|
|
307
|
+
| Entity | Zone |
|
|
308
|
+
|---|---|
|
|
309
|
+
| `products` | `product.details.after` |
|
|
310
|
+
| `orders` | `order.details.after` |
|
|
311
|
+
| `categories` | `product_category.details.after` |
|
|
312
|
+
| `collections` | `product_collection.details.after` |
|
|
313
|
+
| `customers` | `customer.details.after` |
|
|
314
|
+
| `regions` | `region.details.after` |
|
|
315
|
+
| `sales_channels` | `sales_channel.details.after` |
|
|
316
|
+
| `stores` | `store.details.after` |
|
|
317
|
+
| `promotions` | `promotion.details.after` |
|
|
318
|
+
| `campaigns` | `campaign.details.after` |
|
|
319
|
+
| `price_lists` | `price_list.details.after` |
|
|
320
|
+
| `shipping_profiles` | `shipping_profile.details.after` |
|
|
321
|
+
| `inventory_items` | `inventory_item.details.after` |
|
|
322
|
+
| `product_variants` | `product_variant.details.after` |
|
|
323
|
+
| `product_tags` | `product_tag.details.after` |
|
|
324
|
+
| `product_types` | `product_type.details.after` |
|
|
325
|
+
|
|
326
|
+
### Additional Widget
|
|
327
|
+
|
|
328
|
+
- **`hide-default-metadata` (`src/admin/widgets/hide-default-metadata.tsx`)**
|
|
329
|
+
- **Zone:** `product.details.side.before`
|
|
330
|
+
- **Purpose:** DOM-level hiding of default Medusa metadata panel to avoid UI duplication.
|
|
331
|
+
- **Interaction:** none (automatic MutationObserver behavior).
|
|
332
|
+
|
|
333
|
+
> ⚠️ Note: This widget hides DOM elements by structure/class heuristics and may need adjustment if Admin DOM structure changes in future Medusa releases.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Models & Entities
|
|
338
|
+
|
|
339
|
+
No custom database models/entities are defined in this plugin source.
|
|
340
|
+
|
|
341
|
+
The plugin operates on existing Medusa entities by reading/writing their `metadata` field via admin APIs.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Use Cases & Examples
|
|
346
|
+
|
|
347
|
+
1. **Product enrichment without code changes**
|
|
348
|
+
- Add new product metadata fields (brand, care instructions, support URLs) via plugin options.
|
|
349
|
+
- Use: `entities.products.descriptors` + `product.details.after` widget.
|
|
350
|
+
|
|
351
|
+
2. **Operational order annotations**
|
|
352
|
+
- Add structured metadata to orders (source channel, routing note, internal tags).
|
|
353
|
+
- Use: `entities.orders.descriptors` + metadata table UI.
|
|
354
|
+
|
|
355
|
+
3. **Per-entity metadata governance**
|
|
356
|
+
- Make fields required and validated (`min/max/regex`) to standardize admin data entry.
|
|
357
|
+
- Use: descriptor `required` and `validation`.
|
|
358
|
+
|
|
359
|
+
4. **Variant-level custom attributes**
|
|
360
|
+
- Persist metadata on product variants and update through variant route workflow.
|
|
361
|
+
- Use: `POST /admin/product-variants/:id`.
|
|
362
|
+
|
|
363
|
+
5. **File/media metadata links**
|
|
364
|
+
- Upload file/image/video/audio in metadata form and store uploaded URL in metadata.
|
|
365
|
+
- Use: file/media descriptor types in metadata table.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Troubleshooting
|
|
370
|
+
|
|
371
|
+
### Metadata widget doesn’t appear
|
|
372
|
+
|
|
373
|
+
- **Cause:** no descriptors configured for that entity.
|
|
374
|
+
- **Fix:** add `entities.<entityType>.descriptors` in plugin options; rebuild/restart app.
|
|
375
|
+
|
|
376
|
+
### Product type / tag / settings pages: config exists but widget never shows
|
|
377
|
+
|
|
378
|
+
- **Cause:** the admin UI only renders the dynamic table after `/admin/metadata-config?entity=…` returns descriptors. If the server cannot associate your `plugins[]` entry with this package (for example `resolve` is a filesystem path, `file:…`, or `require.resolve("medusa-dynamic-metadata")`), options were previously ignored and the widget hid itself (built-in **Metadata** can still show **0 keys**).
|
|
379
|
+
- **Fix:** use `resolve: "medusa-dynamic-metadata"` when possible, or any path whose segments include the folder `medusa-dynamic-metadata` (path-based `resolve` values are supported). Restart the server after changing config.
|
|
380
|
+
- **Verify:** `curl -sS "http://localhost:9000/admin/metadata-config?entity=product_types" -H "Authorization: Bearer <token>"` should return a non-empty `metadataDescriptors` array when `product_types` is configured.
|
|
381
|
+
- **Admin bundle:** after upgrading or developing the plugin locally, run `medusa plugin:build` (or `npm run build` in the package) and restart so Admin loads the latest widgets (for example `product-types-metadata-table`).
|
|
382
|
+
|
|
383
|
+
### “Unable to load metadata configuration”
|
|
384
|
+
|
|
385
|
+
- **Cause:** admin can’t reach `/admin/metadata-config` or plugin not loaded.
|
|
386
|
+
- **Fix:** verify plugin registration in `medusa-config.ts`, rebuild plugin, restart Medusa server.
|
|
387
|
+
|
|
388
|
+
### “No API endpoint configured for <entityType>”
|
|
360
389
|
|
|
361
|
-
|
|
390
|
+
- **Cause:** entity type lacks mapping in registry and no `api_endpoint` override configured.
|
|
391
|
+
- **Fix:** set `api_endpoint` (and optionally `widget_zone`) for that entity in plugin options.
|
|
362
392
|
|
|
363
|
-
|
|
364
|
-
- `npm run dev` - Run in development/watch mode
|
|
393
|
+
### Save fails with backend error
|
|
365
394
|
|
|
366
|
-
|
|
395
|
+
- **Cause:** invalid payload, endpoint mismatch, or authorization issue.
|
|
396
|
+
- **Fix:** verify resolved endpoint, admin auth/session, and descriptor/value shape.
|
|
367
397
|
|
|
368
|
-
|
|
398
|
+
### File upload fails in metadata field
|
|
369
399
|
|
|
370
|
-
|
|
400
|
+
- **Cause:** `/admin/uploads` request fails or no URL returned.
|
|
401
|
+
- **Fix:** confirm uploads endpoint availability and permissions; inspect server response message shown in toast.
|
|
371
402
|
|
|
372
|
-
|
|
403
|
+
### Default and custom metadata sections both visible
|
|
373
404
|
|
|
374
|
-
|
|
405
|
+
- **Cause:** hide widget not active for current zone/page or DOM structure mismatch.
|
|
406
|
+
- **Fix:** verify `hide-default-metadata` widget registration and adjust selector logic if Admin markup changed.
|
|
375
407
|
|
|
376
|
-
|
|
408
|
+
---
|
|
377
409
|
|
|
378
|
-
## Other channels
|
|
379
410
|
|
|
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/)
|