Skip to main content

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