WEBVTT - Eight plugins, one session, zero npm installs

1
00:00:00.000 --> 00:00:04.075
I shipped eight EmDash plugins in one session.

2
00:00:04.075 --> 00:00:05.094
Not prototypes.

3
00:00:05.094 --> 00:00:06.113
Not stubs.

4
00:00:06.113 --> 00:00:12.736
Working, tested, event-driven plugins with admin settings, dashboard widgets, storage, and API routes.

5
00:00:12.736 --> 00:00:18.849
Every one of them runs in production on this blog right now.

6
00:00:18.849 --> 00:00:30.057
This post is what I learned about building fast when the framework handles infrastructure and you only have to think about behavior.

7
00:00:30.057 --> 00:00:33.623
The plugin architecture that makes this possible

8
00:00:33.623 --> 00:00:36.679
EmDash's definePlugin() gives you five primitives:

9
00:00:36.679 --> 00:00:44.830
Storage — per-plugin document collections with indexes. Not key-value — full queryable collections with cursor pagination.

10
00:00:44.830 --> 00:00:50.434
KV — simple key-value for settings and state. Persists across deploys.

11
00:00:50.434 --> 00:01:01.642
Hooks — lifecycle events like content:afterSave, plugin:install, cron. Your code runs in response to things happening, not on a schedule you manage.

12
00:01:01.642 --> 00:01:08.264
Routes — plugin-scoped API endpoints. The framework handles auth, CORS, and request parsing.

13
00:01:08.264 --> 00:01:17.434
Admin UI — settings schemas, dashboard widgets, and admin pages. You declare the schema; EmDash renders the UI.

14
00:01:17.434 --> 00:01:29.660
The key insight: none of these require you to think about databases, authentication, deployment, or infrastructure. You write behavior. The framework provides everything else.

15
00:01:29.660 --> 00:01:31.189
What I built

16
00:01:31.189 --> 00:01:32.717
1. Agent Analytics

17
00:01:32.717 --> 00:01:46.472
Classifies every request by user-agent against 15 known AI crawler patterns. Records per-hit and daily aggregates in plugin storage. Dashboard widget shows agent vs human traffic percentage.

18
00:01:46.472 --> 00:01:56.660
The core is one pure function — classifyUserAgent(ua) — that returns { isAgent, name, operator }. Everything else is wiring.

19
00:01:56.660 --> 00:01:58.189
2. Tip Jar

20
00:01:58.189 --> 00:02:01.755
An x402 tip button on every post.

21
00:02:01.755 --> 00:02:03.283
Visitors send $0.

22
00:02:03.283 --> 00:02:05.830
25 USDC with one click.

23
00:02:05.830 --> 00:02:10.925
The payment settles on Base mainnet to my hardware wallet.

24
00:02:10.925 --> 00:02:17.547
The plugin records tips in storage and shows total revenue on the dashboard.

25
00:02:17.547 --> 00:02:25.189
The entire payment flow is EmDash's native x402.enforce() — I didn't write any payment code.

26
00:02:25.189 --> 00:02:27.736
3. MiniMax TTS Narration (Event-Driven)

27
00:02:27.736 --> 00:02:33.849
When a post is published, the content:afterSave hook checks if narration exists.

28
00:02:33.849 --> 00:02:37.925
If not, it generates audio via MiniMax's speech-2.

29
00:02:37.925 --> 00:02:44.038
8-hd model, uploads to R2, and records the narration in plugin storage.

30
00:02:44.038 --> 00:02:55.755
If quota is exhausted, it marks the item as quota-blocked with a 5-hour retry window and a cron handler processes the queue later.

31
00:02:55.755 --> 00:03:04.415
The narration is auto-discovered by the AudioPlayer component on the client — no manual wiring per post.

32
00:03:04.415 --> 00:03:06.962
4. MiniMax Hero Image (Event-Driven)

33
00:03:06.962 --> 00:03:21.226
Same pattern as TTS: content:afterSave fires on publish, checks if a hero image exists, generates one via MiniMax image-01 with configurable style presets (editorial-minimal, tech-dark, hand-drawn, photo-reportage, abstract-geometric).

34
00:03:21.226 --> 00:03:32.943
Five style presets, each expanding to a paragraph-level prompt with composition directives. The output is a 16:9 JPEG that goes straight to R2.

35
00:03:32.943 --> 00:03:34.472
5. Agent SEO

36
00:03:34.472 --> 00:03:35.491
Generates /robots.

37
00:03:35.491 --> 00:03:40.585
txt with explicit allow rules for 16 AI crawlers, /llms.

38
00:03:40.585 --> 00:03:42.623
txt per the llmstxt.

39
00:03:42.623 --> 00:03:45.170
org spec, and Organization JSON-LD.

40
00:03:45.170 --> 00:03:54.340
The bot catalog is versioned data — adding a new crawler is one line in a TypeScript array.

41
00:03:54.340 --> 00:03:55.868
6. PostHog Analytics

42
00:03:55.868 --> 00:04:07.585
Snippet builder with admin-path auto-exclusion and Do Not Track respect. The snippet only fires on public pages; the admin panel never gets tracked.

43
00:04:07.585 --> 00:04:09.113
7. Related Posts

44
00:04:09.113 --> 00:04:25.415
Pure-functional tag-overlap algorithm. Takes a post's tags and all posts' tags, returns the top N related posts ranked by shared tag count. The plugin stores the graph; the template renders the component.

45
00:04:25.415 --> 00:04:27.453
8. Forms (EmDash First-Party)

46
00:04:27.453 --> 00:04:36.113
The one I didn't build — EmDash ships it. Contact forms, submissions, Turnstile spam protection, email notifications.

47
00:04:36.113 --> 00:04:38.151
What makes this fast

48
00:04:38.151 --> 00:04:44.264
Three things made it possible to ship eight plugins in one session:

49
00:04:44.264 --> 00:04:55.981
No boilerplate. definePlugin() handles registration, storage provisioning, settings persistence, admin UI rendering, and route mounting. The smallest useful plugin is about 20 lines.

50
00:04:55.981 --> 00:04:57.509
Pure functional cores.

51
00:04:57.509 --> 00:05:05.151
Every plugin has a pure function at its center — classifyUserAgent(), buildRobotsTxt(), findRelatedPosts(), buildPostImagePrompt(), portableTextToNarration().

52
00:05:05.151 --> 00:05:16.358
These are trivial to test (64 unit tests, all passing in under a second) and trivial to reuse outside the plugin context.

53
00:05:16.358 --> 00:05:17.887
Event-driven by default.

54
00:05:17.887 --> 00:05:28.585
The content:afterSave hook means I never had to build admin buttons, cron schedules, or manual trigger endpoints for the common case.

55
00:05:28.585 --> 00:05:31.642
Publish a post and things happen.

56
00:05:31.642 --> 00:05:35.208
Quota runs out and things retry later.

57
00:05:35.208 --> 00:05:37.245
The plugins are autonomous.

58
00:05:37.245 --> 00:05:38.264
The numbers

59
00:05:38.264 --> 00:05:41.321
8 plugins, all running in production

60
00:05:41.321 --> 00:05:43.868
64 unit tests, 0 failures

61
00:05:43.868 --> 00:05:50.491
7 bridge routes for MiniMax API (image, TTS, music, video, video-status, video-download, voices)

62
00:05:50.491 --> 00:05:52.528
5 MiniMax-generated hero images

63
00:05:52.528 --> 00:05:55.075
1 ambient music track (music-2.5)

64
00:05:55.075 --> 00:05:57.623
1 cinematic hero video (Hailuo-2.3)

65
00:05:57.623 --> 00:06:00.679
1 AI narration with auto-discovery AudioPlayer

66
00:06:00.679 --> 00:06:04.755
All plugins MIT licensed, structured for npm extraction

67
00:06:04.755 --> 00:06:07.302
Zero npm packages published (yet)

68
00:06:07.302 --> 00:06:08.321
What's next

69
00:06:08.321 --> 00:06:12.906
Every plugin extracts cleanly as a standalone npm package.

70
00:06:12.906 --> 00:06:26.151
The plan is to publish them individually, write a companion blog post for each, and sell the skills that teach people how to build their own.

71
00:06:26.151 --> 00:06:33.792
The blog is the storefront; the plugins are the product; x402 is the revenue layer.

72
00:06:33.792 --> 00:06:45.000
If you're building on EmDash and want to see how any of these work, the source is at github.com/integrate-your-mind/mondello-dev. Pull requests welcome.
