Plugin Catalog
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:
| Capability | How | Notes |
|---|---|---|
| Modify data before write | beforeCreate/beforeUpdate return modified data | Works |
| Block an operation | Throw from any middleware or before-hook | Works |
| Side effects after mutation | afterCreate/afterUpdate/afterDelete hooks | Works |
| Enrich request context | extendContext() — adds data to HandlerContext | Runs once per request |
| Access database | context.db (full Drizzle instance) in any hook/middleware | No transaction control across hooks |
| Add new endpoints | buildSetup → addServerHandler() | Build-time only |
| Modify response data | Return values from afterGet/afterList/afterCreate/afterUpdate hooks | Hooks returning undefined are side-effect only (backward compatible) |
| Modify list query/filters | Push SQL conditions into context.additionalFilters in beforeList or middleware | List handler merges with and() |
| Short-circuit with cached data | Set context.shortCircuit = { data } in pre-execute middleware | Skips handler, returns cached data directly |
| Access "before" state on delete | Must manually fetch via context.db in beforeDelete | beforeDelete(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-authmiddleware - 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/beforeUpdateglobal 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.contextor a customgetSessionfunction, maps tocontext.userandcontext.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,afterDeleteglobal 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,afterDeleteglobal 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,-3if 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/beforeUpdatehooks (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/revisionsandPOST /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-executemiddleware (serve from cache viacontext.shortCircuit),post-executemiddleware +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
beforeListhooks (push conditions tocontext.additionalFilters) - Approach: Read
?q=searchtermfrom query, buildOR(ILIKE(col1, '%q%'), ILIKE(col2, '%q%'), ...)and push intocontext.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-authmiddleware (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:
- Token CRUD lifecycle — hooks on your token resource(s) handle hashing on create, masking on read, rotation on update, and cache invalidation on delete.
- 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 setscontext.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
| Option | Type | Default | Description |
|---|---|---|---|
resources | Record<string, ApiTokenResourceConfig> | required | Token table(s) and their column config |
auth.enabled | boolean | true | Enable Bearer token authentication |
auth.header | string | 'Authorization' | HTTP header to read |
auth.prefix | string | 'Bearer' | Header value prefix |
auth.tokenPrefix | string | '' | Prefix prepended to generated tokens (e.g. 'sk_') |
hashAlgorithm | 'sha256' | 'sha512' | 'sha256' | Hash algorithm for token storage |
tokenLength | number | 32 | Random bytes in generated tokens (64 hex chars) |
cache.enabled | boolean | true | Enable in-memory token cache |
cache.ttlMs | number | 300000 | Cache TTL in milliseconds |
cache.maxEntries | number | 1000 | Max cached entries (oldest evicted first) |
lastUsedDebounceMs | number | 60000 | Debounce window for lastUsedAt DB writes |
mapUser | (row) => AuthUser | auto-map | Custom user mapping from DB row |
getPermissions | (row) => string[] | user.permissions | Extract permission strings from user row |
Per-resource config (ApiTokenResourceConfig):
| Option | Type | Default | Description |
|---|---|---|---|
secretField | string | 'key' | Column that stores the hashed token |
userRelation.field | string | 'userId' | FK column on the token table |
userRelation.resource | string | 'users' | User table name |
orgField | string | — | Organization FK column (enables team tokens) |
scopeField | string | — | JSON array column for scopes |
expiresField | string | — | Timestamp column for token expiry |
lastUsedField | string | — | Timestamp column, auto-updated on each auth |
authEnabled | boolean | true | Whether 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.
| Scope | Meaning |
|---|---|
"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 / empty | Unrestricted (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"]:
GET /api/articles→ scope check:articles:readmatches → pass → resource auth runsPOST /api/articles→ scope check:articles:creatematches → pass → resource auth runsDELETE /api/articles/1→ scope check:articles:deletenot in scopes → 403GET /api/users→ scope check:users:readnot in scopes → 403
Token Lifecycle
Create — POST /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.
Read — GET /api/apiKeys or GET /api/apiKeys/1
The hash is replaced with a masked preview: sk_...f6e5. The full hash is never exposed.
Update — PATCH /api/apiKeys/1 { "name": "Renamed Key" }
Direct writes to the secret field are blocked. The name, scopes, and other fields can be updated normally.
Rotate — PATCH /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.
Delete — DELETE /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 hashMap<"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
| Concern | Mitigation |
|---|---|
| Brute force | 256-bit entropy (32 random bytes) makes guessing infeasible. Pair with rate limit plugin for additional protection. |
| Timing attacks | SHA-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 leakage | Configurable prefix (e.g. sk_) enables secret scanning tools (GitHub, GitGuardian) to detect leaked tokens in commits. |
| Over-privilege | Scopes are a coarse gate. Resource auth config (functions, object-level, field-level) runs on top. Both must pass. |
| Stale tokens | Expiry check on every auth. Cache TTL (5 min default) limits the window for deleted/expired tokens. |
| Hash exposure | Hash 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
| Plugin | Status | Hooks Used |
|---|---|---|
| Rate Limiting | Shipped | pre-auth middleware |
| Request Metadata | Shipped | Context extender + before hooks |
| Better Auth | Shipped | Context extender |
| Audit Log | Shipped | before + after hooks + buildSetup |
| Webhook | Shipped | after hooks |
| Activity Feed | Shipped | after hooks + buildSetup |
| Slug Generation | Shipped | before hooks + context.db |
| Data Validation | Shipped | before hooks (throw on failure) |
| Data Export | Shipped | buildSetup (new endpoints) |
| File Upload | Shipped | buildSetup + endpoints |
| Revision History | Shipped | before/after hooks + buildSetup |
| Cache | Shipped | pre-execute middleware + context.shortCircuit |
| Search | Shipped | beforeList + context.additionalFilters |
| Field Encryption | Shipped | before hooks + after hooks (return value) |
| API Token | Shipped | Context extender + pre-auth middleware + before/after hooks + buildSetup |
| Soft Delete | Core feature | N/A |
Request Metadata Plugin
The Request Metadata Plugin automatically captures request metadata (IP address, geolocation, user-agent, etc.) and makes it available throughout your API handlers. It supports both context-only access (for hooks and authorization) and optional database persistence.
Handler Overrides