{
  "$schema": "https://registry.mercurjs.com/registry-item.json",
  "name": "product-import-export",
  "description": "CSV product import & export for vendors. Includes API routes, workflows, and vendor portal drawers.",
  "dependencies": [
    "multer",
    "@types/multer",
    "@medusajs/core-flows"
  ],
  "registryDependencies": [],
  "docs": "## Product Import/Export Block\n\n### Dependencies\n\nInstall dependencies in the **API workspace** (not the project root):\n\n```bash\ncd packages/api\nbun add @medusajs/core-flows multer @types/multer\n```\n\n### Configuration\n\nNo additional modules needed — uses built-in Medusa product CSV steps.\n\n### Middleware Setup\n\nAdd the import middleware to your `api/middlewares.ts`:\n\n```ts\nimport { defineMiddlewares } from '@medusajs/medusa'\nimport { productImportExportMiddlewares } from './vendor/products/middlewares'\n\nexport default defineMiddlewares({\n  routes: [...productImportExportMiddlewares],\n})\n```\n\n### Run codegen\n\nAfter installing the block, regenerate SDK types:\n\n```bash\nnpx @mercurjs/cli@latest codegen\n```\n\n### Vendor Navigation\n\nThe block includes a `products/page.tsx` that overrides the default product list page, adding Import and Export buttons in the header. The buttons open drawer routes at `/products/import` and `/products/export`.",
  "categories": [
    "api",
    "workflow",
    "vendor"
  ],
  "files": [
    {
      "path": "product-import-export/workflows/steps/get-seller-products.ts",
      "content": "import { createStep, StepResponse } from \"@medusajs/framework/workflows-sdk\"\nimport { ContainerRegistrationKeys } from \"@medusajs/framework/utils\"\nimport { Query } from \"@medusajs/framework/modules-sdk\"\n\ntype GetSellerProductsStepInput = {\n  seller_id: string\n}\n\nexport const getSellerProductsStep = createStep(\n  \"get-seller-products\",\n  async (input: GetSellerProductsStepInput, { container }) => {\n    const query = container.resolve<Query>(ContainerRegistrationKeys.QUERY)\n\n    const { data: relations } = await query.graph({\n      entity: \"product_seller\",\n      fields: [\n        \"product.*\",\n        \"product.variants.*\",\n        \"product.variants.prices.*\",\n        \"product.variants.options.*\",\n        \"product.options.*\",\n        \"product.collection.*\",\n        \"product.categories.*\",\n        \"product.sales_channels.*\",\n        \"product.images.*\",\n        \"product.tags.*\",\n        \"product.type.*\",\n      ],\n      filters: {\n        seller_id: input.seller_id,\n      },\n    })\n\n    const products = relations.map((r: any) => r.product)\n\n    return new StepResponse(products)\n  }\n)\n",
      "type": "registry:api"
    },
    {
      "path": "product-import-export/workflows/steps/validate-products-to-import.ts",
      "content": "import { createStep, StepResponse } from \"@medusajs/framework/workflows-sdk\"\nimport { MedusaError } from \"@medusajs/framework/utils\"\nimport { z } from \"zod\"\n\nconst CreateVariantPrice = z.object({\n  currency_code: z.string(),\n  amount: z.number(),\n})\n\nconst CreateProductOption = z.object({\n  title: z.string(),\n  values: z.array(z.string()).optional(),\n})\n\nconst CreateProductVariant = z.object({\n  title: z.string(),\n  sku: z.string().optional().nullable(),\n  barcode: z.string().optional().nullable(),\n  ean: z.string().optional().nullable(),\n  upc: z.string().optional().nullable(),\n  hs_code: z.string().optional().nullable(),\n  mid_code: z.string().optional().nullable(),\n  manage_inventory: z.boolean().optional(),\n  allow_backorder: z.boolean().optional(),\n  weight: z.number().optional().nullable(),\n  length: z.number().optional().nullable(),\n  height: z.number().optional().nullable(),\n  width: z.number().optional().nullable(),\n  material: z.string().optional().nullable(),\n  origin_country: z.string().optional().nullable(),\n  options: z.record(z.string()).optional(),\n  prices: z.array(CreateVariantPrice).optional(),\n  inventory_quantity: z.number().optional(),\n})\n\nconst CreateProduct = z.object({\n  title: z.string(),\n  subtitle: z.string().optional().nullable(),\n  description: z.string().optional().nullable(),\n  handle: z.string().optional(),\n  status: z.enum([\"draft\", \"proposed\", \"published\", \"rejected\"]).optional(),\n  thumbnail: z.string().optional().nullable(),\n  weight: z.number().optional().nullable(),\n  length: z.number().optional().nullable(),\n  height: z.number().optional().nullable(),\n  width: z.number().optional().nullable(),\n  hs_code: z.string().optional().nullable(),\n  mid_code: z.string().optional().nullable(),\n  material: z.string().optional().nullable(),\n  origin_country: z.string().optional().nullable(),\n  collection_id: z.string().optional().nullable(),\n  type_id: z.string().optional().nullable(),\n  tags: z.array(z.object({ id: z.string() })).optional(),\n  categories: z.array(z.object({ id: z.string() })).optional(),\n  sales_channels: z.array(z.object({ id: z.string() })).optional(),\n  images: z.array(z.object({ url: z.string() })).optional(),\n  options: z.array(CreateProductOption).optional(),\n  variants: z.array(CreateProductVariant).optional(),\n})\n\ntype ValidateProductsToImportStepInput = {\n  products: Record<string, any>[]\n}\n\nexport const validateProductsToImportStep = createStep(\n  \"validate-products-to-import\",\n  async (input: ValidateProductsToImportStepInput) => {\n    const errors: string[] = []\n\n    for (let i = 0; i < input.products.length; i++) {\n      const result = CreateProduct.safeParse(input.products[i])\n      if (!result.success) {\n        const issues = result.error.issues\n          .map((issue) => `${issue.path.join(\".\")}: ${issue.message}`)\n          .join(\"; \")\n        errors.push(`Product ${i + 1}: ${issues}`)\n      }\n    }\n\n    if (errors.length > 0) {\n      throw new MedusaError(\n        MedusaError.Types.INVALID_DATA,\n        `Validation failed for ${errors.length} product(s):\\n${errors.join(\"\\n\")}`\n      )\n    }\n\n    return new StepResponse(input.products)\n  }\n)\n",
      "type": "registry:api"
    },
    {
      "path": "product-import-export/workflows/import-seller-products.ts",
      "content": "import {\n  createWorkflow,\n  transform,\n  WorkflowResponse,\n} from \"@medusajs/framework/workflows-sdk\"\nimport { parseProductCsvStep } from \"@medusajs/core-flows\"\nimport { createProductsWorkflow } from \"@medusajs/core-flows\"\nimport { validateProductsToImportStep } from \"./steps/validate-products-to-import\"\n\ntype ImportSellerProductsWorkflowInput = {\n  file_content: string\n  seller_id: string\n}\n\nexport const importSellerProductsWorkflow = createWorkflow(\n  \"import-seller-products\",\n  function (input: ImportSellerProductsWorkflowInput) {\n    const parsedProducts = parseProductCsvStep(input.file_content)\n\n    validateProductsToImportStep({ products: parsedProducts })\n\n    const productsInput = transform(\n      { parsedProducts },\n      ({ parsedProducts }) => ({\n        products: parsedProducts.map((p: any) => ({\n          ...p,\n          status: p.status || \"draft\",\n        })),\n      })\n    )\n\n    const createdProducts = createProductsWorkflow.runAsStep({\n      input: productsInput,\n    })\n\n    return new WorkflowResponse(createdProducts)\n  }\n)\n",
      "type": "registry:api"
    },
    {
      "path": "product-import-export/workflows/export-seller-products.ts",
      "content": "import {\n  createWorkflow,\n  transform,\n  WorkflowResponse,\n} from \"@medusajs/framework/workflows-sdk\"\nimport {\n  generateProductCsvStep,\n  useQueryGraphStep,\n} from \"@medusajs/core-flows\"\nimport { getSellerProductsStep } from \"./steps/get-seller-products\"\n\ntype ExportSellerProductsWorkflowInput = {\n  seller_id: string\n}\n\nexport const exportSellerProductsWorkflow = createWorkflow(\n  \"export-seller-products\",\n  function (input: ExportSellerProductsWorkflowInput) {\n    const products = getSellerProductsStep({\n      seller_id: input.seller_id,\n    })\n\n    const csvFile = generateProductCsvStep(products)\n\n    const fileData = useQueryGraphStep({\n      entity: \"file\",\n      fields: [\"id\", \"url\"],\n      filters: {\n        id: csvFile.id,\n      },\n      options: {\n        throwIfKeyNotFound: true,\n      },\n    })\n\n    const result = transform({ fileData }, ({ fileData }) => {\n      const file = fileData.data[0]\n      return { url: file.url }\n    })\n\n    return new WorkflowResponse(result)\n  }\n)\n",
      "type": "registry:api"
    },
    {
      "path": "product-import-export/api/vendor/products/import/route.ts",
      "content": "import \"multer\"\nimport {\n  AuthenticatedMedusaRequest,\n  MedusaResponse,\n} from \"@medusajs/framework/http\"\nimport { MedusaError } from \"@medusajs/framework/utils\"\nimport { importSellerProductsWorkflow } from \"../../../../workflows/import-seller-products\"\nimport { fetchSellerByAuthActorId } from \"../helpers/helpers\"\n\nexport const POST = async (\n  req: AuthenticatedMedusaRequest<{ file: Express.Multer.File }>,\n  res: MedusaResponse<{ summary: { created: number } }>\n) => {\n  const file = (req as any).file as Express.Multer.File | undefined\n\n  if (!file) {\n    throw new MedusaError(\n      MedusaError.Types.INVALID_DATA,\n      \"No file uploaded. Please upload a CSV file.\"\n    )\n  }\n\n  const seller = await fetchSellerByAuthActorId(\n    req.auth_context.actor_id,\n    req.scope\n  )\n\n  if (!seller) {\n    throw new MedusaError(\n      MedusaError.Types.NOT_FOUND,\n      \"Seller not found for the current user.\"\n    )\n  }\n\n  const fileContent = file.buffer.toString(\"utf-8\")\n\n  const { result } = await importSellerProductsWorkflow(req.scope).run({\n    input: {\n      file_content: fileContent,\n      seller_id: seller.id,\n    },\n  })\n\n  res.status(200).json({\n    summary: {\n      created: Array.isArray(result) ? result.length : 0,\n    },\n  })\n}\n",
      "type": "registry:api"
    },
    {
      "path": "product-import-export/api/vendor/products/export/route.ts",
      "content": "import {\n  AuthenticatedMedusaRequest,\n  MedusaResponse,\n} from \"@medusajs/framework/http\"\nimport { MedusaError } from \"@medusajs/framework/utils\"\nimport { exportSellerProductsWorkflow } from \"../../../../workflows/export-seller-products\"\nimport { fetchSellerByAuthActorId } from \"../helpers/helpers\"\n\nexport const POST = async (\n  req: AuthenticatedMedusaRequest,\n  res: MedusaResponse<{ url: string }>\n) => {\n  const seller = await fetchSellerByAuthActorId(\n    req.auth_context.actor_id,\n    req.scope\n  )\n\n  if (!seller) {\n    throw new MedusaError(\n      MedusaError.Types.NOT_FOUND,\n      \"Seller not found for the current user.\"\n    )\n  }\n\n  const { result } = await exportSellerProductsWorkflow(req.scope).run({\n    input: {\n      seller_id: seller.id,\n    },\n  })\n\n  res.json(result)\n}\n",
      "type": "registry:api"
    },
    {
      "path": "product-import-export/api/vendor/products/helpers/helpers.ts",
      "content": "import { ContainerRegistrationKeys } from \"@medusajs/framework/utils\"\nimport { MedusaContainer } from \"@medusajs/framework/types\"\n\nexport const fetchSellerByAuthActorId = async (\n  authActorId: string,\n  scope: MedusaContainer,\n  fields: string[] = [\"id\"]\n) => {\n  const query = scope.resolve(ContainerRegistrationKeys.QUERY)\n\n  const {\n    data: [seller],\n  } = await query.graph({\n    entity: \"seller\",\n    filters: { id: authActorId },\n    fields,\n  })\n\n  return seller\n}\n",
      "type": "registry:api"
    },
    {
      "path": "product-import-export/api/vendor/products/middlewares.ts",
      "content": "import { MiddlewareRoute } from \"@medusajs/medusa\"\nimport multer from \"multer\"\n\nconst upload = multer({ storage: multer.memoryStorage() })\n\nexport const productImportExportMiddlewares: MiddlewareRoute[] = [\n  {\n    method: [\"POST\"],\n    matcher: \"/vendor/products/import\",\n    middlewares: [upload.single(\"file\")],\n  },\n]\n",
      "type": "registry:api"
    },
    {
      "path": "product-import-export/vendor/hooks/api/product-import-export.tsx",
      "content": "import { useMutation, useQueryClient } from \"@tanstack/react-query\"\nimport type { UseMutationOptions } from \"@tanstack/react-query\"\nimport { queryKeysFactory } from \"@mercurjs/dashboard-shared\"\nimport { ClientError } from \"@mercurjs/client\"\nimport { client } from \"../../lib/client\"\n\nconst PRODUCTS_QUERY_KEY = \"vendor_products\" as const\nexport const productsQueryKeys = queryKeysFactory(PRODUCTS_QUERY_KEY)\n\ntype ImportProductsResponse = { summary: { created: number } }\ntype ExportProductsResponse = { url: string }\n\nexport const useImportProducts = (\n  options?: UseMutationOptions<ImportProductsResponse, ClientError, File>\n) => {\n  const queryClient = useQueryClient()\n\n  return useMutation({\n    mutationFn: async (file: any) => client.vendor.products.import.mutate({file, fetchOptions: {headers:{'Content-Type': 'multipart/form-data'}}}),\n    onSuccess: (data, variables, context) => {\n      queryClient.invalidateQueries({ queryKey: productsQueryKeys.lists() })\n      options?.onSuccess?.(data, variables, context)\n    },\n    ...options,\n  })\n}\n\nexport const useExportProducts = (\n  options?: UseMutationOptions<ExportProductsResponse, ClientError, void>\n) => {\n  return useMutation({\n    mutationFn: async () =>\n      client.vendor.products.export.mutate(),\n    onSuccess: (data, variables, context) => {\n      options?.onSuccess?.(data, variables, context)\n    },\n    ...options,\n  })\n}\n",
      "type": "registry:vendor"
    },
    {
      "path": "product-import-export/vendor/routes/products/page.tsx",
      "content": "import { Link } from \"react-router-dom\"\nimport { Button } from \"@medusajs/ui\"\nimport { ArrowDownTray, ArrowUpTray } from \"@medusajs/icons\"\nimport { ProductListPage } from \"@mercurjs/vendor/pages\"\n\nexport default function ProductsWithImportExport() {\n  return (\n    <ProductListPage>\n      <ProductListPage.Table>\n        <ProductListPage.Header>\n          <ProductListPage.HeaderTitle />\n          <ProductListPage.HeaderActions>\n            <Button size=\"small\" variant=\"secondary\" asChild>\n              <Link to=\"import\">\n                <ArrowUpTray />\n                Import\n              </Link>\n            </Button>\n            <Button size=\"small\" variant=\"secondary\" asChild>\n              <Link to=\"export\">\n                <ArrowDownTray />\n                Export\n              </Link>\n            </Button>\n            <ProductListPage.HeaderCreateButton />\n          </ProductListPage.HeaderActions>\n        </ProductListPage.Header>\n        <ProductListPage.DataTable />\n      </ProductListPage.Table>\n    </ProductListPage>\n  )\n}\n",
      "type": "registry:vendor"
    },
    {
      "path": "product-import-export/vendor/routes/products/import/page.tsx",
      "content": "import { useState } from \"react\";\nimport { Button, Heading } from \"@medusajs/ui\";\n\nimport { RouteDrawer } from \"@mercurjs/dashboard-shared\";\nimport { UploadImport } from \"./components/upload-import\";\nimport { ImportSummary } from \"./components/import-summary\";\n\nexport default function ProductImportPage() {\n  const [summary, setSummary] = useState<{ created: number } | null>(null);\n\n  return (\n    <RouteDrawer>\n      <RouteDrawer.Header>\n        <RouteDrawer.Title asChild>\n          <Heading>Import Products</Heading>\n        </RouteDrawer.Title>\n        <RouteDrawer.Description>\n          Upload a CSV file to import products into your store.\n        </RouteDrawer.Description>\n      </RouteDrawer.Header>\n      <RouteDrawer.Body>\n        {summary ? (\n          <ImportSummary created={summary.created} />\n        ) : (\n          <UploadImport onSuccess={setSummary} />\n        )}\n      </RouteDrawer.Body>\n      <RouteDrawer.Footer>\n        <RouteDrawer.Close asChild>\n          <Button variant=\"secondary\">{summary ? \"Done\" : \"Cancel\"}</Button>\n        </RouteDrawer.Close>\n      </RouteDrawer.Footer>\n    </RouteDrawer>\n  );\n}\n",
      "type": "registry:vendor"
    },
    {
      "path": "product-import-export/vendor/routes/products/import/components/upload-import.tsx",
      "content": "import { useState } from \"react\";\nimport { Button, toast } from \"@medusajs/ui\";\nimport { Trash } from \"@medusajs/icons\";\n\nimport { FileUpload, FilePreview } from \"@mercurjs/dashboard-shared\";\nimport type { FileType } from \"@mercurjs/dashboard-shared\";\nimport { useImportProducts } from \"../../../../hooks/api/product-import-export\";\nimport { downloadImportTemplate } from \"../helpers/import-template\";\n\ntype UploadImportProps = {\n  onSuccess: (summary: { created: number }) => void;\n};\n\nexport const UploadImport = ({ onSuccess }: UploadImportProps) => {\n  const [file, setFile] = useState<FileType | null>(null);\n\n  const { mutateAsync: importProducts, isPending } = useImportProducts({\n    onSuccess: (data) => {\n      toast.success(\n        `Successfully imported ${data.summary?.created ?? 0} product(s).`,\n      );\n      onSuccess(data.summary);\n    },\n    onError: (error) => {\n      toast.error(error.message);\n    },\n  });\n\n  const handleFileUpload = (files: FileType[]) => {\n    if (files.length > 0) {\n      setFile(files[0] ?? null);\n    }\n  };\n\n  const handleImport = async () => {\n    if (!file) return;\n    await importProducts(file.file);\n  };\n\n  const handleRemoveFile = () => {\n    setFile(null);\n  };\n\n  return (\n    <div className=\"flex flex-col gap-y-4\">\n      <div className=\"rounded-lg border border-ui-border-base bg-ui-bg-subtle p-3\">\n        <span className=\"text-ui-fg-subtle txt-small\">\n          Upload a CSV file with your products. You can{\" \"}\n          <button\n            type=\"button\"\n            className=\"text-ui-fg-interactive hover:text-ui-fg-interactive-hover underline\"\n            onClick={downloadImportTemplate}\n          >\n            download the template\n          </button>{\" \"}\n          to get started.\n        </span>\n      </div>\n\n      {!file ? (\n        <FileUpload\n          label=\"Upload CSV file\"\n          hint=\"Only .csv files are supported\"\n          formats={[\".csv\", \"text/csv\"]}\n          onUploaded={handleFileUpload}\n          multiple={false}\n        />\n      ) : (\n        <FilePreview\n          filename={file.file.name}\n          loading={isPending}\n          activity=\"Importing products...\"\n          actions={[\n            {\n              actions: [\n                {\n                  icon: <Trash />,\n                  label: \"Remove\",\n                  onClick: handleRemoveFile,\n                },\n              ],\n            },\n          ]}\n        />\n      )}\n\n      {file && (\n        <div className=\"flex justify-end\">\n          <Button onClick={handleImport} isLoading={isPending} disabled={!file}>\n            Import Products\n          </Button>\n        </div>\n      )}\n    </div>\n  );\n};\n",
      "type": "registry:vendor"
    },
    {
      "path": "product-import-export/vendor/routes/products/import/components/import-summary.tsx",
      "content": "import { Text } from \"@medusajs/ui\"\nimport { CheckCircleSolid } from \"@medusajs/icons\"\n\ntype ImportSummaryProps = {\n  created: number\n}\n\nexport const ImportSummary = ({ created }: ImportSummaryProps) => {\n  return (\n    <div className=\"flex flex-col items-center gap-y-4 py-8\">\n      <CheckCircleSolid className=\"text-ui-fg-interactive\" />\n      <div className=\"flex flex-col items-center gap-y-1\">\n        <Text size=\"large\" weight=\"plus\">\n          Import Complete\n        </Text>\n        <Text className=\"text-ui-fg-subtle\">\n          {created} product(s) were successfully created.\n        </Text>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:vendor"
    },
    {
      "path": "product-import-export/vendor/routes/products/import/helpers/import-template.ts",
      "content": "const IMPORT_TEMPLATE_HEADER = [\n  \"Product Id\",\n  \"Product Handle\",\n  \"Product Title\",\n  \"Product Subtitle\",\n  \"Product Description\",\n  \"Product Status\",\n  \"Product Thumbnail\",\n  \"Product Weight\",\n  \"Product Length\",\n  \"Product Width\",\n  \"Product Height\",\n  \"Product HS Code\",\n  \"Product Origin Country\",\n  \"Product MID Code\",\n  \"Product Material\",\n  \"Product Collection Id\",\n  \"Product Collection Title\",\n  \"Product Type Id\",\n  \"Product Type\",\n  \"Product Tags\",\n  \"Product Discountable\",\n  \"Product External Id\",\n  \"Variant Id\",\n  \"Variant Title\",\n  \"Variant SKU\",\n  \"Variant Barcode\",\n  \"Variant Inventory Quantity\",\n  \"Variant Allow Backorder\",\n  \"Variant Manage Inventory\",\n  \"Variant Weight\",\n  \"Variant Length\",\n  \"Variant Width\",\n  \"Variant Height\",\n  \"Variant HS Code\",\n  \"Variant Origin Country\",\n  \"Variant MID Code\",\n  \"Variant Material\",\n  \"Variant EAN\",\n  \"Variant UPC\",\n  \"Option 1 Name\",\n  \"Option 1 Value\",\n  \"Option 2 Name\",\n  \"Option 2 Value\",\n  \"Price USD\",\n  \"Price EUR\",\n]\n\nexport const getImportTemplateContent = (): string => {\n  return IMPORT_TEMPLATE_HEADER.join(\",\") + \"\\n\"\n}\n\nexport const downloadImportTemplate = () => {\n  const content = getImportTemplateContent()\n  const blob = new Blob([content], { type: \"text/csv\" })\n  const url = URL.createObjectURL(blob)\n  const a = document.createElement(\"a\")\n  a.href = url\n  a.download = \"product-import-template.csv\"\n  a.click()\n  URL.revokeObjectURL(url)\n}\n",
      "type": "registry:vendor"
    },
    {
      "path": "product-import-export/vendor/routes/products/export/page.tsx",
      "content": "import { Button, Heading, Text, toast } from \"@medusajs/ui\"\n\nimport { RouteDrawer, useRouteModal } from \"@mercurjs/dashboard-shared\"\nimport { useExportProducts } from \"../../../hooks/api/product-import-export\"\n\nfunction ExportProductsContent() {\n  const { handleSuccess } = useRouteModal()\n\n  const { mutateAsync: exportProducts, isPending } = useExportProducts({\n    onSuccess: (data) => {\n      if (data.url) {\n        const a = document.createElement(\"a\")\n        a.href = data.url\n        a.download = `products-export-${Date.now()}.csv`\n        a.click()\n      }\n      toast.success(\"Products exported successfully.\")\n      handleSuccess()\n    },\n    onError: (error) => {\n      toast.error(error.message)\n    },\n  })\n\n  const handleExport = async () => {\n    await exportProducts()\n  }\n\n  return (\n    <>\n      <RouteDrawer.Header>\n        <RouteDrawer.Title asChild>\n          <Heading>Export Products</Heading>\n        </RouteDrawer.Title>\n        <RouteDrawer.Description>\n          Export your products as a CSV file.\n        </RouteDrawer.Description>\n      </RouteDrawer.Header>\n      <RouteDrawer.Body>\n        <div className=\"flex flex-col gap-y-4\">\n          <Text className=\"text-ui-fg-subtle\">\n            This will export all your products to a CSV file. The download will\n            start automatically when the export is ready.\n          </Text>\n        </div>\n      </RouteDrawer.Body>\n      <RouteDrawer.Footer>\n        <RouteDrawer.Close asChild>\n          <Button variant=\"secondary\">Cancel</Button>\n        </RouteDrawer.Close>\n        <Button onClick={handleExport} isLoading={isPending}>\n          Export\n        </Button>\n      </RouteDrawer.Footer>\n    </>\n  )\n}\n\nexport default function ProductExportPage() {\n  return (\n    <RouteDrawer>\n      <ExportProductsContent />\n    </RouteDrawer>\n  )\n}\n",
      "type": "registry:vendor"
    }
  ]
}