medusa-blog-management
v0.0.12
Published
A starter for Medusa plugins.
Maintainers
Readme
medusa-blog-management
Medusa v2 plugin that adds a blog module with admin CRUD, storefront read APIs, and optional storefront helper utilities.
Plugin Overview
medusa-blog-management adds blog content management capabilities to a Medusa backend.
Implemented features in code:
- Custom
blogmodule with ablog_postmodel and migration. - Admin APIs for full blog post CRUD.
- Store APIs for published blog listing/detail.
- Store detail route increments
read_count. - Admin extension routes/pages for listing, creating, editing, and viewing posts.
- Tiptap-based rich-text editor component for admin authoring.
- Helper utilities for storefront integration (
getBlogList,getBlogPost, metadata generator).
It solves the need to run editorial/blog content in the same Medusa system as commerce data.
Medusa Version
- Built for Medusa v2 (
@medusajs/framework/@medusajs/medusa2.12.4).
Installation & Setup
1) Install plugin
npm install medusa-blog-managementyarn add medusa-blog-management2) Register module + plugin in medusa-config.ts
import { defineConfig } from "@medusajs/framework/utils"
export default defineConfig({
modules: {
blog: {
resolve: "medusa-blog-management",
},
},
plugins: [
{
resolve: "medusa-blog-management",
},
],
})⚠️ Note: The code exports the module from
src/index.tsand includes admin route extensions; in practice, projects typically keep module and plugin registration aligned as shown above.
3) Run migrations
npx medusa db:migrateThe plugin includes migration Migration20260112153000 to create blog_post.
Configuration (config.ts / plugin options)
No plugin options/config schema is implemented in source code. The plugin is registered by resolve only.
| Option | Type | Required | Default | Description | |---|---|---|---|---| | None implemented | - | - | - | No runtime plugin options are defined in code. |
Example:
{
resolve: "medusa-blog-management"
}Environment Variables
No process.env.* usage exists in implemented source files under src.
| Variable | Purpose | Required | Example | |---|---|---|---| | None in implementation | - | - | - |
REST APIs / Routes
Admin blog routes
1) GET /admin/blog-posts
Returns paginated list of blog posts.
- Auth: Admin route context
- Query params:
| Param | Type | Required | Notes |
|---|---|---|---|
| skip | number | No | Default 0, must be >= 0. |
| take | number | No | Default 20, range 1..100. |
- Response:
{ posts, count, limit, offset }
2) POST /admin/blog-posts
Creates a blog post (validated by Zod CreateBlogPostSchema).
- Auth: Admin route context
- Body schema:
| Field | Type | Required | Notes |
|---|---|---|---|
| title | string | Yes | 1..500 chars. |
| handle | string | No | Auto-generated from title if omitted. |
| status | "draft" \| "published" | No | Default "draft". |
| thumbnail | string \| null | No | URL validation when provided. |
| short_description | string \| null | No | Max 1000 chars. |
| read_time | number | No | Integer, >= 0, default 0. |
| tags | string[] \| null | No | Stored in JSON field. |
| body | string \| null | No | Rich text HTML. |
| created_by | string \| null | No | Max 255 chars. |
| metadata | Record<string, unknown> \| null | No | Arbitrary metadata. |
- Response:
{ post }
3) GET /admin/blog-posts/:id
Returns single blog post by id.
- Auth: Admin route context
- Response:
{ post }
4) POST /admin/blog-posts/:id
Updates blog post by id (validated by UpdateBlogPostSchema).
- Auth: Admin route context
- Body: all create fields optional (with same validation constraints).
- Response:
{ post }
5) DELETE /admin/blog-posts/:id
Deletes blog post by id.
- Auth: Admin route context
- Response:
{ id, object: "blog_post", deleted: true }
Store blog routes
6) GET /store/blog-posts
Returns paginated published posts only.
- Auth: Public store route
- Query params:
skip(default0),take(default10,1..100) - Response:
{ posts, count, limit, offset }
7) GET /store/blog-posts/:id
Returns a published post by id.
- Auth: Public store route
- Behavior: asynchronously increments
read_countviaincrementReadCount. - Response:
{ post }
Health routes
8) GET /admin/plugin
Simple plugin health endpoint, returns 200.
9) GET /store/plugin
Simple plugin health endpoint, returns 200.
cURL examples
curl -X GET "http://localhost:9000/admin/blog-posts?skip=0&take=20" \
-H "Authorization: Bearer <ADMIN_TOKEN>"curl -X POST "http://localhost:9000/admin/blog-posts" \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"title": "How to launch with Medusa",
"status": "draft",
"short_description": "Launch checklist for your store",
"body": "<p>Content...</p>",
"tags": ["launch", "guide"],
"created_by": "Team"
}'curl -X GET "http://localhost:9000/store/blog-posts?skip=0&take=10"curl -X GET "http://localhost:9000/store/blog-posts/<post_id>"Services
BlogModuleService
Location: src/modules/blog/service.ts
Extends MedusaService({ Post }) and provides generated repository/service operations for Post.
Custom method implemented:
| Method | Signature | Description |
|---|---|---|
| incrementReadCount | (id: string) => Promise<void> | Retrieves post and increments read_count by 1. Used in store detail route. |
Common generated methods used by routes (from MedusaService model integration):
listAndCountPostscreatePostsretrievePostupdatePostsdeletePosts
Workflows & Steps (Medusa v2)
No custom workflows or steps are implemented.
Subscribers / Event Hooks
No event subscribers are implemented.
Admin UI / Widgets
The plugin includes admin route extensions (not widget-zone injection).
Admin routes
| Route | Purpose | Interaction/Data |
|---|---|---|
| /blog | Blog list table | Fetches /admin/blog-posts; supports delete; links to create/edit/detail. |
| /blog/create | Create post page | Uses BlogForm; submits to /admin/blog-posts. |
| /blog/:id | Edit post page | Fetches /admin/blog-posts/:id; submits updates via POST /admin/blog-posts/:id. |
| /blog/detail/:id | Read-only admin detail view | Fetches /admin/blog-posts/:id; displays metadata/tags/content. |
Main admin components
| Component | What it renders |
|---|---|
| BlogForm | Full authoring form (title, status, author, read time, thumbnail upload, tags, rich content). |
| RichTextEditor | Tiptap editor with bold/italic/headings/lists/image upload/YouTube embed. |
Admin media upload interaction
Both BlogForm and RichTextEditor upload images via POST /admin/uploads, then persist returned file URL in content/thumbnail fields.
Models & Entities
blog_post model
Defined in src/modules/blog/models/post.ts, with migration in Migration20260112153000.
| Field | Type | Nullable | Notes |
|---|---|---|---|
| id | id | No | Primary key. |
| title | text | No | Post title. |
| handle | text | No | Unique slug/handle. |
| status | text | No | Default "draft" (draft/published usage in code). |
| thumbnail | text | Yes | Thumbnail URL. |
| short_description | text | Yes | Summary text. |
| read_time | number | No | Default 0. |
| tags | json | Yes | Stored as array/object JSON. |
| body | text | Yes | Rich text content. |
| created_by | text | Yes | Author name. |
| read_count | number | No | Default 0. |
| metadata | json | Yes | Arbitrary metadata. |
| created_at | timestamptz | No | Migration-managed timestamp. |
| updated_at | timestamptz | No | Migration-managed timestamp. |
| deleted_at | timestamptz | Yes | Soft-delete marker. |
Indexes in migration:
- Unique index on
handlewheredeleted_at IS NULL - Index on
deleted_atwheredeleted_at IS NULL
Relationships: no explicit relations to core Medusa entities are defined.
Use Cases & Examples
Editorial blog management inside Medusa Admin
- Create/edit/publish posts from
/blogadmin routes without external CMS.
- Create/edit/publish posts from
Public storefront blog feed
- Use
GET /store/blog-poststo render a paginated list of published content.
- Use
Article detail analytics
- Use
GET /store/blog-posts/:id; each read incrementsread_count.
- Use
SEO/social metadata generation in frontend apps
- Use exported helper
getBlogMetadata(post)for OG/Twitter metadata.
- Use exported helper
Headless storefront integration
- Use
getBlogListandgetBlogPosthelper functions with base URL + publishable key transport options.
- Use
Troubleshooting
EntityManager is not available in the container
- Cause:
BlogModuleServiceconstructor requires injectedmanager. - Fix: ensure plugin/module registration is correct in
medusa-config.ts, rebuild plugin, and restart server.
Migration/table errors (blog_post not found)
- Cause: migration not applied.
- Fix: run
npx medusa db:migrateand verify migrationMigration20260112153000is included in build output.
Validation failed on create/update
- Cause: request payload does not match Zod schema (
validators.ts). - Fix: check response
errorsarray and adjust field formats (URL, lengths, enums, numeric ranges).
Store detail endpoint returns 404 for existing post
- Cause: store route returns only
status === "published". - Fix: publish the post via admin update before expecting storefront visibility.
Image upload failures in admin form/editor
- Cause:
/admin/uploadsunavailable or file upload module not configured in host app. - Fix: configure Medusa upload module/provider and verify admin upload permissions.
