Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when adding cross-cutting concerns to durable functions — structured logging or tracing across all functions, error tracking with Sentry, payload encryption for sensitive data, dependency injection of clients (DB, Stripe, etc.) into function handlers, custom telemetry, or behavior that should apply uniformly across many functions. Covers Inngest middleware lifecycle, creating custom middleware, dependencyInjectionMiddleware, @inngest/middleware-encryption, @inngest/middleware-sentry, and cus
.claude/skills/asymmetric-al-inngest-middleware/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 54% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 64% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 76% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 136% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 145% | 0% |
Master Inngest middleware to handle cross-cutting concerns like logging, error tracking, dependency injection, and data transformation. Middleware runs at key points in the function lifecycle, enabling powerful patterns for observability and shared functionality.
> These skills are focused on TypeScript. For Python or Go, refer to the Inngest documentation for language-specific guidance. Core concepts apply across all languages.
> Note: The middleware system was significantly rewritten in v4. The lifecycle hooks documented here reflect the v4 API. If migrating from v3, consult the migration guide for details on breaking changes.
> ⚠ For Realtime use the inngest-realtime skill, NOT this one. Inngest v3 used realtimeMiddleware() from @inngest/realtime to inject a publish arg into function handlers. v4 ships realtime natively — step.realtime.publish is built-in, no middleware required. Do NOT install @inngest/realtime on a v4 project (it's a v3-era package and produces TypeError: Cls is not a constructor at runtime). See the inngest-realtime skill for the v4 pattern.
Middleware allows code to run at various points in an Inngest client's lifecycle - during function execution, event sending, and more. Think of middleware as hooks into the Inngest execution pipeline.
When to use middleware:
Middleware can be registered at client-level (affects all functions) or function-level (affects specific functions).
typescriptconst inngest = new Inngest({ id: "my-app", middleware: [ loggingMiddleware, // Runs 1st errorMiddleware, // Runs 2nd ], }); inngest.createFunction( { id: "example", middleware: [ authMiddleware, // Runs 3rd metricsMiddleware, // Runs 4th ], triggers: [{ event: "test" }], }, async () => { /* function code */ }, );
Order matters: Client middleware runs first, then function middleware, in the order specified.
typescriptimport { InngestMiddleware } from "inngest"; const loggingMiddleware = new InngestMiddleware({ name: "Logging Middleware", init() { // Setup phase - runs when client initializes const logger = setupLogger(); return { // Function execution lifecycle // Note: `fn` is loosely typed in middleware generics; fn.id works at runtime onFunctionRun({ ctx, fn }) { return { beforeExecution() { logger.info("Function starting", { functionId: fn.id, eventName: ctx.event.name, runId: ctx.runId, }); }, afterExecution() { logger.info("Function completed", { functionId: fn.id, runId: ctx.runId, }); }, transformOutput({ result }) { // Log function output logger.debug("Function output", { functionId: fn.id, output: result.data, }); // Return unmodified result return { result }; }, }; }, // Event sending lifecycle onSendEvent() { return { transformInput({ payloads }) { logger.info("Sending events", { count: payloads.length, events: payloads.map((p) => p.name), }); // Spread to convert readonly array to mutable array return { payloads: [...payloads] }; }, }; }, }; }, });
Python middleware follows a similar pattern. See Dependency Injection Reference for complete Python examples.
`## Dependency Injection Share expensive or stateful clients across all functions. **See [Dependency Injection Reference](./references/dependency-injection.md) for detailed patterns.** ### Quick Example - Built-in DI
import { dependencyInjectionMiddleware } from "inngest";
const inngest = new Inngest({ id: 'my-app', middleware: dependencyInjectionMiddleware({ openai: new OpenAI(), db: new PrismaClient(), }), ], });
// Functions automatically get injected dependencies inngest.createFunction( { id: "ai-summary", triggers: { event: "document/uploaded" }] }, async ({ event, openai, db }) => { // Dependencies available in function context const summary = await openai.chat.completions.create({ messages: { role: "user", content: event.data.content }], model: "gpt-4", });
await db.document.update({ where: { id: event.data.documentId }, data: { summary: summary.choices0].message.content } }); } );
`## Middleware Packages Beyond `dependencyInjectionMiddleware` (built-in, shown above), Inngest provides official middleware as **separate packages**. **See [Middleware Reference](./references/built-in-middleware.md) for complete details.** ### Encryption Middleware
npm install @inngest/middleware-encryption
import { encryptionMiddleware } from "@inngest/middleware-encryption";
const inngest = new Inngest({ id: "my-app", middleware: encryptionMiddleware({ key: process.env.ENCRYPTION_KEY, }), ], });
Automatically encrypts all step data, function output, and event `data.encrypted` field. Supports key rotation via `fallbackDecryptionKeys`.
### Sentry Error Tracking
npm install @inngest/middleware-sentry
import as Sentry from "@sentry/node"; import { sentryMiddleware } from "@inngest/middleware-sentry";
Sentry.init({ / your Sentry config / });
const inngest = new Inngest({ id: "my-app", middleware: sentryMiddleware()], });
Captures exceptions, adds tracing to each function run, and includes function ID and event names as context. Requires `@sentry/*@>=8.0.0`.
## Common Middleware Patterns
### Metrics and Performance Tracking
const metricsMiddleware = new InngestMiddleware({ name: "Metrics Tracking", init() { return { onFunctionRun({ ctx, fn }) { let startTime: number;
return { beforeExecution() { startTime = Date.now(); metrics.increment("inngest.step.started", { function: fn.id, event: ctx.event.name, }); },
afterExecution() { const duration = Date.now() - startTime; metrics.histogram("inngest.step.duration", duration, { function: fn.id, event: ctx.event.name, }); },
transformOutput({ result }) { const status = result.error ? "error" : "success"; metrics.increment("inngest.step.completed", { function: fn.id, status: status, });
return { result }; }, }; }, }; }, });
### Advanced Patterns
**Authentication:** Validate tokens and inject user context
**Conditional logic:** Apply middleware based on event type or function
**Circuit breakers:** Prevent cascading failures from external services
### Configuration-Based Middleware
Create reusable middleware with configuration options for different environments and use cases. See reference documentation for complete examples.
## Best Practices
### Design Principles
1. **Keep middleware focused:** One concern per middleware
2. **Handle errors gracefully:** Don't let middleware crash functions
3. **Consider performance:** Middleware runs on every execution
4. **Use proper typing:** Let TypeScript infer middleware types
5. **Test thoroughly:** Middleware affects all functions that use it
### Common Use Cases to Implement
- **Retry logic** for transient failures
- **Circuit breakers** for external service calls
- **Request/response logging** for debugging
- **User context enrichment** from external sources
- **Feature flags** for gradual rollouts
- **Custom authentication** and authorization checks
### Error Handling in Middleware
const robustMiddleware = new InngestMiddleware({ name: "Robust Middleware", init() { return { onFunctionRun({ ctx, fn }) { return { transformOutput({ result }) { try { // Your middleware logic here return performTransformation(result); } catch (middlewareError) { // Log error but don't break the function console.error("Middleware error:", middlewareError);
// Return original result on middleware failure return { result }; } }, }; }, }; }, });
### Testing Middleware
Use Inngest's testing utilities (`createMockContext`, `createMockFunction`) to unit test middleware behavior.
**For complete implementation examples and advanced patterns, see:**
- [Dependency Injection Reference](./references/dependency-injection.md)
- [Built-in Middleware Reference](./references/built-in-middleware.md)
## This Repository
These upstream Inngest instructions are vendored for agent tooling and
integration work in this monorepo.
## Repository Triggers
Use this skill when `inngest-middleware` matches the current Inngest task. If the
right skill is unclear, start with `docs/ai/skills/inngest/SKILL.md`.
## Repository Workflow
1. Confirm whether the request is agent-tooling guidance or product runtime
integration.
2. Use `inngest-brownfield-audit` before changing existing app workflows or
fragile background work.
3. Follow this upstream guidance under OpenSpec, root `AGENTS.md`, repo
rulebooks, framework docs, and runtime evidence.
4. Keep runtime packages, app code, migrations, and `INNGEST_*` env
requirements out of agent-tooling-only changes.
## Repository Checklist
- [ ] The task has explicit product-runtime scope before adding Inngest app code
or dependencies.
- [ ] Existing workflows were audited before introducing or changing durable
workflow behavior.
- [ ] Any MCP usage is backed by a running Inngest dev server on the configured
port.
- [ ] Upstream source and license attribution remain documented in
`docs/ai/skills/inngest/references/upstream.md`.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→pass | 12,454 | 3,981 | -68% | 1 | 1 | 0% | 2,216 | 3,420 | +54% | 0 | 0 | — |
case-01 | fail→pass | 14,113 | 9,468 | -33% | 1 | 1 | 0% | 2,894 | 4,745 | +64% | 0 | 0 | — |
case-02 | fail→pass | 11,914 | 6,587 | -45% | 1 | 1 | 0% | 2,249 | 3,960 | +76% | 0 | 0 | — |
case-04 | pass→pass | 8,250 | 5,186 | -37% | 1 | 1 | 0% | 1,464 | 3,462 | +136% | 0 | 0 | — |
case-05 | pass→pass | 12,373 | 9,472 | -23% | 1 | 1 | 0% | 2,483 | 4,433 | +79% | 0 | 0 | — |
case-06 | pass→pass | 9,689 | 4,795 | -51% | 1 | 1 | 0% | 1,766 | 3,494 | +98% | 0 | 0 | — |
case-07 | fail→pass | 7,481 | 4,599 | -39% | 1 | 1 | 0% | 1,443 | 3,411 | +136% | 0 | 0 | — |
case-08 | fail→pass | 7,857 | 5,408 | -31% | 1 | 1 | 0% | 1,420 | 3,486 | +145% | 0 | 0 | — |
case-09 | fail→pass | 12,513 | 5,408 | -57% | 1 | 1 | 0% | 2,143 | 3,577 | +67% | 0 | 0 | — |
case-10 | fail→pass | 5,992 | 2,587 | -57% | 1 | 1 | 0% | 1,119 | 3,024 | +170% | 0 | 0 | — |
case-11 | pass→pass | 8,934 | 4,042 | -55% | 1 | 1 | 0% | 1,574 | 3,290 | +109% | 0 | 0 | — |
case-12 | pass→pass | 7,972 | 5,138 | -36% | 1 | 1 | 0% | 1,454 | 3,545 | +144% | 0 | 0 | — |
case-13 | fail→pass | 9,996 | 9,269 | -7% | 1 | 1 | 0% | 1,697 | 4,339 | +156% | 0 | 0 | — |
case-14 | fail→pass | 10,233 | 5,089 | -50% | 1 | 1 | 0% | 1,872 | 3,574 | +91% | 0 | 0 | — |
case-15 | pass→pass | 5,821 | 3,141 | -46% | 1 | 1 | 0% | 1,016 | 3,078 | +203% | 0 | 0 | — |
case-16 | fail→pass | 22,471 | 4,491 | -80% | 1 | 1 | 0% | 3,688 | 3,352 | -9% | 0 | 0 | — |
case-17 | pass→pass | 10,277 | 5,622 | -45% | 1 | 1 | 0% | 1,789 | 3,603 | +101% | 0 | 0 | — |
case-18 | pass→pass | 9,152 | 5,475 | -40% | 1 | 1 | 0% | 1,689 | 3,524 | +109% | 0 | 0 | — |
case-19 | pass→pass | 5,825 | 3,312 | -43% | 1 | 1 | 0% | 973 | 3,152 | +224% | 0 | 0 | — |
case-20 | pass→pass | 9,466 | 7,610 | -20% | 1 | 1 | 0% | 1,732 | 4,079 | +136% | 0 | 0 | — |
case-21 | fail→pass | 17,697 | 5,982 | -66% | 1 | 1 | 0% | 2,735 | 3,719 | +36% | 0 | 0 | — |
case-22 | pass→pass | 9,754 | 7,305 | -25% | 1 | 1 | 0% | 1,657 | 3,892 | +135% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.