You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We run EmDash on Cloudflare Workers, so we ship an in-repo email transport that talks to the
SES v2 HTTP API (SigV4 via aws4fetch). SMTP transports are not an option there: nodemailer
needs Node's net/tls, which Workers do not expose.
We wanted a small admin page for that plugin (show what is configured, send a test email).
Every plugin example we found that renders a Block Kit admin page is a sandboxed plugin.
Ours has to stay format: "native", because it imports cloudflare:workers to read the
Worker env at hook time (hooks run outside a request, so Astro.locals.runtime is not there).
It turns out a native plugin can render a Block Kit page too, with no React and no sandbox:
definePlugin({id: "ses-email",version: "1.1.0",capabilities: ["hooks.email-transport:register"],hooks: {"email:deliver": {exclusive: true, handler }},routes: {admin: {handler: adminRouteHandler}},// the name must be "admin"admin: {pages: [{path: "/",label: "Email (AWS SES)",icon: "envelope"}]},})
The page shows up under Plugins in the sidebar and renders the blocks the route returns.
What we measured (emdash 0.36.0, @emdash-cms/admin 0.36.0, as installed)
emdash/src/emdash-runtime.ts:2589-2600 derives adminMode for configured (native)
plugins: admin.entry gives "react", admin.pages/admin.widgets give "blocks".
So declaring admin.pages is what flips a native plugin into Block Kit mode.
@emdash-cms/adminSandboxedPluginPage POSTs the interaction to /_emdash/api/plugins/<id>/admin and renders data.blocks plus data.toast. The sidebar
builder only checks config.adminMode === "blocks", not whether the plugin is sandboxed.
emdash/src/emdash-runtime.ts:3776-3790 dispatches that call through PluginRouteRegistry for a configured plugin, so the route key has to be literally admin. The implicit-admin-route fallback at emdash-runtime.ts:3719-3730 is only for
sandboxed/marketplace plugins, which is easy to misread as "sandboxed only".
emdash/src/astro/routes/api/plugins/[pluginId]/[...path].ts:53 defaults the route to plugins:manage plus CSRF when the route declares no permission, which is the right
default for an admin page.
Route handler gets RouteContext, so ctx.input is the BlockInteraction
(page_load / form_submit / block_action) and the return value becomes data.
The question
Is this a supported contract for format: "native" plugins, or an implementation detail of
how the manifest is built that we should not rely on?
We ask because the docs and the plugin examples only show Block Kit pages for sandboxed
plugins, and because the implicit admin route fallback is explicitly sandbox-only. If it is
supported, it is worth a line in the plugin docs: it is the only way to get an admin UI in a
plugin that needs host-runtime access (a Workers binding, cloudflare:workers, an env
secret), which a sandboxed plugin cannot reach.
Happy to send a docs PR if you tell us which way it should read.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
What we did
We run EmDash on Cloudflare Workers, so we ship an in-repo email transport that talks to the
SES v2 HTTP API (SigV4 via
aws4fetch). SMTP transports are not an option there:nodemailerneeds Node's
net/tls, which Workers do not expose.We wanted a small admin page for that plugin (show what is configured, send a test email).
Every plugin example we found that renders a Block Kit admin page is a sandboxed plugin.
Ours has to stay
format: "native", because it importscloudflare:workersto read theWorker env at hook time (hooks run outside a request, so
Astro.locals.runtimeis not there).It turns out a native plugin can render a Block Kit page too, with no React and no sandbox:
The page shows up under Plugins in the sidebar and renders the blocks the route returns.
What we measured (emdash 0.36.0, @emdash-cms/admin 0.36.0, as installed)
emdash/src/emdash-runtime.ts:2589-2600derivesadminModefor configured (native)plugins:
admin.entrygives"react",admin.pages/admin.widgetsgive"blocks".So declaring
admin.pagesis what flips a native plugin into Block Kit mode.@emdash-cms/adminSandboxedPluginPagePOSTs the interaction to/_emdash/api/plugins/<id>/adminand rendersdata.blocksplusdata.toast. The sidebarbuilder only checks
config.adminMode === "blocks", not whether the plugin is sandboxed.emdash/src/emdash-runtime.ts:3776-3790dispatches that call throughPluginRouteRegistryfor a configured plugin, so the route key has to be literallyadmin. The implicit-admin-route fallback atemdash-runtime.ts:3719-3730is only forsandboxed/marketplace plugins, which is easy to misread as "sandboxed only".
emdash/src/astro/routes/api/plugins/[pluginId]/[...path].ts:53defaults the route toplugins:manageplus CSRF when the route declares nopermission, which is the rightdefault for an admin page.
RouteContext, soctx.inputis theBlockInteraction(
page_load/form_submit/block_action) and the return value becomesdata.The question
Is this a supported contract for
format: "native"plugins, or an implementation detail ofhow the manifest is built that we should not rely on?
We ask because the docs and the plugin examples only show Block Kit pages for sandboxed
plugins, and because the implicit
adminroute fallback is explicitly sandbox-only. If it issupported, it is worth a line in the plugin docs: it is the only way to get an admin UI in a
plugin that needs host-runtime access (a Workers binding,
cloudflare:workers, an envsecret), which a sandboxed plugin cannot reach.
Happy to send a docs PR if you tell us which way it should read.
All reactions