docs: editing and general fixes of medusa's learning resources (#7261)
* docs: editing and general fixes of medusa's learning resources * fix build script * update ui dependency * fix build * adjust next.js steps
This commit is contained in:
@@ -0,0 +1,686 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Contentful Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Contentful](https://www.contentful.com/) is a headless CMS service that allows developers to integrate rich CMS functionalities into any platform.
|
||||
|
||||
By integrating Contentful to Medusa, you can benefit from powerful features in your ecommerce store such as:
|
||||
|
||||
- Rich CMS details for product.
|
||||
- Easy-to-use interface to manage content for static content and pages.
|
||||
- Localization for product and storefront content.
|
||||
- Two-way sync between Contentful and Medusa.
|
||||
|
||||
---
|
||||
|
||||
## Install the Contentful Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Contentful account with a space](https://www.contentful.com/sign-up/).
|
||||
- An Event Module installed in the Medusa application, such as the [Redis Event Module](../../../architectural-modules/event/redis/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Contentful plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-contentful
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "space_id", "The Contentful space's ID."],
|
||||
["7", "access_token", "The personal access token for content management."],
|
||||
["8", "environment", "The Contentful environment."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-contentful`,
|
||||
options: {
|
||||
space_id: process.env.CONTENTFUL_SPACE_ID,
|
||||
access_token: process.env.CONTENTFUL_ACCESS_TOKEN,
|
||||
environment: process.env.CONTENTFUL_ENV,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Contentful Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`space_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ID of your [Contentful space](https://www.contentful.com/help/find-space-id/).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`access_token`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating [the personal access token for content management](https://www.contentful.com/help/personal-access-tokens/#how-to-get-a-personal-access-token-the-web-app).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`environment`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [Contentful environment](https://www.contentful.com/developers/docs/concepts/multiple-environments/). Typically, its value should be `master`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`ignore_threshold`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The number of seconds to wait before re-syncing a specific record.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`2`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`custom_<TYPE>_fields`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object that allows you to map fields in Medusa to [custom field names](#custom-field-mapping).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the following environment variables.
|
||||
|
||||
```bash
|
||||
CONTENTFUL_SPACE_ID=<YOUR_SPACE_ID>
|
||||
CONTENTFUL_ACCESS_TOKEN=<YOUR_ACCESS_TOKEN>
|
||||
CONTENTFUL_ENV=master
|
||||
```
|
||||
|
||||
### Custom Field Mapping
|
||||
|
||||
When the plugin syncs data between Contentful and Medusa, it expects a set of fields to be defined in the respective content models in Contentful. If you use different names to define those fields in Contentful, specify them in the `custom_<TYPE>_fields` option mentioned earlier, where `<TYPE>` is the name of the content model.
|
||||
|
||||
For example, to change the name of the product’s `title` field, pass the following option to the plugin:
|
||||
|
||||
```js title="medusa-config.js" highlights={[["9"], ["10"], ["11"]]}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-contentful`,
|
||||
options: {
|
||||
space_id: process.env.CONTENTFUL_SPACE_ID,
|
||||
access_token: process.env.CONTENTFUL_ACCESS_TOKEN,
|
||||
environment: process.env.CONTENTFUL_ENV,
|
||||
custom_product_fields: {
|
||||
title: "name",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
The rest of this section includes the field names you can customize using this option for each content model type.
|
||||
|
||||
<Details summaryContent="product">
|
||||
|
||||
- `title`
|
||||
- `subtitle`
|
||||
- `description`
|
||||
- `variants`
|
||||
- `options`
|
||||
- `medusaId`
|
||||
- `type`
|
||||
- `collection`
|
||||
- `tags`
|
||||
- `handle`
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="variant">
|
||||
|
||||
- `title`
|
||||
- `sku`
|
||||
- `prices`
|
||||
- `options`
|
||||
- `medusaId`
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="region">
|
||||
|
||||
- `name`
|
||||
- `countries`
|
||||
- `paymentProviders`
|
||||
- `fulfillmentProviders`
|
||||
- `medusaId`
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="collection">
|
||||
|
||||
- `title`
|
||||
- `medusaId`
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="type">
|
||||
|
||||
- `name`
|
||||
- `medusaId`
|
||||
|
||||
</Details>
|
||||
|
||||
### Migrate Content Models
|
||||
|
||||
In your Contentful space, you must have content models for Medusa entities such as products and regions.
|
||||
|
||||
You can either create the content models manually, or create a loader in the Medusa application that migrates these content models into Contentful.
|
||||
|
||||
This section includes migration scripts for Medusa’s data models that are relevant for Contentful.
|
||||
|
||||
Before creating the migration scripts, run the following command in the root of your Medusa backend to install Contentful’s migration SDK:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install --save-dev contentful-migration
|
||||
```
|
||||
|
||||
<Details summaryContent="product Content Model">
|
||||
|
||||
Create the file `src/loaders/contentful-migrations/product.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/contentful-migrations/product.ts"
|
||||
import Migration, {
|
||||
MigrationContext,
|
||||
} from "contentful-migration"
|
||||
|
||||
export function productMigration(
|
||||
migration: Migration,
|
||||
context?: MigrationContext
|
||||
) {
|
||||
const product = migration
|
||||
.createContentType("product")
|
||||
.name("Product")
|
||||
.displayField("title")
|
||||
|
||||
product
|
||||
.createField("title")
|
||||
.name("Title")
|
||||
.type("Symbol")
|
||||
.required(true)
|
||||
product
|
||||
.createField("subtitle")
|
||||
.name("Subtitle")
|
||||
.type("Symbol")
|
||||
product
|
||||
.createField("handle")
|
||||
.name("Handle")
|
||||
.type("Symbol")
|
||||
product
|
||||
.createField("thumbnail")
|
||||
.name("Thumbnail")
|
||||
.type("Link")
|
||||
.linkType("Asset")
|
||||
product
|
||||
.createField("description")
|
||||
.name("Description")
|
||||
.type("Text")
|
||||
product
|
||||
.createField("options")
|
||||
.name("Options")
|
||||
.type("Object")
|
||||
product
|
||||
.createField("tags")
|
||||
.name("Tags")
|
||||
.type("Object")
|
||||
product
|
||||
.createField("collection")
|
||||
.name("Collection")
|
||||
.type("Symbol")
|
||||
product
|
||||
.createField("type")
|
||||
.name("Type")
|
||||
.type("Symbol")
|
||||
product
|
||||
.createField("variants")
|
||||
.name("Variants")
|
||||
.type("Array")
|
||||
.items({
|
||||
type: "Link",
|
||||
linkType: "Entry",
|
||||
validations: [
|
||||
{
|
||||
linkContentType: ["productVariant"],
|
||||
},
|
||||
],
|
||||
})
|
||||
product
|
||||
.createField("medusaId")
|
||||
.name("Medusa ID")
|
||||
.type("Symbol")
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="productVariant Content Model">
|
||||
|
||||
Create the file `src/loaders/contentful-migrations/product-variant.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/contentful-migrations/product-variant.ts"
|
||||
import Migration, {
|
||||
MigrationContext,
|
||||
} from "contentful-migration"
|
||||
|
||||
export function productVariantMigration(
|
||||
migration: Migration,
|
||||
context?: MigrationContext
|
||||
) {
|
||||
const productVariant = migration
|
||||
.createContentType("productVariant")
|
||||
.name("Product Variant")
|
||||
.displayField("title")
|
||||
|
||||
productVariant
|
||||
.createField("title")
|
||||
.name("Title")
|
||||
.type("Symbol")
|
||||
.required(true)
|
||||
productVariant
|
||||
.createField("sku")
|
||||
.name("SKU")
|
||||
.type("Symbol")
|
||||
productVariant
|
||||
.createField("options")
|
||||
.name("Options")
|
||||
.type("Object")
|
||||
productVariant
|
||||
.createField("prices")
|
||||
.name("Prices")
|
||||
.type("Object")
|
||||
productVariant
|
||||
.createField("medusaId")
|
||||
.name("Medusa ID")
|
||||
.type("Symbol")
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="collection Content Model">
|
||||
|
||||
Create the file `src/loaders/contentful-migrations/product-collection.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/contentful-migrations/product-collection.ts"
|
||||
import Migration, {
|
||||
MigrationContext,
|
||||
} from "contentful-migration"
|
||||
|
||||
export function productCollectionMigration(
|
||||
migration: Migration,
|
||||
context?: MigrationContext
|
||||
) {
|
||||
const collection = migration
|
||||
.createContentType("collection")
|
||||
.name("Product Collection")
|
||||
.displayField("title")
|
||||
|
||||
collection
|
||||
.createField("title")
|
||||
.name("Title")
|
||||
.type("Symbol")
|
||||
.required(true)
|
||||
collection
|
||||
.createField("medusaId")
|
||||
.name("Medusa ID")
|
||||
.type("Symbol")
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="productType Content Model">
|
||||
|
||||
Create the file `src/loaders/contentful-migrations/product-type.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/contentful-migrations/product-type.ts"
|
||||
import Migration, {
|
||||
MigrationContext,
|
||||
} from "contentful-migration"
|
||||
|
||||
export function productTypeMigration(
|
||||
migration: Migration,
|
||||
context?: MigrationContext
|
||||
) {
|
||||
const collection = migration
|
||||
.createContentType("productType")
|
||||
.name("Product Type")
|
||||
.displayField("title")
|
||||
|
||||
collection
|
||||
.createField("title")
|
||||
.name("Title")
|
||||
.type("Symbol")
|
||||
.required(true)
|
||||
collection
|
||||
.createField("medusaId")
|
||||
.name("Medusa ID")
|
||||
.type("Symbol")
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="region Content Model">
|
||||
|
||||
Create the file `src/loaders/contentful-migrations/region.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/contentful-migrations/region.ts"
|
||||
import Migration, {
|
||||
MigrationContext,
|
||||
} from "contentful-migration"
|
||||
|
||||
export function regionMigration(
|
||||
migration: Migration,
|
||||
context?: MigrationContext
|
||||
) {
|
||||
const region = migration
|
||||
.createContentType("region")
|
||||
.name("Region")
|
||||
.displayField("name")
|
||||
|
||||
region
|
||||
.createField("name")
|
||||
.name("Name")
|
||||
.type("Symbol")
|
||||
.required(true)
|
||||
region
|
||||
.createField("countries")
|
||||
.name("Options")
|
||||
.type("Object")
|
||||
region
|
||||
.createField("paymentProviders")
|
||||
.name("Payment Providers")
|
||||
.type("Object")
|
||||
region
|
||||
.createField("fulfillmentProviders")
|
||||
.name("Fulfillment Providers")
|
||||
.type("Object")
|
||||
region
|
||||
.createField("currencyCode")
|
||||
.name("Currency Code")
|
||||
.type("Symbol")
|
||||
region
|
||||
.createField("medusaId")
|
||||
.name("Medusa ID")
|
||||
.type("Symbol")
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
Finally, create a loader at `src/loaders/index.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/index.ts"
|
||||
import {
|
||||
ConfigModule,
|
||||
StoreService,
|
||||
MedusaContainer,
|
||||
} from "@medusajs/medusa"
|
||||
import { runMigration } from "contentful-migration"
|
||||
import {
|
||||
productMigration,
|
||||
} from "./contentful-migrations/product"
|
||||
import {
|
||||
productVariantMigration,
|
||||
} from "./contentful-migrations/product-variant"
|
||||
import {
|
||||
productCollectionMigration,
|
||||
} from "./contentful-migrations/product-collection"
|
||||
import {
|
||||
productTypeMigration,
|
||||
} from "./contentful-migrations/product-type"
|
||||
import {
|
||||
regionMigration,
|
||||
} from "./contentful-migrations/region"
|
||||
|
||||
type ContentfulPluginType = {
|
||||
resolve: string
|
||||
options: {
|
||||
space_id: string
|
||||
access_token: string
|
||||
environment: string
|
||||
}
|
||||
}
|
||||
|
||||
export default async (
|
||||
container: MedusaContainer,
|
||||
config: ConfigModule
|
||||
): Promise<void> => {
|
||||
// ensure that migration only runs once
|
||||
const storeService = container.resolve<StoreService>(
|
||||
"storeService"
|
||||
)
|
||||
const store = await storeService.retrieve()
|
||||
|
||||
if (store.metadata?.ran_contentful_migrations) {
|
||||
return
|
||||
}
|
||||
|
||||
console.info("Running contentful migrations...")
|
||||
|
||||
// load Contentful options
|
||||
const contentfulPlugin = config.plugins
|
||||
.find((plugin) =>
|
||||
typeof plugin === "object" &&
|
||||
plugin.resolve === "medusa-plugin-contentful"
|
||||
) as ContentfulPluginType
|
||||
|
||||
if (!contentfulPlugin) {
|
||||
console.log(
|
||||
"Didn't find Contentful plugin. Aborting migration..."
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const options = {
|
||||
spaceId: contentfulPlugin.options.space_id,
|
||||
accessToken: contentfulPlugin.options.access_token,
|
||||
environment: contentfulPlugin.options.environment,
|
||||
yes: true,
|
||||
}
|
||||
|
||||
const migrationFunctions = [
|
||||
{
|
||||
name: "Product",
|
||||
function: productMigration,
|
||||
},
|
||||
{
|
||||
name: "Product Variant",
|
||||
function: productVariantMigration,
|
||||
},
|
||||
{
|
||||
name: "Product Collection",
|
||||
function: productCollectionMigration,
|
||||
},
|
||||
{
|
||||
name: "Product Type",
|
||||
function: productTypeMigration,
|
||||
},
|
||||
{
|
||||
name: "Region",
|
||||
function: regionMigration,
|
||||
},
|
||||
]
|
||||
|
||||
await Promise.all(
|
||||
migrationFunctions.map(async (migrationFunction) => {
|
||||
console.info(`Migrating ${
|
||||
migrationFunction.name
|
||||
} component...`)
|
||||
try {
|
||||
await runMigration({
|
||||
...options,
|
||||
migrationFunction: migrationFunction.function,
|
||||
})
|
||||
console.info(`Finished migrating ${
|
||||
migrationFunction.name
|
||||
} component`)
|
||||
} catch (e) {
|
||||
if (
|
||||
typeof e === "object" && "errors" in e &&
|
||||
Array.isArray(e.errors) &&
|
||||
e.errors.length > 0 &&
|
||||
e.errors[0].type === "Invalid Action" &&
|
||||
e.errors[0].message.includes("already exists")
|
||||
) {
|
||||
console.info(`${
|
||||
migrationFunction.name
|
||||
} already exists. Skipping its migration.`)
|
||||
} else {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
await storeService.update({
|
||||
metadata: {
|
||||
ran_contentful_migrations: true,
|
||||
},
|
||||
})
|
||||
|
||||
console.info("Finished contentful migrations")
|
||||
}
|
||||
```
|
||||
|
||||
Notice that in the script you store a flag in the default store’s `metadata` attribute to ensure these migrations only run once.
|
||||
|
||||
### Setup Webhooks
|
||||
|
||||
As mentioned in the introduction, this plugin supports two-way sync. A subscriber in the plugin listens to changes in the data, such as adding a new product, and syncs the data with Contentful.
|
||||
|
||||
To update the Medusa application when changes occur in Contentful, you must configure webhooks settings in Contentful.
|
||||
|
||||
<Note>
|
||||
|
||||
For webhooks to work, your Medusa application must be deployed and accessible publicly.
|
||||
|
||||
</Note>
|
||||
|
||||
To do that:
|
||||
|
||||
1. On your Contentful Space Dashboard, click on Settings from the navigation bar, then choose Webhooks.
|
||||
2. Click on the Add Webhook button.
|
||||
3. In the form, enter a name for the webhook.
|
||||
4. In the URL field, choose the method `POST` and in the input next to it enter the URL `<MEDUSA_URL>/hooks/contentful` where `<MEDUSA_URL>` is the URL of your deployed Medusa application.
|
||||
5. Scroll down to find the Content Type select field. Choose `application/json` as its value.
|
||||
6. You can leave the rest of the fields the same and click on the Save button.
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
Run the following command to start your Medusa application and test the plugin:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
If you created migration scripts, they’ll run when the Medusa application starts and migrate your content models to Contentful. You can go to your space’s dashboard to confirm they’ve been created.
|
||||
|
||||
After that, try the sync functionality by creating or updating products in the Medusa application. If you’ve also setup webhooks, you can test out the sync from Contentful to Medusa.
|
||||
@@ -0,0 +1,229 @@
|
||||
export const metadata = {
|
||||
title: `Strapi Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note>
|
||||
|
||||
This plugin is a [community plugin](https://github.com/SGFGOV/medusa-strapi-repo) and is not managed by the official Medusa team. It supports v4 of Strapi. If you run into any issues, please refer to the [plugin's repository](https://github.com/SGFGOV/medusa-strapi-repo).
|
||||
|
||||
</Note>
|
||||
|
||||
## Features
|
||||
|
||||
[Strapi](https://strapi.io/) is an open source headless CMS service that allows developers to have complete control over their content models. It can be integrated into many other frameworks, including Medusa.
|
||||
|
||||
By integrating Strapi into Medusa, you can benefit from powerful features in your ecommerce store, such as:
|
||||
|
||||
- Rich CMS details for product.
|
||||
- Easy-to-use interface to manage content for static content and pages.
|
||||
- Localization for product and storefront content.
|
||||
- Two-way sync between Strapi and Medusa.
|
||||
|
||||
---
|
||||
|
||||
## Preparations
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- A [PostgreSQL database](https://www.postgresql.org/docs/current/sql-createdatabase.html) for Strapi.
|
||||
|
||||
</Note>
|
||||
|
||||
In this section, you’ll setup a Strapi project with a Medusa plugin installed. To do that:
|
||||
|
||||
1. Clone the Strapi project repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/SGFGOV/medusa-strapi-repo.git
|
||||
```
|
||||
|
||||
2. Change to the `medusa-strapi-repo/packages/medusa-strapi` directory.
|
||||
3. Copy the `.env.test` file to a new `.env` file.
|
||||
|
||||
### Change Strapi Environment Variables
|
||||
|
||||
In the `.env` file, change the following environment variables:
|
||||
|
||||
```bash
|
||||
# IMPORTANT: Change supersecret with random and unique strings
|
||||
APP_KEYS=supersecret
|
||||
API_TOKEN_SALT=supersecret
|
||||
ADMIN_JWT_SECRET=supersecret
|
||||
JWT_SECRET=supersecret
|
||||
|
||||
MEDUSA_STRAPI_SECRET=supersecret
|
||||
|
||||
MEDUSA_BACKEND_URL=http://localhost:9000
|
||||
MEDUSA_BACKEND_ADMIN=http://localhost:7001
|
||||
|
||||
SUPERUSER_EMAIL=support@medusa-commerce.com
|
||||
SUPERUSER_USERNAME=SuperUser
|
||||
SUPERUSER_PASSWORD=MedusaStrapi1
|
||||
|
||||
DATABASE_HOST=localhost
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_NAME=postgres_strapi
|
||||
DATABASE_USERNAME=postgres
|
||||
DATABASE_PASSWORD=
|
||||
DATABASE_SSL=false
|
||||
DATABASE_SCHEMA=public
|
||||
```
|
||||
|
||||
1. Change `APP_KEYS`, `API_TOKEN_SALT`, `JWT_SECRET`, and `ADMIN_JWT_SECRET` to a random and unique string. These keys are used by Strapi to sign session cookies, generate API tokens, and more.
|
||||
2. Change `MEDUSA_STRAPI_SECRET` to a random unique string. The value of this environment variable is used later in your Medusa configurations.
|
||||
3. Change `MEDUSA_BACKEND_URL` to the URL of your Medusa backend. If you’re running it locally, it should be `http://localhost:9000`.
|
||||
4. Change `MEDUSA_BACKEND_ADMIN` to the URL of your Medusa Admin. If you’re running it locally, it should be `http://localhost:7001`.
|
||||
5. Change the following environment variables to define the Strapi super user:
|
||||
1. `SUPERUSER_EMAIL`: the super user’s email. By default, it’s `support@medusa-commerce.com`.
|
||||
2. `SUPERUSER_USERNAME`: the super user’s username. By default, it’s `SuperUser`.
|
||||
3. `SUPERUSER_PASSWORD`: the super user’s password. By default, it’s `MedusaStrapi1`.
|
||||
4. `SUPERUSER_FIRSTNAME`: the super user’s first name. By default, it’s `Medusa`.
|
||||
5. `SUPERUSER_LASTNAME`: the super user’s last name. By default, it’s `Commerce`.
|
||||
6. Change the database environment variables based on your database configurations. All database environment variables start with `DATABASE_`.
|
||||
7. You can optionally configure other services, such as S3 or MeiliSearch, as explained [here](https://github.com/SGFGOV/medusa-strapi-repo/tree/development/packages/medusa-strapi#media-bucket).
|
||||
|
||||
### Build Packages
|
||||
|
||||
Once you’re done, install and build packages in the root `medusa-strapi-repo` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
# Install packages
|
||||
npm install
|
||||
# Build packages
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Install the Strapi Plugin in Medusa
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- An Event Module installed in the Medusa application, such as the [Redis Event Module](../../../architectural-modules/event/redis/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Strapi plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-strapi-ts
|
||||
```
|
||||
|
||||
Next, add the plugin to the `plugins` array in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "medusa-plugin-strapi-ts",
|
||||
options: {
|
||||
strapi_protocol: process.env.STRAPI_PROTOCOL,
|
||||
strapi_host: process.env.STRAPI_SERVER_HOSTNAME,
|
||||
strapi_port: process.env.STRAPI_PORT,
|
||||
strapi_secret: process.env.STRAPI_SECRET,
|
||||
strapi_default_user: {
|
||||
username: process.env.STRAPI_MEDUSA_USER,
|
||||
password: process.env.STRAPI_MEDUSA_PASSWORD,
|
||||
email: process.env.STRAPI_MEDUSA_EMAIL,
|
||||
confirmed: true,
|
||||
blocked: false,
|
||||
provider: "local",
|
||||
},
|
||||
strapi_admin: {
|
||||
username: process.env.STRAPI_SUPER_USERNAME,
|
||||
password: process.env.STRAPI_SUPER_PASSWORD,
|
||||
email: process.env.STRAPI_SUPER_USER_EMAIL,
|
||||
},
|
||||
auto_start: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Strapi Plugin Options
|
||||
|
||||
1. `strapi_protocol`: The protocol of the Strapi server. If running locally, it should be `http`. Otherwise, it should be `https`.
|
||||
2. `strapi_host`: the domain of the Strapi server. If running locally, use `127.0.0.1`.
|
||||
3. `strapi_port`: the port that the Strapi server is running on, if any. If running locally, use `1337`.
|
||||
4. `strapi_secret`: the same secret used for the `MEDUSA_STRAPI_SECRET` environment variable in the Strapi project.
|
||||
5. `strapi_default_user`: The details of an existing user or a user to create in the Strapi backend that is used to update data in Strapi. It’s an object accepting the following properties:
|
||||
1. `username`: The user’s username.
|
||||
2. `password`: The user’s password.
|
||||
3. `email`: The user’s email.
|
||||
4. `confirmed`: Whether the user is confirmed.
|
||||
5. `blocked`: Whether the user is blocked.
|
||||
6. `provider`: The name of the authentication provider.
|
||||
6. `strapi_admin`: The details of the super admin. The super admin is only used to create the default user if it doesn’t exist. It’s an object accepting the following properties:
|
||||
1. `username`: the super admin’s username. Its value is the same as that of the `SUPERUSER_USERNAME` environment variable in the Strapi project.
|
||||
2. `password`: the super admin’s password. Its value is the same as that of the `SUPERUSER_PASSWORD` environment variable in the Strapi project.
|
||||
3. `email`: the super admin’s email. Its value is the same as that of the `SUPERUSER_EMAIL` environment variable in the Strapi project.
|
||||
7. `auto_start`: Whether to initialize the Strapi connection when Medusa starts. Disabling this may cause issues when syncing data from Medusa to Strapi.
|
||||
|
||||
Refer to the [plugin’s README](https://github.com/SGFGOV/medusa-strapi-repo/blob/development/packages/medusa-plugin-strapi-ts/README.md) for more options.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
STRAPI_PROTOCOL=http
|
||||
STRAPI_SERVER_HOSTNAME=127.0.0.1
|
||||
STRAPI_PORT=1337
|
||||
STRAPI_SECRET=supersecret
|
||||
|
||||
STRAPI_MEDUSA_USER=medusa
|
||||
STRAPI_MEDUSA_PASSWORD=supersecret
|
||||
STRAPI_MEDUSA_EMAIL=admin@medusa-test.com
|
||||
|
||||
STRAPI_SUPER_USERNAME=SuperUser
|
||||
STRAPI_SUPER_PASSWORD=MedusaStrapi1
|
||||
STRAPI_SUPER_USER_EMAIL=support@medusa-commerce.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the integration between Medusa and Strapi, first, start the Strapi server by running the following command in the `medusa-strapi-repo/packages/medusa-strapi` directory:
|
||||
|
||||
```bash title="medusa-strapi-repo/packages/medusa-strapi" npm2yarn
|
||||
npm run develop
|
||||
```
|
||||
|
||||
Then, start the Medusa application:
|
||||
|
||||
```bash title="Medusa Backend" npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
If the connection to Strapi is successful, you’ll find the following message logged in your Medusa application with no errors:
|
||||
|
||||
```bash
|
||||
info: Checking Strapi Health ,data:
|
||||
debug: check-url: http://127.0.0.1:1337/_health ,data:
|
||||
info: Strapi Subscriber Initialized
|
||||
```
|
||||
|
||||
### Synced Entities
|
||||
|
||||
The Medusa and Strapi plugins support syncing the following Medusa data models:
|
||||
|
||||
- `Region`
|
||||
- `Product`
|
||||
- `ProductVariant`
|
||||
- `ProductCollection`
|
||||
- `ProductCategory`
|
||||
|
||||
### Two-Way Syncing
|
||||
|
||||
To test syncing data from Medusa to Strapi, try creating or updating a product either using the Medusa Admin or the [REST APIs](https://docs.medusajs.com/api/admin#products_postproducts). This triggers the associated event in Medusa, which makes the updates in Strapi.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Data is only synced to Strapi once you create or update them. So, if you have products in your Medusa application from before integrating Strapi, they won’t be available by default in Strapi. You’ll have to make updates to them, which triggers the update in Strapi.
|
||||
|
||||
</Note>
|
||||
|
||||
To test syncing data from Strapi to Medusa, try updating one of the products in the Strapi dashboard. If you check the product’s details in Medusa, they’re updated as expected.
|
||||
Reference in New Issue
Block a user