Tutorial: Build a Custom Plugin
Learn to create an AutoDeployBase plugin from scratch.
What We're Building
A "Notifications" plugin that adds:
- In-app notifications
- Email notifications
- Admin notification center
- User preferences
Step 1: Create Plugin Structure
npx autodeploybase plugin create notifications
This creates:
plugins/notifications/
├── manifest.json
├── generator.js
├── templates/
└── README.md
Step 2: Define the Manifest
plugins/notifications/manifest.json
{
"name": "notifications",
"version": "1.0.0",
"description": "In-app and email notifications",
"author": "Your Name",
"category": "feature",
"tier": "public",
"frameworks": ["next", "nuxt", "sveltekit"],
"dependencies": {
"sonner": "^1.0.0"
},
"pluginDependencies": ["email"],
"settings": {
"schema": {
"enabled": {
"type": "boolean",
"label": "Enable Notifications",
"default": true
},
"emailEnabled": {
"type": "boolean",
"label": "Enable Email Notifications",
"default": true
},
"maxPerUser": {
"type": "number",
"label": "Max Notifications per User",
"default": 100,
"min": 10,
"max": 1000
}
}
},
"adminMenu": {
"label": "Notifications",
"icon": "Bell",
"href": "/admin/notifications"
}
}
Step 3: Write the Generator
plugins/notifications/generator.js
import { sdk } from 'autodeploybase';
export default async function generate(context) {
const { projectPath, framework, settings } = context;
console.log('Generating notifications plugin...');
// 1. Copy framework-specific templates
await sdk.copyTemplates({
source: `./templates/${framework}`,
destination: projectPath,
variables: {
emailEnabled: settings.emailEnabled,
maxPerUser: settings.maxPerUser
}
});
// 2. Copy shared templates
await sdk.copyTemplates({
source: './templates/shared',
destination: projectPath
});
// 3. Register database schema
await sdk.registerPrismaSchema(`
model Notification {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
type NotificationType
title String
message String
data Json?
read Boolean @default(false)
createdAt DateTime @default(now())
@@index([userId])
@@index([read])
}
model NotificationPreference {
id String @id @default(cuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
inApp Boolean @default(true)
email Boolean @default(true)
marketing Boolean @default(false)
}
enum NotificationType {
INFO
SUCCESS
WARNING
ERROR
}
`);
// 4. Register API routes
await sdk.registerAPIRoutes({
routes: [
{ path: '/api/notifications', methods: ['GET', 'POST'] },
{ path: '/api/notifications/:id', methods: ['PATCH', 'DELETE'] },
{ path: '/api/notifications/mark-all-read', methods: ['POST'] },
{ path: '/api/notifications/preferences', methods: ['GET', 'PUT'] }
]
});
// 5. Register admin menu
await sdk.registerAdminMenu({
id: 'notifications',
label: 'Notifications',
icon: 'Bell',
children: [
{ id: 'all', label: 'All Notifications', href: '/admin/notifications' },
{ id: 'send', label: 'Send Notification', href: '/admin/notifications/send' }
]
});
// 6. Register provider
await sdk.registerProvider({
name: 'NotificationProvider',
import: '@/plugins/notifications/provider',
order: 20
});
// 7. Register settings
await sdk.registerSettings({
pluginId: 'notifications',
schema: {
enabled: { type: 'boolean', default: true },
emailEnabled: { type: 'boolean', default: true },
maxPerUser: { type: 'number', default: 100 }
}
});
console.log('Notifications plugin generated!');
}
Step 4: Create Templates
Shared Library
templates/shared/lib/notifications.ts
import { prisma } from '@/lib/db';
export interface CreateNotificationInput {
userId: string;
type: 'INFO' | 'SUCCESS' | 'WARNING' | 'ERROR';
title: string;
message: string;
data?: Record<string, any>;
}
export async function createNotification(input: CreateNotificationInput) {
// Check user preferences
const prefs = await prisma.notificationPreference.findUnique({
where: { userId: input.userId }
});
if (prefs && !prefs.inApp) {
return null;
}
// Create notification
const notification = await prisma.notification.create({
data: input
});
// Send email if enabled
{{#if emailEnabled}}
if (prefs?.email) {
await sendNotificationEmail(input);
}
{{/if}}
return notification;
}
export async function getUserNotifications(userId: string) {
return prisma.notification.findMany({
where: { userId },
orderBy: { createdAt: 'desc' },
take: {{maxPerUser}}
});
}
export async function markAsRead(notificationId: string, userId: string) {
return prisma.notification.updateMany({
where: { id: notificationId, userId },
data: { read: true }
});
}
export async function markAllAsRead(userId: string) {
return prisma.notification.updateMany({
where: { userId, read: false },
data: { read: true }
});
}
React Provider (Next.js)
templates/next/plugins/notifications/provider.tsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { Toaster, toast } from 'sonner';
interface Notification {
id: string;
type: string;
title: string;
message: string;
read: boolean;
createdAt: string;
}
interface NotificationContextType {
notifications: Notification[];
unreadCount: number;
markAsRead: (id: string) => Promise<void>;
markAllAsRead: () => Promise<void>;
refresh: () => Promise<void>;
}
const NotificationContext = createContext<NotificationContextType | null>(null);
export function NotificationProvider({ children }: { children: React.ReactNode }) {
const [notifications, setNotifications] = useState<Notification[]>([]);
const unreadCount = notifications.filter(n => !n.read).length;
async function refresh() {
const res = await fetch('/api/notifications');
const data = await res.json();
setNotifications(data);
}
async function markAsRead(id: string) {
await fetch(`/api/notifications/${id}`, {
method: 'PATCH',
body: JSON.stringify({ read: true })
});
await refresh();
}
async function markAllAsRead() {
await fetch('/api/notifications/mark-all-read', { method: 'POST' });
await refresh();
}
useEffect(() => {
refresh();
// Poll for new notifications
const interval = setInterval(refresh, 30000);
return () => clearInterval(interval);
}, []);
return (
<NotificationContext.Provider value={{
notifications,
unreadCount,
markAsRead,
markAllAsRead,
refresh
}}>
{children}
<Toaster position="top-right" />
</NotificationContext.Provider>
);
}
export function useNotifications() {
const context = useContext(NotificationContext);
if (!context) {
throw new Error('useNotifications must be used within NotificationProvider');
}
return context;
}
API Routes
templates/next/app/api/notifications/route.ts
import { NextResponse } from 'next/server';
import { requireAuth } from '@/lib/auth';
import { getUserNotifications, createNotification } from '@/lib/notifications';
export async function GET(request: Request) {
const user = await requireAuth(request);
const notifications = await getUserNotifications(user.id);
return NextResponse.json(notifications);
}
export async function POST(request: Request) {
const user = await requireAuth(request);
const body = await request.json();
const notification = await createNotification({
userId: user.id,
...body
});
return NextResponse.json(notification, { status: 201 });
}
Admin Page
templates/next/app/admin/notifications/page.tsx
import { prisma } from '@/lib/db';
import { NotificationsTable } from '@/plugins/notifications/components/NotificationsTable';
export default async function AdminNotificationsPage() {
const notifications = await prisma.notification.findMany({
include: { user: { select: { email: true, name: true } } },
orderBy: { createdAt: 'desc' },
take: 100
});
return (
<div>
<h1>Notifications</h1>
<NotificationsTable notifications={notifications} />
</div>
);
}
Step 5: Test the Plugin
# Create test project
npx autodeploybase init test-notifications \
--framework next \
--archetype blank \
--plugins ./plugins/notifications
cd test-notifications
npm install
npm run dev
Step 6: Add Documentation
plugins/notifications/README.md
# Notifications Plugin
Add in-app and email notifications to your AutoDeployBase project.
## Installation
\`\`\`bash
npx autodeploybase add notifications
\`\`\`
## Configuration
\`\`\`json
{
"plugins": {
"notifications": {
"settings": {
"emailEnabled": true,
"maxPerUser": 100
}
}
}
}
\`\`\`
## Usage
### Create Notification
\`\`\`typescript
import { createNotification } from '@/lib/notifications';
await createNotification({
userId: user.id,
type: 'SUCCESS',
title: 'Welcome!',
message: 'Thanks for signing up.'
});
\`\`\`
### In Components
\`\`\`tsx
import { useNotifications } from '@/plugins/notifications/provider';
function NotificationBell() {
const { unreadCount, notifications } = useNotifications();
return (
<div>
<span>{unreadCount} unread</span>
</div>
);
}
\`\`\`
Step 7: Publish
# Validate
npx autodeploybase plugin validate ./plugins/notifications
# Publish to npm
cd plugins/notifications
npm publish
# Or to AutoDeployBase registry
npx autodeploybase plugin publish
Next Steps
- Add real-time updates with WebSockets
- Add notification categories
- Add scheduled notifications
- Add notification templates