{"id":97985,"date":"2026-10-02T12:38:00","date_gmt":"2026-10-02T16:38:00","guid":{"rendered":"https:\/\/overcentral.com\/en\/?p=97985"},"modified":"2026-09-29T07:47:55","modified_gmt":"2026-09-29T11:47:55","slug":"deploy-emdash-cloudflare-workers-97985","status":"publish","type":"post","link":"https:\/\/overcentral.com\/en\/deploy-emdash-cloudflare-workers-97985\/","title":{"rendered":"Deploy EmDash on Cloudflare Workers: The Real 5-Minute Guide"},"content":{"rendered":"<p>You have three minutes of CLI time and two minutes of configuration. That&#8217;s the promise. But the non-obvious part isn&#8217;t running <code>npm create emdash@latest<\/code>codecode \u2014 it&#8217;s wiring Cloudflare&#8217;s paid infrastructure correctly so the sandboxed plugins actually work.<\/p>\n<p>Deploying <a href=\"https:\/\/emdash.dev\/\" target=\"_blank\" rel=\"noopener noreferrer\" data-iacss-external=\"1\">EmDash<\/a> on <a href=\"https:\/\/workers.cloudflare.com\/\" target=\"_blank\" rel=\"noopener noreferrer\" data-iacss-external=\"1\">Cloudflare Workers<\/a> means you&#8217;re not just deploying a CMS. You&#8217;re provisioning four separate Cloudflare products: Workers (compute), D1 (database), R2 (file storage), and KV (session state). Each requires its own API token scope. Miss one, and the deployment fails silently after the build step.<\/p>\n<h2>What You Actually Need Before You Start<\/h2>\n<p>Skip the free tier. The sandboxed plugin feature that makes EmDash superior to WordPress requires <strong>Dynamic Workers<\/strong>, which only run on the Paid Workers plan ($5\/month minimum).<\/p>\n<p>You also need:<\/p>\n<ul>\n<li>A Cloudflare account with the Workers Paid plan enabled<\/li>\n<li>API tokens with <strong>Workers<\/strong>, <strong>D1<\/strong>, <strong>R2<\/strong>, and <strong>KV<\/strong> permissions (all <strong>Edit<\/strong> scope)<\/li>\n<li>Node.js 22.12+ installed locally<\/li>\n<li>A domain on Cloudflare (or use the workers.dev subdomain for testing)<\/li>\n<\/ul>\n<h2>Step 1: Set Up the Paid Plan<\/h2>\n<p>Go to your Cloudflare dashboard \u2192 Workers &amp; Pages \u2192 Plans. Switch to <strong>Paid<\/strong>. This unlocks Dynamic Workers and the ability to deploy EmDash with plugin isolation.<\/p>\n<p>Without this step, <code>npm create emdash@latest<\/code>codecode will succeed, but your deployment will return error <strong>10195<\/strong> when it tries to spin up a sandboxed plugin.<\/p>\n<h2>Step 2: Create the Project with the Right Template<\/h2>\n<p>Run:<\/p>\n<p>&#8220;`bash<\/p>\n<p>npm create emdash@latest my-site<\/p>\n<p>&#8220;`<\/p>\n<p>When prompted, choose <strong>Cloudflare Workers<\/strong> as the deployment target. Select a template (Blog, Marketing, or Portfolio \u2014 I recommend <strong>Blog<\/strong> for testing). The CLI will ask for your Cloudflare API token. Use the one with the four scopes above.<\/p>\n<h2>Step 3: Configure Bindings (The Tricky Part)<\/h2>\n<p>After the project is created, open <code>astro.config.mjs<\/code>codecode. You&#8217;ll see something like:<\/p>\n<p>&#8220;`js<\/p>\n<p>database: {<\/p>\n<p>  type: &#8216;d1&#8217;,<\/p>\n<p>  binding: &#8216;DB&#8217;<\/p>\n<p>},<\/p>\n<p>storage: {<\/p>\n<p>  type: &#8216;r2&#8217;,<\/p>\n<p>  binding: &#8216;MEDIA&#8217;<\/p>\n<p>},<\/p>\n<p>kv: {<\/p>\n<p>  type: &#8216;kv&#8217;,<\/p>\n<p>  binding: &#8216;SESSION&#8217;<\/p>\n<p>}<\/p>\n<p>&#8220;`<\/p>\n<p>These bindings must match the actual resources in your Cloudflare account. If you haven&#8217;t created them yet, the CLI usually does it automatically \u2014 but <strong>not always<\/strong>. Verify in your Cloudflare dashboard that three resources exist:<\/p>\n<ul>\n<li>A D1 database named <code>emdash-db<\/code>codecode<\/li>\n<li>An R2 bucket named <code>emdash-media<\/code>codecode<\/li>\n<li>A KV namespace named <code>emdash-session<\/code>codecode<\/li>\n<\/ul>\n<p>If any are missing, create them manually and update <code>astro.config.mjs<\/code>codecode with the correct names.<\/p>\n<h2>Step 4: Deploy and Verify<\/h2>\n<p>Run:<\/p>\n<p>&#8220;`bash<\/p>\n<p>npx astro build<\/p>\n<p>npx wrangler deploy<\/p>\n<p>&#8220;`<\/p>\n<p>The first deploy takes about 30 seconds. After it completes, visit your site&#8217;s URL. Add <code>\/admin<\/code>codecode to see the login screen. Set up your passkey (WebAuthn) \u2014 no passwords.<\/p>\n<p>Now test the sandbox. Create a simple plugin (follow the EmDash docs) and install it. If it runs without errors, Dynamic Workers are active. You can verify by checking the Workers dashboard for a new <code>emdash-plugin-executor<\/code>codecode worker.<\/p>\n<h2>Common Pitfalls and How to Avoid Them<\/h2>\n<ul>\n<li><strong>Forgetting to enable the Paid plan<\/strong> \u2014 The CLI won&#8217;t warn you. The error only surfaces after deployment.<\/li>\n<li><strong>API token scope mismatch<\/strong> \u2014 Use a token with all four Edit scopes, not just Workers.<\/li>\n<li><strong>D1 database not auto-created<\/strong> \u2014 This happens in about 1 in 5 deployments. Check the dashboard.<\/li>\n<li><strong>Cold start latency<\/strong> \u2014 V8 isolates are millisecond-fast, but D1 queries can add 200ms on the first request. Pre-warm by hitting your site with a simple GET request after deploy.<\/li>\n<\/ul>\n<h2>The Cost Surprise Nobody Talks About<\/h2>\n<p>Deploying EmDash is cheap \u2014 $5\/month for the paid plan covers 10 million requests. But the billing model is <strong>serverless per-request<\/strong>, not flat-rate. A DDoS attack or a misbehaving plugin can spike your bill. Cloudflare offers no global spending cap. You must manually set CPU time limits per Worker and configure WAF rate limiting to prevent runaway costs. That&#8217;s a task most beginners overlook, and it can turn a $5 month into a $500 surprise.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You have three minutes of CLI time and two minutes of configuration. That&#8217;s the promise. But the non-obvious part isn&#8217;t running npm create emdash@latestcodecode \u2014 it&#8217;s wiring Cloudflare&#8217;s paid infrastructure correctly so the sandboxed plugins actually work. Deploying EmDash on Cloudflare Workers means you&#8217;re not just deploying a CMS. You&#8217;re provisioning four separate Cloudflare products: [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":98812,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/97985.png","fifu_image_alt":"Deploy EmDash on Cloudflare Workers: The Real 5-Minute Guide","footnotes":""},"categories":[31],"tags":[],"class_list":["post-97985","post","type-post","status-publish","format-standard","has-post-thumbnail","category-technology"],"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/97985.png","fifu_image_alt":"Deploy EmDash on Cloudflare Workers: The Real 5-Minute Guide","_links":{"self":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/97985","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=97985"}],"version-history":[{"count":1,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/97985\/revisions"}],"predecessor-version":[{"id":98325,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/97985\/revisions\/98325"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media\/98812"}],"wp:attachment":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media?parent=97985"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/categories?post=97985"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/tags?post=97985"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}