Back to Blog
A person writing notes on a desk calendar in an office setting
Tutorial
August 17, 2026
11 min read

How to Build a Content Publishing Calendar for a CMS

By SimpleCalendarJS Team

SimpleCalendarJS~18 KB gzipped · Zero dependencies · Any framework

Every CMS eventually needs a content publishing calendar. Whether you're running a WordPress multisite, a headless CMS like Strapi or Sanity, or a custom-built admin panel — the moment your team publishes more than a few posts per week, spreadsheets and status columns stop scaling. You need a visual calendar that shows what's going out, when, and what's still in draft.

The SaaS vs. custom calendar decision

The first instinct is to reach for a SaaS editorial calendar tool. CoSchedule starts at $29/user/month ($279/month for the full Marketing Calendar). Narrato and StoryChief offer similar features at comparable price points. These tools work well for marketing teams who need built-in social publishing, approval workflows, and multi-channel distribution.

But for developers building or maintaining a CMS, SaaS editorial calendars create problems:

ConcernSaaS toolCustom calendar
Data ownershipContent metadata lives in a third partyEverything stays in your CMS database
Integration depthWebhooks and API sync (often fragile)Direct database or API queries
CustomisationLimited to what the vendor exposesFull control over UI and behaviour
Cost at scale$29–$279/user/month, grows with teamOne-time library cost or free
Framework fitStandalone app, separate loginEmbedded in your existing admin UI

If your CMS already stores posts with a status field (draft, scheduled, published) and a publishedAt timestamp, you already have everything a calendar needs. The missing piece is the visual layer — a calendar component that renders your content pipeline on a timeline.

Choosing a calendar library for a CMS

Most JavaScript calendar libraries were built for event scheduling, not content planning. But the data model is identical: a title, a start date, an optional end date, and a colour. Here's how the options compare for CMS embedding:

LibraryBundle Size (gzip)LicenseDrag & DropCMS Fit
FullCalendar~500 KB (full)Free core / paid premiumYes (premium)Heavy for an admin panel widget
DayPilot Pro~100 KB+Per-developer ($849+)YesGood features, commercial license required
DayPilot Lite~50 KBApache 2.0LimitedFree but missing key features
React Big Calendar~50 KB (+ React)MITCommunity pluginReact-only, no vanilla JS
CalendarJS~40 KBMITYesLimited ecosystem
SimpleCalendarJS~18 KBFree personal / license req. commercialYesLightweight, framework-agnostic

For a CMS admin panel, the priority is small footprint and fast load. Admin pages already carry their own framework, rich text editors, media uploaders, and navigation. Adding 500 KB for FullCalendar — or paying $849+ per developer for DayPilot Pro — doesn't make sense when the calendar's job is to show posts on a timeline and let editors drag them to reschedule.

Building the publishing calendar

Step 1: Define the content-to-event mapping

Your CMS stores posts. The calendar displays events. The mapping is straightforward:

function postsToCalendarEvents(posts) { const statusColors = { draft: '#f59e0b', scheduled: '#3b82f6', published: '#10b981', review: '#8b5cf6', }; return posts.map((post) => ({ id: post.id, title: post.title, start: new Date(post.publishedAt || post.createdAt), end: new Date(post.publishedAt || post.createdAt), color: statusColors[post.status] || '#6b7280', })); }

Draft posts show in amber, scheduled posts in blue, published posts in green, and posts under review in purple. Editors can scan the calendar and immediately see the pipeline's health — too many amber dots means the review queue is backed up.

Step 2: Mount the calendar in your admin panel

npm install simple-calendar-js
import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/dist/simple-calendar-js.min.css'; const calendar = new SimpleCalendarJs('#editorial-calendar', { defaultView: 'month', locale: 'en-US', enabledViews: ['month', 'week'], fetchEvents: async (start, end) => { const res = await fetch( `/api/cms/posts?from=${start.toISOString()}&to=${end.toISOString()}` ); const posts = await res.json(); return postsToCalendarEvents(posts); }, onEventClick: (event) => { window.location.href = `/admin/posts/${event.id}/edit`; }, onSlotClick: (date) => { window.location.href = `/admin/posts/new?publishAt=${date.toISOString()}`; }, });

Clicking a post on the calendar opens the editor. Clicking an empty date slot creates a new draft pre-filled with that publish date. The fetchEvents callback runs on every navigation, so the calendar always reflects the latest content pipeline state.

Step 3: Add the API endpoint

Your CMS needs an endpoint that returns posts within a date range. Here's a minimal example for a Node.js backend with a SQL database:

app.get('/api/cms/posts', async (req, res) => { const { from, to } = req.query; const posts = await db.query( `SELECT id, title, status, published_at, created_at FROM posts WHERE (published_at BETWEEN $1 AND $2) OR (published_at IS NULL AND created_at BETWEEN $1 AND $2) ORDER BY COALESCE(published_at, created_at)`, [from, to] ); res.json( posts.rows.map((row) => ({ id: row.id, title: row.title, status: row.status, publishedAt: row.published_at, createdAt: row.created_at, })) ); });

For headless CMS platforms like Strapi or Sanity, replace the SQL query with the platform's content API. Strapi uses REST or GraphQL; Sanity uses GROQ. The calendar doesn't care where the data comes from — it only needs an array of events with title, start, and color.

Step 4: Integrate with WordPress

WordPress has its own post scheduling system, but the built-in calendar view is limited to a list in the Posts screen. To add a visual editorial calendar to the WordPress admin:

function editorial_calendar_admin_page() { add_menu_page( 'Editorial Calendar', 'Calendar', 'edit_posts', 'editorial-calendar', 'render_editorial_calendar', 'dashicons-calendar-alt', 5 ); } add_action('admin_menu', 'editorial_calendar_admin_page'); function render_editorial_calendar() { wp_enqueue_script( 'simple-calendar-js', plugins_url('dist/simple-calendar-js.min.js', __FILE__), [], '1.0.0', true ); wp_enqueue_style( 'simple-calendar-css', plugins_url('dist/simple-calendar-js.min.css', __FILE__) ); echo '<div id="editorial-calendar" style="max-width:960px;margin:20px auto;"></div>'; }

Then initialise the calendar with the WordPress REST API:

const calendar = new SimpleCalendarJs('#editorial-calendar', { defaultView: 'month', fetchEvents: async (start, end) => { const res = await fetch( `/wp-json/wp/v2/posts?after=${start.toISOString()}&before=${end.toISOString()}&status=draft,pending,future,publish&per_page=100`, { headers: { 'X-WP-Nonce': wpApiSettings.nonce } } ); const posts = await res.json(); return posts.map((p) => ({ id: p.id, title: p.title.rendered, start: new Date(p.date), color: p.status === 'publish' ? '#10b981' : p.status === 'future' ? '#3b82f6' : '#f59e0b', })); }, onEventClick: (event) => { window.location.href = `/wp-admin/post.php?post=${event.id}&action=edit`; }, });

This gives WordPress editors a month-view calendar showing all posts — drafts, scheduled, and published — without installing a third-party plugin that adds its own database tables, telemetry, or upsells.

Step 5: Theme the calendar for your admin panel

CMS admin panels have their own visual language. The calendar should match it, not clash. SimpleCalendarJS uses CSS custom properties:

#editorial-calendar .uc-calendar { --uc-primary: #2563eb; --uc-bg: #ffffff; --uc-border: #e2e8f0; --uc-text: #1e293b; --uc-radius: 6px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } @media (prefers-color-scheme: dark) { #editorial-calendar .uc-calendar { --uc-bg: #1a1a2e; --uc-border: #2d2d44; --uc-text: #e2e8f0; } }

For WordPress, match the admin colour scheme. For Strapi or a custom React admin, pull values from your existing design tokens.

When SaaS editorial tools make more sense

Building a custom content calendar makes sense when your team is developer-led and your CMS already manages the content pipeline. But SaaS tools earn their cost in specific situations:

  • Non-technical marketing teams who need drag-and-drop social scheduling, approval workflows, and direct publishing to Twitter, LinkedIn, and Facebook — CoSchedule ($29+/user/month) or StoryChief handles this without code
  • Multi-channel content operations where blog posts, emails, social posts, and ads all need to appear on one timeline — purpose-built tools like Narrato or DivvyHQ coordinate across channels
  • Enterprise publishers with dozens of editors, compliance requirements, and audit trails — platforms like Kordiam (formerly Desk-Net) are built for newsrooms

If your need is simpler — see what's publishing this week, reschedule a draft by clicking a date, colour-code posts by status — a ~18 KB calendar library mounted in your existing admin panel does the job at a fraction of the cost and complexity.

Summary

  • SaaS editorial calendar tools like CoSchedule ($29+/user/month) and StoryChief work for marketing teams but add cost, vendor dependency, and integration friction for developer-led CMS setups
  • Your CMS already stores the data a calendar needs — post title, status, and publish date — the missing piece is the visual layer
  • FullCalendar (~500 KB) and DayPilot Pro ($849+/dev) are overkill for an admin panel widget that shows content on a timeline
  • SimpleCalendarJS adds a full month/week calendar at ~18 KB gzipped — map post statuses to colours, wire up fetchEvents to your CMS API, and editors get a scannable content pipeline
  • Works with WordPress (via the REST API), headless CMS platforms like Strapi and Sanity (via their content APIs), and any custom admin panel

Sources & Further Reading

Research & References

Image Credits

All images free to use under the Pexels License.

Frequently Asked Questions

What is a content publishing calendar?

A content publishing calendar (also called an editorial calendar) is a visual schedule that shows what content will be published, when, and on which channel. It helps content teams plan blog posts, social updates, newsletters, and other content in a single timeline view.

Should I build a custom content calendar or use a SaaS tool like CoSchedule?

If your CMS already has a content pipeline and you want full control over the UI and data, building a custom calendar with a JavaScript library is cheaper and more flexible. SaaS tools like CoSchedule ($29+/user/month) make sense for non-technical marketing teams who need built-in social publishing and approval workflows.

Can I use SimpleCalendarJS inside a CMS admin panel?

Yes. SimpleCalendarJS is framework-agnostic and mounts to any DOM element. It works inside WordPress admin pages, headless CMS dashboards built with React or Vue, and custom admin panels — anywhere you can render HTML and load JavaScript.

How do I display draft and scheduled posts on a calendar?

Query your CMS for posts with status 'draft', 'scheduled', or 'published', then map each post to a calendar event with a title, date, and colour based on its status. Pass these events to the calendar's fetchEvents callback so the view updates as dates change.

What JavaScript calendar library is best for a CMS editorial calendar?

It depends on your requirements. FullCalendar (~500 KB full bundle) has the most features but adds significant weight. DayPilot Pro requires per-developer licensing. SimpleCalendarJS (~18 KB gzipped) is the lightest option with month, week, and day views — ideal for embedding in a CMS admin panel without bloating the backend.

Add a calendar to your app today

Free for personal projects. $49/year or $199 lifetime per commercial project.