JS
Medusa
What is Medusa?
Medusa is an ecommerce platform with a built-in framework for customization that allows you to build custom commerce applications without reinventing core commerce logic.
Installation
Run the following command and replace <NAME> and <DATABASE_URL> with your store name:
$ npx create-medusa-app@latest <NAME> --with-nextjs-starter --db-url "<DATABASE_URL>" --no-migrations
If you don't need any database connectivity in the beginning, you can add --skip-db, for example:
$ npx create-medusa-app@latest <NAME> --skip-db
Medusa CLI
$ npx medusa user -e <ADMIN_EMAIL> -p <ADMIN_SUPER_SECRET_PASSWORD>
Database related CLI:
$ npx medusa db:setup #Create the database, run migrations and sync links
$ npx medusa db:create #Create the database used by your application
$ npx medusa db:migrate #Migrate the database by executing pending migrations
$ npx medusa db:migrate:scripts #Run all migration scripts
$ npx medusa db:rollback [modules...] #Rollback last batch of executed migrations for a given module
$ npx medusa db:generate [modules...] #Generate migrations for a given module
If you want to seed the database:
$ npx medusa exec ./src/scripts/seed.ts
$ medusa plugin:db:generate #Generate migrations for modules in a plugin
$ medusa db:sync-links #Sync database schema with the links defined by your application and Medusa core
$ medusa plugin:build #Build plugin source for publishing to a package registry
$ medusa plugin:develop #Start plugin development process in watch mode. Changes will be re-published to the local
Deployment
Railway
Backend
- Click on Create on right top corner
- Choose GitHub Repo
- Select or authorize the
Medusa Backendrepository from your own GitHub account - Open the Variables tab by clicking on the newly created service
- Deploy the new service
NODE_ENV="production"
PORT="9000"
ADMIN_CORS="https://${{RAILWAY_PUBLIC_DOMAIN}},https://${{RAILWAY_PRIVATE_DOMAIN}}"
AUTH_CORS="https://${{RAILWAY_PUBLIC_DOMAIN}},https://${{RAILWAY_PRIVATE_DOMAIN}}"
COOKIE_SECRET=""
DATABASE_URL="postgresql://<DB_OWNER>:<DB_PASSWORD>@<NEON_INSTANCE>.<REGION>.aws.neon.tech/<DB_NAME>?sslmode=require"
JWT_SECRET=""
MEILISEARCH_HOST="https://${{MeiliSearch.MEILI_PUBLIC_URL}}"
MEILISEARCH_MASTER_KEY="${{MeiliSearch.MEILI_MASTER_KEY}}"
MINIO_ACCESS_KEY="${{Bucket.MINIO_ROOT_USER}}"
MINIO_ENDPOINT="${{Bucket.MINIO_PUBLIC_HOST}}"
MINIO_SECRET_KEY="${{Bucket.MINIO_ROOT_PASSWORD}}"
RAILWAY_HEALTHCHECK_TIMEOUT_SEC="720"
RAILWAY_PUBLIC_DOMAIN_VALUE="https://${{RAILWAY_PUBLIC_DOMAIN}}"
REDIS_URL="${{Redis.REDIS_URL}}?family=0"
S3_ACCESS_KEY_ID=""
S3_BUCKET=""
S3_ENDPOINT="https://s3.<REGION>.amazonaws.com"
S3_FILE_URL="https://<BUCKET_NAME>.s3.<REGION>.amazonaws.com"
S3_REGION="<REGION>"
S3_SECRET_ACCESS_KEY=""
SENDGRID_API_KEY=""
SENDGRID_FROM_EMAIL=""
FOURPX_API_KEY="fecfabd3-aff1-46ef-a37c-9cf125c57b39"
FOURPX_API_SECRET="71862644-c4ac-4934-95c7-71deb5601c4e"
SFEXPRESS_API_CLIENTID=""
SFEXPRESS_MODE="sandbox"
SFEXPRESS_SECRET_PRODUCTION=""
SFEXPRESS_SECRET_SANDBOX=""
STORE_CORS=""
STRIPE_API_KEY="pk_...V"
STRIPE_WEBHOOK_SECRET="whsec_..."
Storefront
- Click on Create on right top corner
- Choose GitHub Repo
- Select or authorize the
Medusa Storefrontrepository from your own GitHub account - Open the Variables tab by clicking on the newly created service
- Deploy the new service
PORT=8000
MEDUSA_BACKEND_URL=https://${{Backend.RAILWAY_PUBLIC_DOMAIN}}
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_*****************
NEXT_PUBLIC_BASE_URL=https://${{RAILWAY_PUBLIC_DOMAIN}}
NEXT_PUBLIC_DEFAULT_REGION=us
NEXT_PUBLIC_STRIPE_KEY=
REVALIDATE_SECRET=supersecret
S3_FILE_URL=<bucket_name>.s3.<region>.amazonaws.com
- Open the Settings tab by clicking on the newly created service
- Click on **Custom Domain ** under Public Networking, select the port in the dropdown menu or type `8000``` which was defined in the Variables.
Self-Hosted Medusa Backend
NODE_ENV=production
LOG_FILE=logs/medusa.log
BASE_HOST=xxxxxxxxxx.ngrok-free.app
MEDUSA_ADMIN_ONBOARDING_TYPE=default
#STORE_CORS=http://localhost:8000,https://docs.medusajs.com
STORE_CORS=/ngrok-free\.app$/
ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
AUTH_CORS=http://localhost:5173,http://localhost:9000,http://localhost:8000,https://docs.medusajs.com
REDIS_URL=redis://localhost:6379
#DATABASE_URL=postgresql://postgres.jvskivhrvpwsxuwwbiay:<password>@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres?pgbouncer=true
DATABASE_URL=postgres://medusa:<password>@localhost:5432/medusa
JWT_SECRET=averylongstringhere
COOKIE_SECRET=averylongstringhere
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET=""
S3_ENDPOINT="https://s3.ap-southeast-1.amazonaws.com"
S3_FILE_URL="https://xxxxxx.s3.ap-southeast-1.amazonaws.com"
S3_REGION="ap-southeast-1"
Build for Production
Reference URL: https://docs.medusajs.com/learn/build#build-command
Option 1:
$ npx medusa build
$ cd .medusa/server && npm install
$ cp ../../.env .env.production
$ npm run start
Option 2:
Edit the package.json in the Medusa backend root folder
"scripts": {
"build": "medusa build && ln -s .medusa/server/public/ public",
"seed": "medusa exec ./src/scripts/seed.ts",
"start": "medusa start",
}
Then run npm run start
Modules
Essential Modules Config
import {loadEnv, defineConfig, Modules} from '@medusajs/framework/utils'
loadEnv(process.env.NODE_ENV || 'development', process.cwd())
module.exports = defineConfig({
projectConfig: {
databaseUrl: process.env.DATABASE_URL,
redisUrl: process.env.REDIS_URL,
databaseLogging: false,
http: {
storeCors: process.env.STORE_CORS!,
adminCors: process.env.ADMIN_CORS!,
authCors: process.env.AUTH_CORS!,
jwtSecret: process.env.JWT_SECRET || "supersecret",
cookieSecret: process.env.COOKIE_SECRET || "supersecret",
}
},
modules: [
{
key: Modules.CACHE,
resolve: "@medusajs/cache-redis",
options: {
redisUrl: process.env.REDIS_URL,
ttl: 30, // Time to live in seconds
},
},
{
key: Modules.EVENT_BUS,
resolve: "@medusajs/event-bus-redis",
options: {
redisUrl: process.env.REDIS_URL,
},
},
{
key: Modules.WORKFLOW_ENGINE,
resolve: '@medusajs/workflow-engine-redis',
options: {
redis: {
url: process.env.REDIS_URL,
},
},
},
{
key: Modules.FILE,
resolve: "@medusajs/medusa/file",
options: {
providers: [
{
resolve: "@medusajs/medusa/file-s3",
id: "s3",
options: {
file_url: process.env.S3_FILE_URL,
access_key_id: process.env.S3_ACCESS_KEY_ID,
secret_access_key: process.env.S3_SECRET_ACCESS_KEY,
region: process.env.S3_REGION,
bucket: process.env.S3_BUCKET,
endpoint: process.env.S3_ENDPOINT,
},
},
],
},
...(process.env.SENDGRID_API_KEY && process.env.SENDGRID_FROM_EMAIL ? [{
key: Modules.NOTIFICATION,
resolve: '@medusajs/notification',
options: {
providers: [
...(process.env.SENDGRID_API_KEY && process.env.SENDGRID_FROM_EMAIL ? [{
resolve: '@medusajs/notification-sendgrid',
id: 'sendgrid',
options: {
channels: ['email'],
api_key: process.env.SENDGRID_API_KEY,
from: process.env.SENDGRID_FROM_EMAIL,
}
}] : []),
]
}
}] : []),
...(process.env.STRIPE_API_KEY && process.env.STRIPE_WEBHOOK_SECRET ? [{
key: Modules.PAYMENT,
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "@medusajs/medusa/payment-stripe",
id: "stripe",
options: {
apiKey: process.env.STRIPE_API_KEY,
},
},
],
},
}] : [])
},
],
})
Redis
import {Modules} from "@medusajs/framework/utils"
// ...
module.exports = defineConfig({
// ...
modules: [
{
resolve: "@medusajs/medusa/cache-redis",
options: {
redisUrl: process.env.CACHE_REDIS_URL,
},
},
],
})
AWS S3 Storage
- Create AWS user with
AmazonS3FullAccesspermissions. - Create AWS IAM user access key ID and secret access key.
- Create S3 bucket with the "Public Access setting" enabled:
- On your bucket's dashboard, click on the Permissions tab.
- Click on the Edit button of the Block public access (bucket settings) section.
- In the form that opens, don't toggle any checkboxes and click the "Save changes" button.
- Confirm saving the changes by entering
confirmin the pop-up that shows. - Back on the Permissions page, scroll to the Object Ownership section and click the Edit button.
- In the form that opens:
- Choose the "ACLs enabled" card.
- Click on the "Save changes" button.
- Back on the Permissions page, scroll to the "Access Control List (ACL)" section and click on the Edit button.
- In the form that opens, enable the Read permission for "Everyone (public access)".
- Check the "I understand the effects of these changes on my objects and buckets." checkbox.
- Click on the "Save changes" button.
Stripe Payment
Stripe Setup Prerequisites
Register or login your Stripe account. Retrieve the API key and create webhook.
Install Stripe CLI:
$ brew install stripe/stripe-cli/stripe
$ stripe login
There is a pairing code showing and a Stripe link. Open that link and verify the pairing code.
Stripe Webhook
For production applications, you must set up webhooks in Stripe that inform Medusa of changes and updates to payments.
Webhook URL
Medusa has a {server_url}/hooks/payment/{provider_id} API route that you can use to register webhooks in Stripe, where:
{server_url}is the URL to your deployed Medusa application in server mode.{provider_id}is the ID of the provider as explained in the Stripe Payment Provider IDs section, without the pp_ prefix.The Stripe Module Provider supports the following payment types, and the webhook endpoint URL is different for each:
| Stripe Payment Type | Webhook Endpoint URL |
|---|---|
| Basic Stripe Payment | {server_url}/hooks/payment/stripe_stripe |
| Bancontact Payments | {server_url}/hooks/payment/stripe-bancontact_stripe |
| BLIK Payments | {server_url}/hooks/payment/stripe-blik_stripe |
| giropay Payments | {server_url}/hooks/payment/stripe-giropay_stripe |
| iDEAL Payments | {server_url}/hooks/payment/stripe-ideal_stripe |
| Przelewy24 Payments | {server_url}/hooks/payment/stripe-przelewy24_stripe |
| PromptPay Payments | {server_url}/hooks/payment/stripe-promptpay_stripe |
Webhook Events
When you set up the webhook in Stripe, choose the following events to listen to:
payment_intent.amount_capturable_updatedpayment_intent.succeededpayment_intent.payment_failed
Backend
- Add it to the array of providers passed to the Payment Module in medusa-config.ts:
module.exports = defineConfig({
// ...
modules: [
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "@medusajs/medusa/payment-stripe",
id: "stripe",
options: {
apiKey: process.env.STRIPE_API_KEY,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
capture: true,
automatic_payment_methods: true,
payment_description: "Stripe is highly secured"
},
},
],
},
},
],
})
- Environment Variables
Add these to your .env file:
Login your Stripe account and open the Developer Workbench Overview page. Copy the Secret key from the API keys and a Signing secret from the Webhook tab.
STRIPE_API_KEY=sk_test_your_stripe_secret_key
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
- Add Payment Provider to Region
In your Medusa admin, go to Settings > Regions and add "stripe" as a payment provider to your regions.
Storefront Stripe Implementation
Prerequisites
Steps
- Install Stripe SDK In your storefront, use the following command to install Stripe's JS and React SDKs:
$ pnpm add @stripe/react-stripe-js @stripe/stripe-js
- Add Stripe Environment Variables Next, add an environment variable holding your Stripe publishable API key. For example:
NEXT_PUBLIC_STRIPE_PK=pk_test_51Kj...
Test Payment
To test Stripe payments, use the following test credit card details:
- Card Number: 4242 4242 4242 4242
- Expiry Date: Any future date
- CVC: Any 3 digits
Medusa Plugin Development
Note
Make sure there is no NODE_ENV=production set in your development environment, all devDependencies packages will not be installed. So that you will not able to run medusa cli and yalc.
You create your plugin project separately outside a Medusa backend folder.
npx create-medusa-app my-plugin --plugin
This command publishes the plugin into a local package registry (via yalc) so you can install it into your Medusa application.
npx medusa plugin:publish
After that, run the following Medusa CLI command to install the new plugin:
npx medusa plugin:add @myorg/plugin-name
If you need to remove the dev version plugin in your Medusa project, run:
yalc remove <your-plugin-name>
#or
yalc remove --all
If you need to check all packages installed by yalc, run:
yalc installations show
Register Plugin in Medusa Application (Backend)
module.exports = defineConfig({
// ...
plugins: [
{
resolve: "@myorg/plugin-name",
options: {
apiKey: true,
},
},
],
})
Watch Plugin Changes During Development
npx medusa plugin:develop
This command will:
- Watch for changes in the plugin. Whenever a file is changed, the plugin is automatically built.
- Publish the plugin changes to the local package registry. This will automatically update the plugin in the Medusa application using it. You can also benefit from real-time HMR updates of admin extensions.
Publish Plugin to NPM
npx medusa plugin:build
The command will compile an output in the .medusa/server directory.
You can now publish the plugin to npm using the NPM CLI tool. Run the following command to publish the plugin to npm:
npm publish
Install Public Plugin in Medusa Application
npm install @myorg/plugin-name
Update a Published Plugin
If you've published a plugin, and you've made changes to it, you'll have to publish the update to NPM again.
- First, run the following command to change the version of the plugin:
npm version <type>
Where <type> indicates the type of version update you’re publishing. For example, it can be major or minor. Refer to the npm version documentation for more information.
Then, re-run the same commands for publishing a plugin:
npx medusa plugin:build
npm publish
This will publish an updated version of your plugin under a new version.
Troubleshooting
Module @medusajs/cache-redis doesn't have a serviceName. Please provide a 'key' for the module or check the service joiner config.
- Open the
medsua-config.tsfile in Medusa backend repository
import {Modules} from "@medusajs/framework/utils"
//...
modules: [
{
key: Modules.CACHE,
resolve: "@medusajs/cache-redis",
options: {
redisUrl: process.env.REDIS_URL,
ttl: 30, // Time to live in seconds
},
},
//...
Make sure the modules used have the key
Blocked request. This host example.com is not allowed. To allow this host, add example.com to server.allowedHosts in vite.config.js
Edit medusa-config.ts and add the following admin settings:
module.exports = defineConfig({
...,
admin: {
disable: process.env.DISABLE_MEDUSA_ADMIN === "true",
path: `/${process.env.ADMIN_PANEL_PREFIX}`,
backendUrl: process.env.MEDUSA_BACKEND_URL,
vite: () => ({
server: {
allowedHosts: ['example.com'],
},
}),
},
})
OPTIMIZED_EXTERNAL_IMAGE_REQUEST_UNAUTHORIZED
DYNAMIC_SERVER_USAGE