{"id":98069,"date":"2026-10-10T22:14:00","date_gmt":"2026-10-11T02:14:00","guid":{"rendered":"https:\/\/overcentral.com\/en\/?p=98069"},"modified":"2026-09-29T08:16:46","modified_gmt":"2026-09-29T12:16:46","slug":"emdash-content-scheduling-automation-98069","status":"publish","type":"post","link":"https:\/\/overcentral.com\/en\/emdash-content-scheduling-automation-98069\/","title":{"rendered":"EmDash Content Scheduling: Automate Without Plugin Bloat"},"content":{"rendered":"<p>Most content scheduling systems are just delayed publishing \u2014 a calendar with a toggle. <a href=\"https:\/\/emdash.dev\" target=\"_blank\" rel=\"noopener noreferrer\" data-iacss-external=\"1\">EmDash<\/a> flips that. Its real power isn&#8217;t in a built-in scheduler (it doesn&#8217;t have one yet). It&#8217;s in the event-driven architecture that lets you build custom automation without the security nightmares plugins bring. Think of scheduling not as a date-picker, but as a chain of triggers: &#8220;When the time hits, run this hook, in this isolated sandbox, with these exact permissions.&#8221; That changes everything about how you plan publications.<\/p>\n<h2>What EmDash Offers Out of the Box<\/h2>\n<p>EmDash v0.1 ships with drafts, revisions, and a simple publish\/unpublish toggle on every post and page. You can set a future date manually \u2014 the interface shows a &#8220;Publish on&#8221; field. But there&#8217;s no recurring schedule, no bulk rescheduling, no visual calendar. That&#8217;s by design. The team focused on the foundation: content types, hooks, and the plugin sandbox. Scheduling is something you build on top.<\/p>\n<p>The key components:<\/p>\n<ul>\n<li><strong>Content types<\/strong> \u2013 Define your own post types with custom fields. A &#8220;scheduled date&#8221; field is trivial to add.<\/li>\n<li><strong>Hooks<\/strong> \u2013 Events like <code>post:published<\/code>code, <code>post:saved<\/code>code, <code>cron:tick<\/code>code that plugins can listen to.<\/li>\n<li><strong>Dynamic workers<\/strong> \u2013 Plugins run in isolated V8 isolates with declared capabilities. No database access unless you allow it.<\/li>\n<\/ul>\n<p>This means you can create a scheduling plugin that does exactly what you need \u2014 and nothing more. No bloated all-in-one plugin with 50 features you&#8217;ll never use.<\/p>\n<h2>How to Build a Custom Scheduler (Step-by-Step)<\/h2>\n<h3>1. Create a Content Type for Scheduled Posts<\/h3>\n<p>In the admin, go to Content Types \u2192 Add New. Name it &#8220;Scheduled Post&#8221; or reuse the default &#8220;Post&#8221; type. Add a field called <code>publish_date<\/code>code of type &#8220;datetime&#8221;. That field will store when the post should go live.<\/p>\n<h3>2. Set Up a Plugin with a Cron Hook<\/h3>\n<p>Create a new plugin file. Define a hook that runs on a regular interval (e.g., every minute). The plugin will check for posts where <code>publish_date<\/code>code is in the past and status is &#8220;draft&#8221;, then publish them.<\/p>\n<p>&#8220;`typescript<\/p>\n<p>import { definePlugin } from &#8217;emdash&#8217;<\/p>\n<p>export default definePlugin({<\/p>\n<p>  id: &#8216;scheduler&#8217;,<\/p>\n<p>  version: &#8216;0.1.0&#8217;,<\/p>\n<p>  capabilities: [&#8216;read:content&#8217;, &#8216;write:content&#8217;],<\/p>\n<p>  hooks: {<\/p>\n<p>    &#8216;cron:tick&#8217;: async ({ context, content }) =&gt; {<\/p>\n<p>      const now = new Date()<\/p>\n<p>      const duePosts = await content.find({<\/p>\n<p>        type: &#8216;post&#8217;,<\/p>\n<p>        status: &#8216;draft&#8217;,<\/p>\n<p>        &#8216;publish_date&#8217;: { $lte: now.toISOString() }<\/p>\n<p>      })<\/p>\n<p>      for (const post of duePosts) {<\/p>\n<p>        await content.update(post.id, { status: &#8216;publish&#8217; })<\/p>\n<p>      }<\/p>\n<p>    }<\/p>\n<p>  }<\/p>\n<p>})<\/p>\n<p>&#8220;`<\/p>\n<p>The plugin declares it needs read and write access to content. That&#8217;s all. It cannot touch media, users, or the theme. Secure by default.<\/p>\n<h3>3. Deploy the Plugin<\/h3>\n<p>If you&#8217;re on Cloudflare&#8217;s paid plan ($5\/month), the plugin runs in a dynamic worker \u2014 sandboxed, fast, and isolated. On self-hosted Node.js, it runs in-process (no sandbox yet). Either way, the logic is clean and auditable.<\/p>\n<h3>4. Schedule Your Posts<\/h3>\n<p>Write a post, set the <code>publish_date<\/code>code field to a future time, save as draft. The cron hook will pick it up within the next interval.<\/p>\n<h2>EmDash Scheduling vs WordPress Scheduling: A Comparison<\/h2>\n<table class=\"mw-table\">\n<thead>\n<tr>\n<th>Feature<\/th>\n<th>WordPress (with plugin)<\/th>\n<th>EmDash (custom plugin)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Security model<\/td>\n<td>Plugin has full DB access; one vulnerability exposes all data<\/td>\n<td>Plugin runs in sandbox with scoped permissions; cannot access database unless declared<\/td>\n<\/tr>\n<tr>\n<td>Customization<\/td>\n<td>Limited to plugin&#8217;s UI; often need multiple plugins<\/td>\n<td>Full control via code; you define the exact behavior<\/td>\n<\/tr>\n<tr>\n<td>Recurring schedules<\/td>\n<td>Requires another plugin or custom code<\/td>\n<td>Built into your plugin logic \u2014 any interval, any condition<\/td>\n<\/tr>\n<tr>\n<td>Performance<\/td>\n<td>PHP+MySQL, shared hosting bottlenecks<\/td>\n<td>Serverless V8 isolates, scales to zero, spins up in milliseconds<\/td>\n<\/tr>\n<tr>\n<td>Developer effort<\/td>\n<td>Install, configure, hope updates don&#8217;t break<\/td>\n<td>Write ~30 lines of TypeScript, deploy, maintain in version control<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The tradeoff is clear: WordPress scheduling is easier to start, but EmDash scheduling is safer, faster, and more flexible once you&#8217;re comfortable with code.<\/p>\n<h2>Real-World Use Case: Automating Newsletter Content<\/h2>\n<p>Imagine you publish a weekly newsletter every Friday at 10 AM. In WordPress, you&#8217;d need a cron plugin, a post scheduler, and maybe a separate email plugin \u2014 each with its own permissions and update cycles. In EmDash, one plugin handles everything: it checks for a custom content type &#8220;Newsletter&#8221;, publishes the draft if the date matches, and triggers an email hook (with <code>send:email<\/code>code capability). No extra plugins, no security surface area beyond what you explicitly grant.<\/p>\n<p>The same architecture works for:<\/p>\n<ul>\n<li><strong>Product launches<\/strong> \u2013 Publish a landing page on a specific date, then archive it after the sale ends.<\/li>\n<li><strong>Event registrations<\/strong> \u2013 Open registration at 9 AM, close at 5 PM, all driven by hooks.<\/li>\n<li><strong>Seasonal content<\/strong> \u2013 Rotate holiday banners on a schedule, using a <code>section<\/code>code widget that swaps based on date.<\/li>\n<\/ul>\n<h2>The Limitation You Need to Accept<\/h2>\n<p>EmDash is v0.1. The cron hook might not be fully stable yet. The playground doesn&#8217;t include it. You&#8217;ll need to run your own instance to test. And if you&#8217;re not comfortable writing TypeScript, this approach isn&#8217;t for you \u2014 at least not until a visual scheduler appears in the marketplace.<\/p>\n<p>But that&#8217;s the counterintuitive point: the absence of a built-in scheduler isn&#8217;t a weakness. It&#8217;s an invitation to build exactly what you need, with security and performance that WordPress plugins can&#8217;t match. When the ecosystem matures, someone will package this into a one-click install. Until then, you have the foundation.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most content scheduling systems are just delayed publishing \u2014 a calendar with a toggle. EmDash flips that. Its real power isn&#8217;t in a built-in scheduler (it doesn&#8217;t have one yet). It&#8217;s in the event-driven architecture that lets you build custom automation without the security nightmares plugins bring. Think of scheduling not as a date-picker, but [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":100212,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/98069.png","fifu_image_alt":"EmDash Content Scheduling: Automate Without Plugin Bloat","footnotes":""},"categories":[31],"tags":[],"class_list":["post-98069","post","type-post","status-publish","format-standard","has-post-thumbnail","category-technology"],"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/98069.png","fifu_image_alt":"EmDash Content Scheduling: Automate Without Plugin Bloat","_links":{"self":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98069","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/comments?post=98069"}],"version-history":[{"count":1,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98069\/revisions"}],"predecessor-version":[{"id":100213,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98069\/revisions\/100213"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media\/100212"}],"wp:attachment":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media?parent=98069"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/categories?post=98069"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/tags?post=98069"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}