{"id":97995,"date":"2026-10-03T12:38:00","date_gmt":"2026-10-03T16:38:00","guid":{"rendered":"https:\/\/overcentral.com\/en\/?p=97995"},"modified":"2026-09-29T07:49:26","modified_gmt":"2026-09-29T11:49:26","slug":"emdash-ecommerce-plugin-development-97995","status":"publish","type":"post","link":"https:\/\/overcentral.com\/en\/emdash-ecommerce-plugin-development-97995\/","title":{"rendered":"How a Developer Built a Custom E-Commerce Plugin for EmDash in 2 Days"},"content":{"rendered":"<p>A developer with 10 years of WordPress plugin experience set out to replicate a basic WooCommerce-like product system on EmDash. The result took two days, not weeks. The key wasn&#8217;t coding faster \u2014 it was understanding how EmDash&#8217;s permission model changes plugin design.<\/p>\n<p>Most tutorials focus on the &#8220;what&#8221; of EmDash: V8 isolates, dynamic workers, Astro integration. The non-obvious part is the <em>capability manifest<\/em>. In WordPress, a plugin implicitly gets full database access. In EmDash, you declare exactly what your plugin can touch \u2014 and the runtime enforces it.<\/p>\n<h2>The Problem with WordPress Plugin Architecture<\/h2>\n<p>WordPress plugins run in the same process as the core. A product plugin can read user tables, delete posts, or send arbitrary network requests. The only barrier is trust. EmDash eliminates that trust requirement by design. But that forces developers to think differently about plugin structure.<\/p>\n<p>For an e-commerce plugin, the developer needed three capabilities: <code>read:content<\/code>, <code>write:content<\/code>, and <code>email:send<\/code>. That&#8217;s it. The plugin couldn&#8217;t touch the file system, couldn&#8217;t access other plugins&#8217; data, and couldn&#8217;t make external API calls without an explicit grant. This constraint turned out to be an advantage \u2014 it forced a clean separation between data logic and presentation.<\/p>\n<h2>Designing the Permission Manifest<\/h2>\n<p>The first hour went into the manifest file. EmDash plugins use a <code>definePlugin<\/code> function from the <code>@emdash-cms\/plugin<\/code> library. The developer defined capabilities as a plain object:<\/p>\n<p>&#8220;`javascript<\/p>\n<p>{<\/p>\n<p>  capabilities: [&#8216;read:content&#8217;, &#8216;write:content&#8217;, &#8217;email:send&#8217;]<\/p>\n<p>}<\/p>\n<p>&#8220;`<\/p>\n<p>The non-obvious insight: capabilities are not just security controls \u2014 they are documentation. Any future developer reading this manifest knows exactly what the plugin does. And because the runtime enforces them, there is no way for the plugin to overreach. You cannot accidentally create an admin user or modify another plugin&#8217;s settings.<\/p>\n<h2>Leveraging V8 Isolates and Dynamic Workers<\/h2>\n<p>Dynamic workers are the execution engine. Each plugin runs in its own V8 isolate, which spins up in milliseconds and disappears when the hook finishes. The developer used a <code>product.created<\/code> hook to trigger an email notification when a new product is published.<\/p>\n<p>The non-obvious part: dynamic workers are not just for security. They also solve the cold-start problem. In WordPress, a plugin&#8217;s initialization code runs on every page load. In EmDash, the plugin only executes when the specific hook fires. For an e-commerce site with thousands of products but few admin interactions, this cuts serverless costs dramatically.<\/p>\n<h2>Building the Plugin: Hook Selection and Capabilities<\/h2>\n<p>The developer chose three hooks: <code>product.created<\/code>, <code>product.updated<\/code>, and <code>product.deleted<\/code>. Each hook triggers a dynamic worker that performs only the declared capabilities.<\/p>\n<p>For example, the <code>product.created<\/code> hook reads the product data from the D1 database (via the <code>read:content<\/code> capability), formats an email, and sends it using the <code>email:send<\/code> capability. The plugin never writes to the database directly \u2014 it relies on EmDash&#8217;s built-in content type system.<\/p>\n<p>The non-obvious lesson: you don&#8217;t need custom database tables. EmDash&#8217;s content types (custom post types) come with built-in field definitions, search, and SEO metadata. The developer created a &#8220;Product&#8221; content type with fields for price, SKU, and stock \u2014 all defined through the admin UI, but stored in the same D1 database. The plugin only acts on events.<\/p>\n<h2>Testing and Deployment<\/h2>\n<p>Testing a sandboxed plugin is different from WordPress. You cannot just echo debug statements \u2014 the isolate has no console output by default. The developer used EmDash&#8217;s built-in logging by requesting the <code>log<\/code> capability in the manifest. That allowed him to inspect plugin behavior during development.<\/p>\n<p>Deployment took 15 minutes. The plugin was packaged as a Node module and uploaded to the EmDash admin. Because it runs in a dynamic worker, there was no server restart, no plugin conflict, no database migration. The developer rolled back by simply removing the plugin from the admin panel.<\/p>\n<h2>Lessons Learned<\/h2>\n<p>The most surprising result: the plugin took 2 days to build, but the developer estimated it would have taken 2 weeks in WordPress. The difference came from not having to write authentication, session management, or database abstraction \u2014 EmDash provides all of that. The plugin only needed to handle the business logic.<\/p>\n<p>That said, this timeline is not typical for every plugin. Simple event-driven plugins with limited capabilities are fast. Complex plugins requiring external API integrations or file handling would take longer because each external interaction must be explicitly granted in the manifest. The developer avoided those complexities by keeping the e-commerce plugin self-contained.<\/p>\n<h2>Limitations and Considerations<\/h2>\n<p>The sandbox model has a tradeoff. Plugins that need to modify the core admin interface \u2014 adding custom dashboard widgets or altering the editor \u2014 cannot run in a dynamic worker. Those require a different approach: trusted Node modules that run in-process. For the e-commerce plugin, this wasn&#8217;t an issue because all product management happens through EmDash&#8217;s built-in content types.<\/p>\n<p>Another limitation: the plugin cannot directly access the file system. If your e-commerce plugin generates PDF invoices, you would need to use an external storage service (like R2) and grant explicit permission. The developer skipped that feature for now.<\/p>\n<p>The broader implication: EmDash&#8217;s plugin architecture naturally pushes developers toward event-driven, capability-scoped designs. That&#8217;s a good thing for security, but it requires unlearning the &#8220;full access&#8221; mindset that WordPress has normalized for 20 years.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A developer with 10 years of WordPress plugin experience set out to replicate a basic WooCommerce-like product system on EmDash. The result took two days, not weeks. The key wasn&#8217;t coding faster \u2014 it was understanding how EmDash&#8217;s permission model changes plugin design. Most tutorials focus on the &#8220;what&#8221; of EmDash: V8 isolates, dynamic workers, [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":99052,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/97995.png","fifu_image_alt":"How a Developer Built a Custom E-Commerce Plugin for EmDash in 2","footnotes":""},"categories":[31],"tags":[],"class_list":["post-97995","post","type-post","status-publish","format-standard","has-post-thumbnail","category-technology"],"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/97995.png","fifu_image_alt":"How a Developer Built a Custom E-Commerce Plugin for EmDash in 2","_links":{"self":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/97995","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=97995"}],"version-history":[{"count":1,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/97995\/revisions"}],"predecessor-version":[{"id":98172,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/97995\/revisions\/98172"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media\/99052"}],"wp:attachment":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media?parent=97995"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/categories?post=97995"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/tags?post=97995"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}