I spent time building emdash-sheets-sync: a small EmDash plugin that connects Google Sheets (service account, cached rows, sync, admin UI, and a Portable Text block for the front end). The idea was straightforward. Getting it to a place where I could trust local testing was not.

This post is what went well, what fought me, and why I’m pausing, not because the idea is wrong, but because the cost of fighting the platform stopped making sense for me right now.

What went well

The plugin model matches the mental model. EmDash’s definePlugin() story, routes, storage indexes, admin entry, hooks, declared capabilities and hosts, is coherent. For a “read Sheets, store rows, expose an API” plugin, the shape of the code felt right.

The feature set I wanted is achievable in one package. Service account JSON as a secret, CRUD-ish “table connections,” a sync path, cron-shaped batching, and an Astro component for Portable Text are all reasonable fits for how EmDash documents plugins.

Once the worker path worked, the admin loop was real. After switching to build + wrangler dev --local, I could log in, save settings, add a connection, and see sync succeed. That validated the core integration, not the editor polish, but the data path.

Small, sharp bugs were fixable. Example: Cloudflare’s Kumo button API renamed a variant (dangerdestructive). One line, clear stack trace. That’s the kind of friction I expect in a young design system.

What didn’t go well

Local dev with astro dev was unreliable. I hit Vite SSR dependency optimization races with Miniflare: missing deps_ssr chunks, reloads referencing stale URLs, 500s during normal iteration. That’s not “I misconfigured the plugin”, it’s toolchain churn between Astro, Vite, and the Cloudflare dev path. The workaround became: don’t use astro dev for this harness; use a built worker. That’s a big workflow tax for a “small plugin.”

Session auth on http://localhost was a trap. Production-oriented session cookies defaulted to Secure. Browsers won’t apply those cleanly on plain HTTP. Result: passkey verify returned 200, but I stayed on the login screen, a classic “it works on the server, not in the browser” bug. Fixable, but only if you already know to look for it.

Portable Text blocks didn’t show up where I expected. The plugin declares a Sheet table block in portableTextBlocks, but the inline TipTap editor’s slash menu didn’t offer it; the menu is still built from a fixed list of built-ins. So “insert my block from the post editor” wasn’t there without API/script workarounds (fetch post JSON, append a block, PUT back). That’s a product gap relative to the promise of “custom block types,” not a skill issue.

What I never had (the “finished” author experience)

There was no way in the post editor to insert the Sheet block and see the table there. Plugin blocks from portableTextBlocks never showed up in the slash menu, so the normal “write an article and embed the plugin” flow was incomplete. In that sense it was not a finished product for editors.

Whether readers could ever see data

The site side was wired: Portable Text merges plugin blockComponents, and SheetsTable.astro loads rows from tables/data when the block has the right id (plugin table id). The append-sheet-block script was there to inject that block into content via the API.

It’s accurate to say I didn’t reach a finished product: the editor integration never landed, and the end-to-end “article displays sheet” path was theoretical or script-only, not a complete, shippable workflow.

The gap between README and day-one DX is wide. EmDash’s repo and README describe a compelling future: Portable Text, plugins, Cloudflare, agents. In practice, beta here means you’re often the one connecting dots between adapter behavior, session semantics, and editor completeness.

Why I’m stopping (for now)

I’m not arguing EmDash is a dead end. I’m saying the time-to-confidence for “ship and maintain a plugin on Cloudflare + EmDash” is still high compared to what I wanted from this experiment, which was: validate a simple integration quickly.

I’d rather:

  • Wait for clearer stories around local dev stability and first-class plugin blocks in the editor, or
  • Revisit when I’m ready to contribute upstream (issues/PRs) instead of only carrying workarounds in a private harness.

Stopping here is a scheduling and focus decision, not a verdict on the team or the roadmap.

If you’re reading this as a future plugin author

  • Assume beta means you’ll touch adapter + auth + editor edges, not just your domain logic.
  • Prefer wrangler dev --local (or whatever the project documents as stable) over assuming astro dev is smooth on Workers.
  • If your plugin needs custom PT blocks, confirm insertion UX in the current admin, not only portableTextBlocks in the manifest.

Closing

emdash-sheets-sync was a useful forcing function: it showed where EmDash is already strong (plugin API shape, worker deployment path) and where the developer experience still needs runway (editor integration, local dev reliability, session defaults for local HTTP).

I’m parking the project in a good-enough state: core sync works, docs and scripts document the rough edges. When the platform catches up, I’ll know exactly what to reopen first: the slash menu, not the Sheets API.

— Divyendra