How to Schedule and Publish Content in EmDash CMS

How to Schedule and Publish Content in EmDash CMS

Scheduling in EmDash looks simple from the admin panel — pick a date, save — but there are two details worth knowing upfront: scheduling has to be explicitly enabled per collection, and Cloudflare Workers deployments need an extra piece of configuration or scheduled posts just never go live.

Table of Contents
  1. Scheduling Is an Opt-In Collection Feature
  2. Scheduling From the Admin Panel
  3. Scheduling via CLI or REST API
  4. The Cloudflare Workers Gotcha
  5. Node.js Deployments
  6. Verifying Scheduling Actually Works After Deploy
  7. Frequently Asked Questions
  8. Can I schedule content to unpublish at a future date too?
  9. What happens if I edit a scheduled post before it publishes?
  10. Does the AI assistant respect the same scheduling rules?
  11. Is there a way to see all currently scheduled content at once?
  12. The Bottom Line

Scheduling Is an Opt-In Collection Feature

Collections declare which features they support, and scheduling is one of them, alongside drafts, revisions, and preview:

{
  slug: "posts",
  label: "Blog Posts",
  labelSingular: "Post",
  supports: ["drafts", "revisions", "preview", "scheduling"]
}
emdashkits.com

If a collection was created without `scheduling` in its `supports` array, the scheduling option simply won't be available for that content type in the admin — worth checking first if you don't see a Schedule option where you expect one.

Scheduling From the Admin Panel

  1. Open the content item you want to schedule.
  2. Instead of Publish, choose the schedule option and pick a future date and time.
  3. Save — the item's status becomes scheduled, distinct from draft or published.

It publishes automatically at the specified time with no further action needed — assuming the publish mechanism is actually running, which is the Cloudflare caveat below.

Read also:

Scheduling via CLI or REST API

npx emdash content schedule posts 01ABC123 --at 2026-08-01T09:00:00Z
emdashkits.com
POST /_emdash/api/content/posts/01ABC123/schedule
Content-Type: application/json

{ "scheduledAt": "2026-08-01T09:00:00Z" }
emdashkits.com

Both accept an ISO 8601 datetime. Canceling a schedule reverts the item to its prior status without touching the content itself — the CLI equivalent is `npx emdash content unschedule`, or ask the AI assistant to "cancel the schedule on the launch post."

The Cloudflare Workers Gotcha

On Cloudflare Workers, scheduled publishing, plugin cron, and maintenance tasks run from a Worker Cron Trigger. Without the Cron Trigger, content scheduled in the admin panel does not publish on Cloudflare Workers, and plugin cron jobs do not run. Local astro dev still uses the in-process scheduler.

This is a real, easy-to-miss trap: scheduling a post works fine in local dev (which uses an in-process scheduler), so it's easy to assume it'll work the same way in production — and on Cloudflare Workers specifically, it silently won't, unless you've added a Cron Trigger. New Cloudflare templates include this automatically; if you're adding it to an existing project, you need two things:

  1. Export the EmDash Worker entry so the cron handler exists:
// src/worker.ts
export { default, PluginBridge } from "@emdash-cms/cloudflare/worker";
emdashkits.com
  1. Add a Cron Trigger to your Wrangler config:
{
  "triggers": {
    "crons": ["* * * * *"]
  }
}
emdashkits.com

That trigger runs every minute, which is the right granularity for scheduled publishing — anything coarser and a post scheduled for a specific minute could sit unpublished for longer than expected.

Node.js Deployments

Node.js deployments don't have this gotcha — the in-process scheduler used in local dev is also what runs in a standard Node.js server deployment, so scheduled content publishes without any additional cron configuration.

Verifying Scheduling Actually Works After Deploy

  1. Schedule a test post a few minutes in the future.
  2. Wait for the scheduled time to pass.
  3. Check whether it actually went live — if it's still showing as "scheduled" past its time on a Cloudflare deployment, the Cron Trigger is the first thing to check.

Frequently Asked Questions

Can I schedule content to unpublish at a future date too?

Scheduling as documented covers publishing a draft at a future time. For unpublishing on a schedule, that's not a documented built-in feature — you'd need a plugin using a `cron` hook to check dates and call `content:afterUnpublish`-equivalent logic yourself.

What happens if I edit a scheduled post before it publishes?

Edits are saved to the draft; the scheduled publish time is unaffected unless you explicitly change or cancel it. The version that goes live at the scheduled time is whatever the draft looks like at that moment.

Does the AI assistant respect the same scheduling rules?

Yes — "Schedule the announcement for June 1st at 9am" and "Cancel the schedule on the launch post" are real, documented MCP-driven commands, subject to the same collection-support and Cloudflare Cron Trigger requirements as scheduling through the admin UI.

Is there a way to see all currently scheduled content at once?

List content filtered by status: `npx emdash content list posts --status scheduled`, or the equivalent REST API call with `?status=scheduled` — useful for an editorial calendar view before it goes live.

The Bottom Line

Scheduling works reliably once two things are true: the collection has `scheduling` in its `supports` array, and — specifically on Cloudflare Workers — a Cron Trigger is configured so the scheduled publish actually fires. Skip either and content silently sits unpublished past its scheduled time with no error to alert you. Test a real scheduled post after any production deploy to confirm both pieces are in place.

Share

Comments

Write a comment

Related Articles

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

July 16, 2026

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

July 16, 2026

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

TL;DR — A custom session-cookie login flow appeared to succeed on localhost (the OTP verified, the response looked fine) but every subsequent request to a login-gated page treated the visitor as logged out. Identical code worked fine on the live HTTPS site. The cookie's Secure attribute was hardcoded to true — and per the cookie spec, browsers never store or send a Secure cookie over a plain, non-HTTPS connection.

Table of Contents
  1. Diagnostic
  2. Root cause
  3. Fix
  4. Lessons learned

Diagnostic

Check the actual Set-Cookie response header and the browser's own cookie storage panel — on localhost over http://, the cookie is sent by the server but never actually stored by the browser.

Root cause

// before -- assumes the app is always served over HTTPS
setCookie("session", token, { secure: true, httpOnly: true });
emdashkits.com

A cookie config that quietly assumes "we're always on HTTPS" breaks the instant you test over plain HTTP, which local dev servers commonly are.

Read also:

Fix

// after -- derive secure from the actual request protocol
const isHttps = request.url.startsWith("https://");
setCookie("session", token, { secure: isHttps, httpOnly: true });
emdashkits.com

Lessons learned

  • Any Secure-flagged cookie needs to key off the real request scheme, not an assumption baked in once at cookie-creation time.
  • "Works in production, silently fails in local dev" is a strong signal to check cookie flags before anything else in an auth flow.
  • Check other cookies in the same codebase for the same hardcoded assumption — if one cookie has this bug, sibling cookies set the same way are worth auditing too.
Share
Previous Article

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Comments

Write a comment

Related Articles

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

July 16, 2026

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

July 16, 2026

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

TL;DR — GA4's gtag.js snippet was installed correctly, the page loaded with no visible JavaScript error, and GA4's real-time report still showed zero activity. The CMS's default Content-Security-Policy had no allowance for analytics domains and no config option to add one — so every request to Google's tracking endpoints was blocked at the browser level before it could fail loudly.

Table of Contents
  1. Diagnostic
  2. Root cause
  3. Lessons learned

Diagnostic

Check the browser's dedicated CSP violation reporting, not the regular console error list — CSP blocks are reported through their own channel, not thrown as normal script errors, so "no console errors" doesn't mean nothing was blocked.

Root cause

The CSP's script-src and connect-src directives had no entry for googletagmanager.com or google-analytics.com, and the CMS exposed no configuration surface to add one — the only way in was patching the CSP directives directly.

// patch-package: add analytics domains to the existing CSP directives
scriptSrc.push("https://www.googletagmanager.com");
connectSrc.push("https://www.google-analytics.com", "https://www.googletagmanager.com");
emdashkits.com
Read also:

Lessons learned

  • "No console errors" is not proof nothing was blocked — CSP violations live in their own reporting surface and are easy to miss if you're only scanning for red error text.
  • Before adding any third-party script tag to a site with a CSP already in place, check the CSP's directives first rather than assuming a silently-empty analytics dashboard means a snippet-installation mistake.
Share
Previous Article

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Next Article

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Comments

Write a comment

Related Articles

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

July 16, 2026

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

July 16, 2026

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)