Plugin Catalog

This document lists all shipped plugins for @websideproject/nuxt-auto-api.

Plugin Catalog

This document lists all shipped plugins for @websideproject/nuxt-auto-api.

See Plugin System for how plugins work and how to create your own.

Pipeline Capabilities Reference

Before reading the plugin list, understand what the plugin system can do:

CapabilityHowNotes
Modify data before writebeforeCreate/beforeUpdate return modified dataWorks
Block an operationThrow from any middleware or before-hookWorks
Side effects after mutationafterCreate/afterUpdate/afterDelete hooksWorks
Enrich request contextextendContext() — adds data to HandlerContextRuns once per request
Access databasecontext.db (full Drizzle instance) in any hook/middlewareNo transaction control across hooks
Add new endpointsbuildSetupaddServerHandler()Build-time only
Modify response dataReturn values from afterGet/afterList/afterCreate/afterUpdate hooksHooks returning undefined are side-effect only (backward compatible)
Modify list query/filtersPush SQL conditions into context.additionalFilters in beforeList or middlewareList handler merges with and()
Short-circuit with cached dataSet context.shortCircuit = { data } in pre-execute middlewareSkips handler, returns cached data directly
Access "before" state on deleteMust manually fetch via context.db in beforeDeletebeforeDelete(id, context) — no data param

Shipped Plugins

Rate Limiting (createRateLimitPlugin)

Protects API endpoints from abuse with in-memory sliding window rate limiting.

  • Hooks used: pre-auth middleware
  • Approach: In-memory Map keyed by IP/user with periodic cleanup via setInterval
  • Use case: Prevent brute-force attacks, protect against DDoS, enforce API quotas
createRateLimitPlugin({ windowMs: 60000, max: 100, byIp: true })

Request Metadata (createRequestMetadataPlugin)

Extracts request metadata (IP, geo, user-agent) from HTTP headers and optionally auto-populates database columns on create/update.

  • Hooks used: Context extender + beforeCreate/beforeUpdate global hooks
  • Approach: Reads Cloudflare / X-Forwarded-For headers, populates context.requestMeta. Optional before-hooks write metadata to DB columns via data return value.
  • Use case: Signup geo-tracking, last-seen IP, fraud detection, analytics
createRequestMetadataPlugin({ autoPopulate: { ip: 'signupIp', country: 'signupCountry' }, resources: ['users'] })

Better Auth (createBetterAuthPlugin)

Integrates Better Auth sessions into the HandlerContext.

  • Hooks used: Context extender
  • Approach: Reads session from event.context or a custom getSession function, maps to context.user and context.permissions
  • Use case: Authentication for all auto-api endpoints without manual middleware
createBetterAuthPlugin({ getSession: async (event) => auth.api.getSession({ headers: event.headers }) })

Audit Log (createAuditLogPlugin)

Records every create, update, and delete operation in a dedicated audit log table.

  • Hooks used: beforeUpdate/beforeDelete (snapshot current state), afterCreate/afterUpdate/afterDelete (write log entry), buildSetup (GET /api/audit-logs)
  • Approach: After each mutating operation, write a row to an audit table with: resource, operation, record ID, user, timestamp, before/after data, IP.
  • Use case: Compliance (SOC2, GDPR), admin dashboards, debugging
createAuditLogPlugin({
  table: 'auditLogs',
  resources: ['users', 'orders'], // or '*' for all
  excludeFields: ['password'],
  async: true,
})

Webhook (createWebhookPlugin)

Sends HTTP webhook notifications when resources change.

  • Hooks used: afterCreate, afterUpdate, afterDelete global hooks
  • Approach: After mutations, POST a JSON payload to configured webhook URLs. Fire-and-forget with HMAC signing and retry logic.
  • Use case: Third-party integrations (Slack, Zapier, n8n), event-driven architectures
createWebhookPlugin({
  endpoints: [
    { url: 'https://hooks.slack.com/...', events: ['users.create'] },
    { url: 'https://n8n.example.com/webhook/...', events: ['*'] },
  ],
  signing: { secret: process.env.WEBHOOK_SECRET!, algorithm: 'sha256' },
  retry: { attempts: 3, backoffMs: 1000 },
})

Activity Feed (createActivityFeedPlugin)

Maintains a user-facing activity feed of changes across resources.

  • Hooks used: afterCreate, afterUpdate, afterDelete global hooks + buildSetup (GET /api/activities)
  • Approach: After mutations, write a human-readable activity entry to a table via context.db.
  • Use case: Dashboard "recent activity", notification center, collaboration features
createActivityFeedPlugin({
  table: 'activities',
  resources: ['articles', 'users', 'comments'],
  template: '{user.email} {operation}d a {resource}',
})

Slug Generation (createSlugPlugin)

Automatically generates URL slugs from a source field on create/update.

  • Hooks used: beforeCreate (generate slug), beforeUpdate (regenerate if source changed)
  • Approach: Transliterate to ASCII, kebab-case, check uniqueness via context.db, append -2, -3 if needed.
  • Use case: Blog articles, categories, any resource with URL-friendly identifiers
createSlugPlugin({
  resources: {
    articles: { source: 'title', target: 'slug' },
    categories: { source: 'name', target: 'slug' },
  },
  separator: '-',
  maxLength: 80,
})

Data Validation (createSchemaValidationPlugin)

Adds runtime schema validation to create/update operations. Works with any library implementing .safeParse() (Zod, Valibot, ArkType).

  • Hooks used: beforeCreate/beforeUpdate hooks (throw on validation failure)
  • Approach: Run validation schema against incoming data. Throw 422 with structured errors on failure.
  • Use case: Complex cross-field validation, conditional rules, schema enforcement
import { z } from 'zod'
createSchemaValidationPlugin({
  resources: {
    users: {
      create: z.object({ email: z.string().email(), age: z.number().min(18) }),
      update: z.object({ email: z.string().email().optional() }),
    },
  },
})

Data Export (createExportPlugin)

Adds CSV/JSON export endpoints to resources.

  • Hooks used: buildSetup (GET /api/:resource/export)
  • Approach: Register export endpoints via buildSetup.addServerHandler(). Supports field selection and format choice.
  • Use case: Admin panel "Export" buttons, reporting, data migration
createExportPlugin({
  formats: ['csv', 'json'],
  maxRows: 10000,
  resources: ['users', 'orders', 'articles'],
})

File Upload (createFileUploadPlugin)

Handles file uploads tied to resource records.

  • Hooks used: buildSetup (POST/DELETE /api/:resource/:id/upload)
  • Approach: Register upload/delete endpoints. Store files via local filesystem or NuxtHub Blob. Save URL in a DB column.
  • Use case: Profile avatars, article cover images, document attachments
createFileUploadPlugin({
  storage: 'local',
  resources: {
    users: { field: 'avatarUrl', maxSize: '2mb', accept: ['image/*'] },
    articles: { field: 'coverImage', maxSize: '5mb', accept: ['image/*'] },
  },
})

Revision History (createRevisionPlugin)

Stores a full history of every version of a record, enabling rollback.

  • Hooks used: beforeUpdate (snapshot current state), afterUpdate (write revision), buildSetup (list + restore endpoints)
  • Approach: Before each update, snapshot the current record. Provide GET /api/:resource/:id/revisions and POST /api/:resource/:id/revisions/:version/restore.
  • Use case: CMS content versioning, document editing, compliance, undo
createRevisionPlugin({
  resources: ['articles', 'pages'],
  maxRevisionsPerRecord: 50,
  table: 'revisions',
})

Cache (createCachePlugin)

In-memory caching for list/get operations with automatic invalidation on mutations.

  • Hooks used: pre-execute middleware (serve from cache via context.shortCircuit), post-execute middleware + afterList/afterGet (store in cache), afterCreate/afterUpdate/afterDelete (invalidate)
  • Approach: In-memory Map with TTL, LRU eviction, and per-resource invalidation.
  • Use case: Reduce database load for read-heavy workloads
createCachePlugin({
  ttlMs: 30000,
  maxEntries: 500,
  resources: ['articles', 'categories'],
  invalidateOn: ['create', 'update', 'delete'],
})

Search (createSearchPlugin)

Adds full-text-like search via SQL LIKE/ILIKE to list queries.

  • Hooks used: Resource-specific beforeList hooks (push conditions to context.additionalFilters)
  • Approach: Read ?q=searchterm from query, build OR(ILIKE(col1, '%q%'), ILIKE(col2, '%q%'), ...) and push into context.additionalFilters.
  • Use case: Search bars, filtering by text across multiple fields
createSearchPlugin({
  resources: {
    articles: { fields: ['title', 'body'], minLength: 3 },
    users: { fields: ['name', 'email'] },
  },
  queryParam: 'q',
})

Field Encryption (createEncryptionPlugin)

Transparently encrypts fields before write and decrypts after read using AES-256-GCM.

  • Hooks used: beforeCreate/beforeUpdate (encrypt), afterGet/afterList (decrypt via return value)
  • Approach: AES-256-GCM with random IV. Format: iv:tag:ciphertext (base64).
  • Use case: PII protection, HIPAA compliance, sensitive data at rest
createEncryptionPlugin({
  secret: process.env.ENCRYPTION_KEY!,
  resources: {
    users: ['ssn', 'taxId'],
    payments: ['cardNumber'],
  },
})

API Token (createApiTokenPlugin)

Full API token management with Bearer authentication, scope enforcement, caching, and token rotation. This is the most feature-rich shipped plugin — it covers the full lifecycle from token creation to request authentication.

  • Hooks used: Context extender (Bearer auth), pre-auth middleware (scope enforcement), beforeCreate/afterCreate (hash + one-time reveal), afterGet/afterList (mask hash), beforeUpdate/afterUpdate (block secret writes, rotation), afterDelete (cache invalidation), buildSetup (introspection endpoint)
  • Use case: API key management, service-to-service auth, CI/CD tokens, team/org-scoped tokens

How It Works

The plugin operates at two levels:

  1. Token CRUD lifecycle — hooks on your token resource(s) handle hashing on create, masking on read, rotation on update, and cache invalidation on delete.
  2. Request authentication — a context extender reads Authorization: Bearer <token>, hashes the raw token, looks it up in the DB (or cache), loads the owning user, and sets context.user + context.permissions. A separate pre-auth middleware then enforces token scopes.
Request with Bearer token
  │
  ├─ Context Extender (runs for every request)
  │   ├─ Already authenticated (session/cookie)? → skip
  │   ├─ Hash the raw token with SHA-256
  │   ├─ Check in-memory cache → hit? validate expiry, set context
  │   ├─ Cache miss → query all configured token tables
  │   ├─ Load user row via foreign key
  │   └─ Set context.user, context.permissions, context.tenant (org tokens)
  │
  ├─ Pre-auth Middleware (scope enforcement)
  │   ├─ No scopes or "*" → pass through (unrestricted)
  │   ├─ Map operation: list/get → "read"
  │   └─ Check "resource:operation" against token scopes → 403 if denied
  │
  └─ Normal authorize() runs (resource auth config — functions, object-level, field-level)

Both the scope check AND the resource auth config must pass. A token with articles:read scope still goes through any function-based permission check like read: (ctx) => ctx.user?.role === 'editor'.

Minimal Setup

createApiTokenPlugin({
  resources: {
    apiKeys: {
      userRelation: { field: 'userId', resource: 'users' },
    },
  },
})

This gives you: token hashing, one-time reveal, Bearer auth, in-memory cache, and hash masking on read. Scopes, expiry, and lastUsedAt are opt-in via additional columns.

Full Setup

createApiTokenPlugin({
  resources: {
    apiKeys: {
      secretField: 'key',                            // column storing the hash (default: 'key')
      userRelation: { field: 'userId', resource: 'users' },
      scopeField: 'scopes',                          // JSON array column
      expiresField: 'expiresAt',                     // timestamp column
      lastUsedField: 'lastUsedAt',                   // auto-updated on auth
    },
    teamApiKeys: {                                    // second token table (org-scoped)
      userRelation: { field: 'userId', resource: 'users' },
      orgField: 'organizationId',                    // sets context.tenant on auth
      scopeField: 'scopes',
    },
  },
  auth: {
    tokenPrefix: 'sk_',                              // prefix prepended to generated tokens
    // header: 'Authorization',                      // default
    // prefix: 'Bearer',                             // default
  },
  // hashAlgorithm: 'sha256',                        // default
  // tokenLength: 32,                                // bytes of randomness (default)
  cache: {
    enabled: true,                                    // default
    ttlMs: 300_000,                                   // 5 min (default)
    maxEntries: 1_000,                                // default
  },
  lastUsedDebounceMs: 60_000,                         // 1 min (default)
  mapUser: (row) => ({ id: row.id, email: row.email, roles: [row.role] }),
  getPermissions: (row) => row.role === 'admin' ? ['admin'] : [],
})

Configuration Reference

OptionTypeDefaultDescription
resourcesRecord<string, ApiTokenResourceConfig>requiredToken table(s) and their column config
auth.enabledbooleantrueEnable Bearer token authentication
auth.headerstring'Authorization'HTTP header to read
auth.prefixstring'Bearer'Header value prefix
auth.tokenPrefixstring''Prefix prepended to generated tokens (e.g. 'sk_')
hashAlgorithm'sha256' | 'sha512''sha256'Hash algorithm for token storage
tokenLengthnumber32Random bytes in generated tokens (64 hex chars)
cache.enabledbooleantrueEnable in-memory token cache
cache.ttlMsnumber300000Cache TTL in milliseconds
cache.maxEntriesnumber1000Max cached entries (oldest evicted first)
lastUsedDebounceMsnumber60000Debounce window for lastUsedAt DB writes
mapUser(row) => AuthUserauto-mapCustom user mapping from DB row
getPermissions(row) => string[]user.permissionsExtract permission strings from user row

Per-resource config (ApiTokenResourceConfig):

OptionTypeDefaultDescription
secretFieldstring'key'Column that stores the hashed token
userRelation.fieldstring'userId'FK column on the token table
userRelation.resourcestring'users'User table name
orgFieldstringOrganization FK column (enables team tokens)
scopeFieldstringJSON array column for scopes
expiresFieldstringTimestamp column for token expiry
lastUsedFieldstringTimestamp column, auto-updated on each auth
authEnabledbooleantrueWhether tokens from this table can authenticate

Scopes

Scopes are a coarse pre-filter that runs before the resource auth config. They answer "is this token allowed to touch this resource/operation at all?" — not "does the user have permission?" (that's the resource auth config's job).

Format: resource:operation strings stored as a JSON array in the scope column.

ScopeMeaning
"articles:read"Read (list/get) articles only
"articles:create"Create articles only
"articles:*"All operations on articles
"*:read"Read any resource
"*"Unrestricted (all resources, all operations)
null / emptyUnrestricted (backward compatible — simple tokens without scopes)

Operations are mapped: list and get both check against read. Other operations (create, update, delete) match directly.

Example flow — token with ["articles:read", "articles:create"]:

  1. GET /api/articles → scope check: articles:read matches → pass → resource auth runs
  2. POST /api/articles → scope check: articles:create matches → pass → resource auth runs
  3. DELETE /api/articles/1 → scope check: articles:delete not in scopes → 403
  4. GET /api/users → scope check: users:read not in scopes → 403

Token Lifecycle

CreatePOST /api/apiKeys { "name": "CI Key", "scopes": ["articles:read"] }

The plugin generates a random token (e.g. sk_a1b2c3d4...), hashes it with SHA-256, stores the hash. The response includes the raw token once — it is never returned again.

{ "data": { "id": 1, "name": "CI Key", "key": "sk_a1b2c3d4e5f6...", "scopes": ["articles:read"] } }

The userId is auto-set from context.user.id if not provided. For team tokens, orgField is auto-set from context.tenant.id.

ReadGET /api/apiKeys or GET /api/apiKeys/1

The hash is replaced with a masked preview: sk_...f6e5. The full hash is never exposed.

UpdatePATCH /api/apiKeys/1 { "name": "Renamed Key" }

Direct writes to the secret field are blocked. The name, scopes, and other fields can be updated normally.

RotatePATCH /api/apiKeys/1 { "_rotate": true }

Generates a new token, hashes it, invalidates the cache for the old token. The response includes the new raw token (one-time). The old token stops working immediately.

DeleteDELETE /api/apiKeys/1

Deletes the token record and invalidates the cache entry.

Introspection Endpoint

GET /api/_token/introspect — requires a valid Bearer token in the request.

Returns metadata about the token without exposing the hash:

{
  "data": {
    "resource": "apiKeys",
    "id": 1,
    "name": "CI Key",
    "scopes": ["articles:read"],
    "expiresAt": "2025-12-31T00:00:00.000Z",
    "lastUsedAt": "2025-06-15T10:30:00.000Z",
    "organizationId": null
  }
}

Returns 401 if the request is not authenticated via an API token.

Cache Strategy

The plugin maintains an in-memory cache with two maps:

  • Map<hash, CachedToken> — O(1) lookup by token hash
  • Map<"resource:id", hash> — O(1) invalidation by record

Cache entries expire after ttlMs (default 5 min). Periodic cleanup runs on a setInterval (non-blocking, .unref()). When max entries is reached, the oldest entry is evicted (Map insertion order). Delete and rotate operations invalidate the cache immediately via the reverse map.

Security

ConcernMitigation
Brute force256-bit entropy (32 random bytes) makes guessing infeasible. Pair with rate limit plugin for additional protection.
Timing attacksSHA-256 hash lookup in SQL. Constant-time comparison is not needed because the hash itself is the lookup key — a miss reveals nothing about valid hashes.
Token leakageConfigurable prefix (e.g. sk_) enables secret scanning tools (GitHub, GitGuardian) to detect leaked tokens in commits.
Over-privilegeScopes are a coarse gate. Resource auth config (functions, object-level, field-level) runs on top. Both must pass.
Stale tokensExpiry check on every auth. Cache TTL (5 min default) limits the window for deleted/expired tokens.
Hash exposureHash replaced with masked preview (sk_...f6e5) in GET/LIST. Raw token only returned on create and rotate.

Usage with Other Auth

The token plugin is designed to coexist with any session-based auth. The context extender skips when context.user is already set, so session auth always takes priority.

// server/autoapi-plugins.ts
export default [
  createBetterAuthPlugin({ ... }),   // Session auth for browsers
  createApiTokenPlugin({ ... }),     // Token auth for API clients — skips if session already set user
]

With a plain Nitro auth plugin (no Better Auth):

// server/plugins/auth.ts
export default defineNitroPlugin((nitro) => {
  nitro.hooks.hook('request', async (event) => {
    const session = getCookie(event, 'session')
    if (session) event.context.user = await getUserFromSession(session)
    // Token plugin handles API requests that don't have a session
  })
})

Soft Delete

Built into core — not a plugin.

The delete handler automatically checks for a deletedAt/deleted_at column and does UPDATE SET deletedAt = NOW() instead of DELETE. List/get handlers auto-filter soft-deleted records.


Summary

PluginStatusHooks Used
Rate LimitingShippedpre-auth middleware
Request MetadataShippedContext extender + before hooks
Better AuthShippedContext extender
Audit LogShippedbefore + after hooks + buildSetup
WebhookShippedafter hooks
Activity FeedShippedafter hooks + buildSetup
Slug GenerationShippedbefore hooks + context.db
Data ValidationShippedbefore hooks (throw on failure)
Data ExportShippedbuildSetup (new endpoints)
File UploadShippedbuildSetup + endpoints
Revision HistoryShippedbefore/after hooks + buildSetup
CacheShippedpre-execute middleware + context.shortCircuit
SearchShippedbeforeList + context.additionalFilters
Field EncryptionShippedbefore hooks + after hooks (return value)
API TokenShippedContext extender + pre-auth middleware + before/after hooks + buildSetup
Soft DeleteCore featureN/A

Need a Landing Page?

Modern landing pages with optional modules (blog, docs, forms, i18n). Let's discuss your project.

Build Your MVP

Full-stack SaaS development. Expert in database design, multi-tenancy, and scalable architecture.

Deployment Help

Dockerize your backend, set up CI/CD pipelines, deploy to Cloudflare or Hetzner. Early-stage setup.

Suggest a SaaS Tool

Missing a calculator or tool? Suggest what you'd like to see on our site.