DocsDeveloper SectionStep-by-Step Customization and Development Guide

Customization Recipes and New Feature Development

This guide is tailored for developers who have acquired the project source code and want to add new custom features, make product options multilingual, or build custom visual components.

Recipe 1: Managing and Translating Product Options and Color Swatches (`color`)

Immutable Rule for Color Key (Color Swatches)
In the Medusa admin panel, the option title assigned to color must, without exception, be the word color (or Color). The Next.js frontend uses this key to identify Hex color codes and create interactive color circles and visual filters.

Process of making option titles multilingual with Strapi:

  • For other options (like size, material, brand), naming in any language is free.
  • Corresponding localized translations for option titles are defined dynamically in the Strapi CMS panel.
  • The Next.js frontend fetches the title translation from Strapi based on the user's current Locale while the color filter key remains color.
Example of fetching option translation from Strapi in frontend
typescript
// store/src/lib/strapi-client/get-option-translations.ts
export async function getOptionTranslation(optionKey: string, locale: string = "fa") {
const res = await fetch(
`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/option-translations?filters[key][$eq]=${optionKey}&locale=${locale}`,
{ headers: { Authorization: `Bearer ${process.env.STRAPI_API_TOKEN_FOR_FRONT}` } }
)
const json = await res.json()
return json.data?.[0]?.translated_name || optionKey
}

Recipe 2: Adding a New Workflow and REST API Endpoint in Medusa v2

In Medusa v2, business logic is separated into Steps and Workflows:

store-admin/src/workflows/daily-deals.ts
typescript
import { createWorkflow, createStep, StepResponse, WorkflowResponse } from "@medusajs/framework/workflows-sdk";
const fetchDealsStep = createStep("fetch-deals-step", async () => {
const deals = [{ id: "deal_1", title: "Daily Special Deal", discount: "25%" }];
return new StepResponse(deals);
});
export const getDailyDealsWorkflow = createWorkflow("get-daily-deals", function () {
const deals = fetchDealsStep();
return new WorkflowResponse(deals);
});
store-admin/src/api/store/daily-deals/route.ts
typescript
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
import { getDailyDealsWorkflow } from "../../../workflows/daily-deals";
export async function GET(req: MedusaRequest, res: MedusaResponse) {
const { result } = await getDailyDealsWorkflow(req.scope).run();
res.json({ deals: result });
}

Recipe 3: Creating a New Block for the Homepage

Homepage blocks are located in the store/src/modules/home/components/ path. Each block consists of three parts: a type definition in homepage.ts, a Component schema in Strapi, and a React component folder.

Actual Project Structure
Existing blocks like FeaturesBlock, BlogPostsBlock, ProductShowcase, and ProductSplitView each have a standalone folder and are identified in the BlockRenderer via the __component returned by Strapi.

Example: Adding a "Countdown" Block

  1. Define TypeScript Type — In the store/src/lib/data/homepage.ts file, add the new block interface and append it to the main union type:
store/src/lib/data/homepage.ts
typescript
export interface CountdownBlock {
__component: "ui.countdown-block"
id: number
title?: string
target_date: string
}
// Add to union type
export type HomepageBlock =
| ProductSplitViewBlock
| ProductShowcaseBlock
| FeaturesBlock
| BlogPostsBlock
| CategoryCollectionBlock
| CountdownBlock // <-- added here
  1. Create Folder and React Component — Create a folder named CountdownBlock in the store/src/modules/home/components/ path:
store/src/modules/home/components/CountdownBlock/index.tsx
tsx
import React from "react"
import type { CountdownBlock as CountdownBlockType } from "@lib/data/homepage"
interface CountdownBlockProps {
block: CountdownBlockType
}
export default function CountdownBlock({ block }: CountdownBlockProps) {
return (
<section className="py-16 text-center">
<h2 className="text-xl font-bold mb-4">{block.title}</h2>
<p className="text-gray-500">{block.target_date}</p>
</section>
)
}
  1. Register in BlockRenderer — Open the store/src/modules/home/components/BlockRenderer/index.tsx file and add a new case to the switch:
store/src/modules/home/components/BlockRenderer/index.tsx
tsx
import CountdownBlock from "../CountdownBlock"
// Inside BlockRenderer switch(block.__component):
case "ui.countdown-block":
BlockContent = <CountdownBlock block={block} />
break
  1. Create Component Schema in Strapi — Create a new JSON file in the website-admin/src/components/ui/ folder:
website-admin/src/components/ui/countdown-block.json
json
{
"collectionName": "components_ui_countdown_blocks",
"info": {
"displayName": "Countdown Block",
"icon": "clock",
"description": "A countdown timer block for the homepage"
},
"options": {},
"attributes": {
"title": { "type": "string" },
"target_date": { "type": "datetime", "required": true }
}
}
Final Step
After restarting Strapi, go to the admin panel Homepage > Dynamic Zone. The new "Countdown Block" will appear in the list of available blocks.

Recipe 4: Connecting a New Payment Provider

To add a new payment gateway, define a service implementing AbstractPaymentProvider in the store-admin/src/modules/payment/ folder and register it in medusa-config.ts under the providers section of the payment module.