Shared packages

Integration as package

Package a custom framework integration as an npm library so your team — or the open-source community — can install evlog support for runtime X with one command.

Once you've built a working evlog integration for a new framework or runtime (Custom framework), publishing it as an npm package is the natural next step. This page walks through what your @my-org/evlog-medusa (or evlog-medusa, if it's open-source) needs to ship.

Why package this?

  • Pay the integration cost once — every Medusa app benefits
  • Single update path — the integration's owner ships fixes; consumers bump
  • Match the built-in evlog/<framework> ergonomics — same shape, same docs

Scaffold

src/index.ts
import { defineFrameworkIntegration } from 'evlog/toolkit'
import type { MedusaContainer, MedusaRequest, MedusaResponse } from '@medusajs/framework/types'

export interface MedusaEvlogOptions {
  service?: string
  // ... whatever options your integration accepts
}

const integration = defineFrameworkIntegration<MedusaRequest>({
  name: 'medusa',
  extractRequest: req => ({
    method: req.method,
    path: req.path,
    headers: req.headers,
    requestId: req.headers['x-request-id'] as string | undefined,
  }),
  attachLogger: (req, logger) => {
    ;(req as unknown as { evlog: typeof logger }).evlog = logger
  },
})

export function evlog(options: MedusaEvlogOptions = {}) {
  return integration.middleware(options)
}

Package layout

package.json
{
  "name": "@my-org/evlog-medusa",
  "version": "0.1.0",
  "type": "module",
  "main": "./dist/index.mjs",
  "types": "./dist/index.d.mts",
  "exports": {
    ".": {
      "types": "./dist/index.d.mts",
      "import": "./dist/index.mjs"
    }
  },
  "files": ["dist"],
  "peerDependencies": {
    "evlog": "^2",
    "@medusajs/framework": "^2"
  }
}
tsdown.config.ts
import { defineConfig } from 'tsdown'

export default defineConfig({
  entry: { 'index': 'src/index.ts' },
  format: 'esm',
  dts: true,
  external: ['evlog', 'evlog/toolkit', '@medusajs/framework', '@medusajs/framework/types'],
})

Consuming it

// medusa-config.ts
import { evlog } from '@my-org/evlog-medusa'

export default {
  // ...
  apis: {
    middlewares: [evlog({ service: 'shop-api' })],
  },
}

The integration matches the shape of every built-in evlog/<framework> package — so it slots into existing project setups (drains, enrichers, audit, sampling) without surprise.

Publishing checklist

  • peerDependency on evlog: ^2 AND on the framework's package
  • defineFrameworkIntegration (not raw createMiddlewareLogger) so the standard hooks fire correctly
  • README with a "What you get" section listing the wide event fields the integration produces (method, path, requestId, status, duration, custom)
  • e2e test with the framework's test utils, asserting that a request through the middleware emits a wide event
  • Document any framework-specific gotchas (request lifecycle quirks, async context propagation, etc.)
  • If open-source : link to it from the Frameworks overview so the next person finds it

Examples that exist in the wild

evlog's own evlog/nuxt, evlog/next, evlog/hono, evlog/express, evlog/fastify, evlog/elysia, evlog/nestjs, evlog/sveltekit, evlog/react-router are all built on defineFrameworkIntegration. They're the canonical reference for how a packaged integration should look.