enrichkit
What it does
On every publish, enrichkit makes one LLM call and writes a summary, key topics, auto-tags, a reading level estimate, and a tweet draft to post metadata. You choose which of those five fields to generate. Content under 100 words is skipped - not worth the API call.
There is no companion component. enrichkit writes metadata, not markup - what you do with a summary or a tweet draft is up to your layout, your search index, or an agent reading the post.
Install
npm install @plugdash/enrichkitRegister
Open astro.config.mjs at the root of your Astro project. Import the plugin at the top, then add it to the pluginsarray of the emdash() integration - the one that sits inside Astro's top-level integrations array:
import { defineConfig } from "astro/config"
import emdash from "emdash/astro"
import { enrichkitPlugin } from "@plugdash/enrichkit"
export default defineConfig({
integrations: [
emdash({
plugins: [
enrichkitPlugin({
provider: "anthropic",
apiKey: process.env.ANTHROPIC_API_KEY,
}),
],
}),
],
})Set ANTHROPIC_API_KEY (or OPENAI_API_KEY if you pick OpenAI) as a secret in your build environment. You can also leave apiKey out at build time entirely and set it later from the admin at /_emdash/admin/plugins/enrichkit/settings - either way, the key is only ever read from KV at request time and is never rendered back into the UI (the settings form shows a masked hint like sk-t...1234).
allowedHosts is narrowed automatically to the configured provider's API host (api.anthropic.com orapi.openai.com). With no provider set yet, both hosts are allowed until the admin page picks one.
How it fires
publish (status: published) -> body >= 100 words -> one prompt built for the enabled fields -> provider call (20s timeout) -> parse + validate JSON -> merge into metadataA few things worth knowing before you flip this on:
- Only fires on
content:afterSavewhen the content's status ispublished- drafts, archived, and scheduled content are never enriched. - The Portable Text body is flattened to plain text and capped at 12,000 characters before it goes into the prompt - the opening of a long article carries the argument, the tail rarely changes the summary.
- If the model's reply isn't valid JSON, enrichkit retries once. Two failed parses in a row and it gives up and logs - never more than two provider calls per publish.
- A rate limit (429) is not retried synchronously - the next publish gets a fresh attempt.
- An over-length tweet draft is dropped, not truncated. A tweet cut mid-word is worse than no tweet.
- Every enrichment failure - missing key, timeout, rate limit, invalid JSON, quota error - just skips and logs. It never fails the publish itself.
- Metadata is read-merge-written, so other plugins' keys under
post.data.metadataare preserved.
Examples
Config variants
Default enrichments are summary, key topics, auto-tags, and a tweet draft - reading level is off unless you turn it on. To also get the reading level and skip the tweet draft:
enrichkitPlugin({
provider: "anthropic",
apiKey: process.env.ANTHROPIC_API_KEY,
enrichments: {
readingLevel: true,
tweetDraft: false,
},
})Or run it against OpenAI instead, with an explicit model override:
enrichkitPlugin({
provider: "openai",
apiKey: process.env.OPENAI_API_KEY,
model: "gpt-4o",
})Reading the metadata
Since there's no component, you read post.data.metadata.enrichkitdirectly wherever you want it - typically the post layout:
---
const enrichkit = post.data.metadata?.enrichkit
---
{enrichkit?.summary && <p class="summary">{enrichkit.summary}</p>}
{enrichkit?.autoTags && (
<ul class="tags">
{enrichkit.autoTags.map((tag) => <li>{tag}</li>)}
</ul>
)}Sample output
Sample output with every enrichment on, rendered the way a theme might show it
A look at how static sites evolved from plain HTML to full content platforms, and what that means for publishers choosing a stack today.
static site generators · content management · web performance
Grade 9
Static sites grew up. Here's what changed and why it matters for your next project.
A published post with all default enrichments on ends up with this under post.data.metadata.enrichkit:
{
"summary": "A look at how static sites evolved from plain HTML to full content platforms, and what that means for publishers choosing a stack today.",
"keyTopics": ["static site generators", "content management", "web performance"],
"autoTags": ["ssg", "cms", "performance", "web-dev"],
"tweetDraft": "Static sites grew up. Here's what changed and why it matters for your next project.",
"generatedAt": "2026-04-01T12:00:00Z",
"model": "claude-haiku-4-5"
}Only fields that were enabled and that passed validation get written - a field the model omitted, or returned in the wrong shape, is left out entirely rather than written as null.
No component
enrichkit ships no companion component. It writes metadata, full stop - rendering a summary or a tag list in your theme is a few lines in your layout, and an agent working against the site can read the same fields through the EmDash MCP server without any UI at all.
Props
| prop | type | default | description |
|---|---|---|---|
provider | "anthropic" | "openai" | Required - which LLM API to call | |
apiKey | string | Required - API key for the chosen provider | |
model | string | provider default | claude-haiku-4-5 (Anthropic) or gpt-4o-mini (OpenAI) |
enrichments.summary | boolean | true | 2-3 sentence summary of the main argument |
enrichments.keyTopics | boolean | true | 3-5 main topics as short noun phrases |
enrichments.readingLevel | boolean | false | Estimated grade level (e.g. "Grade 8") |
enrichments.autoTags | boolean | true | 3-8 lowercase, hyphenated discovery tags |
enrichments.tweetDraft | boolean | true | Tweet draft under 280 characters |
For agents
After installing @plugdash/enrichkit and registering it in astro.config.mjs:
- Set an API key - either at build time via the
apiKeyoption (read from an env var) or later from the admin settings page. - Publish a test post with at least 100 words of body text.
- Verify
post.data.metadata.enrichkitis populated:summary(string),keyTopics(string array, if enabled),readingLevel(string, if enabled - off by default),autoTags(lowercase hyphenated string array, if enabled),tweetDraft(string, under 280 chars, if enabled),generatedAt(ISO timestamp), andmodel(the model id used).
If no enrichkit metadata appears, check in order: the post status is "published" and not a draft; the body has at least 100 words; an API key is configured (look for "apiKey not configured" in the plugin logs); and the post was saved after the plugin was installed or reconfigured.
post.data.metadata.enrichkit.tweetDraft in particular is written specifically so an agent with access to the EmDash MCP server can read it and post to social platforms after a human publishes - no further human step required for that part of the workflow.