@pkrakas/medusa-v2-localized-slugs
v0.1.6
Published
Medusa's v2 plugin which adds localized slugs support.
Maintainers
Readme
@pkrakas/medusa-v2-localized-slugs
Medusa v2 plugin that adds per-locale URL slugs for products. Store each product's slug per locale (e.g. en-US → cordless-drill-yz500, lt-LT → akumuliatorinis-suktuvas-yz500) and resolve products by slug on the storefront.
Works alongside Medusa's Translation module for translated product content.
Features
- Custom module (
localized_slugs) with unique constraints on(product_id, locale)and(locale, slug) - Admin widget on the product detail page to view and edit slugs per store locale
- Admin API to upsert slugs in bulk
- Store API to fetch a product by localized slug + locale
- Product link — query products with
+localized_slugs.*or traverselocalized_slugs → product - Upsert semantics — create, update, or delete (empty slug removes the row)
Requirements
- Node.js >= 20
- Medusa v2.15.x (
@medusajs/medusa,@medusajs/framework, etc.) - Translation module enabled (recommended for locale-aware storefronts)
Installation
1. Add the plugin to your Medusa backend
npm install @pkrakas/medusa-v2-localized-slugs2. Register in medusa-config.ts
import { defineConfig } from "@medusajs/framework/utils"
module.exports = defineConfig({
modules: [
{
resolve: "@medusajs/medusa/translation",
},
],
plugins: [
{
resolve: "@pkrakas/medusa-v2-localized-slugs",
options: {},
},
],
featureFlags: {
translation: true,
},
})3. Run migrations
From your Medusa backend app:
npx medusa db:migrate4. Enable locales in Admin
Under Settings → Translations, add the locales your store supports. The admin widget reads supported_locales from the store and shows one slug input per locale.
Data model
| Field | Description |
|-------------|--------------------------------------------|
| id | Primary key |
| product_id| Linked product ID |
| locale | BCP 47 locale code (e.g. en-US, lt-LT) |
| slug | URL-safe slug unique per locale |
Indexes:
- Unique
(product_id, locale)— one slug per product per locale - Unique
(locale, slug)— no duplicate slugs within a locale
Admin UI

A Slugs widget appears on the product detail page (product.details.before zone). It:
- Lists current slugs per locale
- Opens a drawer with inputs for each store-supported locale
- Saves via the admin API below
Clearing a slug (empty string) deletes that locale's slug row.
API
Admin — upsert slugs
POST /admin/products/:id/localized-slugs
Content-Type: application/jsonBody — array of { locale, slug } (product ID comes from the URL):
[
{ "locale": "en-US", "slug": "cordless-drill-yz500" },
{ "locale": "lt-LT", "slug": "akumuliatorinis-suktuvas-yz500" },
{ "locale": "pl-PL", "slug": "" }
]| slug value | Behavior |
|--------------|------------------------------------|
| non-empty | Create or update slug for locale |
| "" | Delete slug for locale (if exists) |
Response: array of created/updated slug records.
Store — resolve product by slug
GET /store/products/slug/:slug?locale=lt-LTOr send the locale via header:
GET /store/products/slug/:slug
x-medusa-locale: lt-LTreq.locale is set by Medusa's applyLocale middleware from the query param or header.
Response: localized slug record with nested product (images, variants, options, etc.).
Errors:
| Status | Reason |
|--------|-------------------------|
| 400 | Missing locale or slug |
| 404 | No matching slug for locale |
Query products with slugs (Admin / custom routes)
const { data } = await query.graph({
entity: "product",
fields: ["id", "title", "+localized_slugs.*"],
filters: { id: productId },
})Storefront example
There is no built-in sdk.store.product.retrieveBySlug. Use client.fetch:
import Medusa from "@medusajs/js-sdk"
const sdk = new Medusa({
baseUrl: process.env.MEDUSA_BACKEND_URL,
publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
})
const result = await sdk.client.fetch(
`/store/products/slug/${encodeURIComponent(slug)}`,
{
method: "GET",
headers: { "x-medusa-locale": locale },
}
)Route your PDP at /[countryCode]/products/[slug] (or similar) and pass the same locale you use for translations.
Locale codes
Use the same locale codes as your store's supported locales and Translation module (BCP 47, e.g. lt-LT). Slug rows are filtered by exact locale match — lt and lt-LT are different keys.
License
MIT
Author
pkrakas
