DeesseJS

Plugins Overview

Introduction to the DeesseJS Plugin System

Plugins Overview

The DeesseJS Plugin System allows you to extend the functionality of your CMS with custom features, integrations, and enhancements. Plugins can add new admin pages, extend collections, integrate with external services, and modify dashboard behavior.

What are Plugins?

Plugins are reusable packages that extend DeesseJS functionality. They can:

  • Add new admin dashboard pages
  • Extend collections with additional fields
  • Integrate third-party services
  • Create custom API endpoints
  • Modify admin UI behavior
  • Add new content types
  • Implement custom workflows

Plugin Types

1. Admin Plugins

Extend the admin dashboard interface:

  • Custom pages and routes
  • Dashboard widgets
  • Custom field types
  • UI components
  • Navigation items

2. Collection Plugins

Extend content management:

  • Custom field types
  • Collection hooks
  • Validation rules
  • Default values
  • Computed fields

3. Integration Plugins

Connect with external services:

  • Authentication providers
  • Payment gateways
  • Email services
  • Analytics platforms
  • CDNs and storage

4. API Plugins

Extend API capabilities:

  • Custom endpoints
  • Middleware
  • Authentication strategies
  • Rate limiting
  • Caching strategies

Installing Plugins

Using the CLI

npx deessejs plugin add <plugin-name>

Using npm/yarn

# npm
npm install @deessejs/plugin-<name>

# yarn
yarn add @deessejs/plugin-<name>

# pnpm
pnpm add @deessejs/plugin-<name>

Registering Plugins

// deesse.config.ts
import { defineConfig } from '@deessejs/core'
import { myPlugin } from '@deessejs/plugin-example'

export const config = defineConfig({
  plugins: [
    myPlugin({
      // Plugin options
      option1: 'value',
      option2: true,
    }),
  ],
})

Plugin Dependencies on Extensions

Plugins can declare dependencies on extensions to access common infrastructure features. This allows plugins to use standardized interfaces for caching, logging, queues, and more.

Declaring Extension Dependencies

export const cachePlugin = definePlugin({
  name: 'cache-plugin',
  extensions: {
    // Require cache extension
    cache: true,

    // Optional logger extension
    logger: false,
  },

  activate(options, { extensions }) {
    // Extensions are available here
    const { cache, logger } = extensions

    return {
      hooks: {
        afterCreate: async ({ item }) => {
          // Use cache extension
          await cache.set(`item:${item.id}`, item, 3600)

          // Use logger extension if available
          if (logger) {
            logger.info('Item cached', { id: item.id })
          }
        },
      },
    }
  },
})

Required vs Optional Extensions

export const advancedPlugin = definePlugin({
  name: 'advanced-plugin',

  extensions: {
    // Required extensions - plugin won't load without them
    cache: true,      // Required
    logger: true,     // Required
    queue: true,      // Required

    // Optional extensions - plugin can work without them
    search: false,    // Optional
    events: false,    // Optional
  },

  activate(options, { extensions }) {
    const { cache, logger, queue } = extensions
    const { search, events } = extensions

    return {
      hooks: {
        afterCreate: async ({ item }) => {
          // These are guaranteed to be available
          await cache.set(`item:${item.id}`, item)
          logger.info('Created', { id: item.id })
          await queue.add('process', { itemId: item.id })

          // Check availability before using optional extensions
          if (search) {
            await search.index('items').addDocuments([item])
          }

          if (events) {
            await events.publish('item.created', item)
          }
        },
      },
    }
  },
})

Checking Extension Availability

export const flexiblePlugin = definePlugin({
  name: 'flexible-plugin',
  extensions: {
    cache: false,  // Optional
    logger: false, // Optional
  },

  activate(options, { extensions }) {
    return {
      hooks: {
        afterCreate: async ({ item }) => {
          // Gracefully handle missing extensions
          if (extensions.cache) {
            await extensions.cache.set(`item:${item.id}`, item, 3600)
          } else {
            console.warn('Cache not available, skipping')
          }

          if (extensions.logger) {
            extensions.logger.info('Item created', { id: item.id })
          }
        },
      },
    }
  },
})

Common Extension Usage Patterns

Cache Extension

export const seoPlugin = definePlugin({
  name: 'seo-plugin',
  extensions: {
    cache: true,
  },

  activate(options, { extensions }) {
    const { cache } = extensions

    return {
      api: {
        routes: [
          {
            method: 'GET',
            path: '/api/seo/score',
            handler: async (req) => {
              const url = new URL(req.url).searchParams.get('url')

              // Try cache first
              const cached = await cache.get(`seo:${url}`)
              if (cached) {
                return Response.json(cached)
              }

              // Calculate score
              const score = await calculateSEOScore(url)

              // Cache result
              await cache.set(`seo:${url}`, score, 3600)

              return Response.json(score)
            },
          },
        ],
      },
    }
  },
})

Queue Extension

export const newsletterPlugin = definePlugin({
  name: 'newsletter-plugin',
  extensions: {
    queue: true,
  },

  activate(options, { extensions }) {
    const { queue } = extensions

    // Register queue worker
    queue.process('send-newsletter', async (job) => {
      const { email, subject, content } = job.data
      await sendEmail(email, subject, content)
    })

    return {
      hooks: {
        afterCreate: async ({ collection, item }) => {
          if (collection === 'posts') {
            // Add job to queue
            await queue.add('send-newsletter', {
              email: 'subscribers@example.com',
              subject: `New Post: ${item.title}`,
              content: item.excerpt,
            })
          }
        },
      },
    }
  },
})

Logger Extension

export const auditPlugin = definePlugin({
  name: 'audit-plugin',
  extensions: {
    logger: true,
  },

  activate(options, { extensions }) {
    const { logger } = extensions

    return {
      hooks: {
        beforeCreate: async ({ collection, data }) => {
          logger.info('Creating item', { collection, data: sanitize(data) })
        },
        afterCreate: async ({ collection, item }) => {
          logger.info('Item created', { collection, id: item.id })
        },
        beforeUpdate: async ({ collection, id, data }) => {
          logger.info('Updating item', { collection, id, data: sanitize(data) })
        },
        afterDelete: async ({ collection, id }) => {
          logger.info('Item deleted', { collection, id })
        },
      },
    }
  },
})

Storage Extension

export const mediaPlugin = definePlugin({
  name: 'media-plugin',
  extensions: {
    storage: true,
  },

  activate(options, { extensions }) {
    const { storage } = extensions

    return {
      hooks: {
        afterCreate: async ({ collection, item }) => {
          if (collection === 'media' && item.file) {
            // Upload to storage
            const url = await storage.upload(`media/${item.id}`, item.file)

            // Update with URL
            await db.media.update({
              where: { id: item.id },
              data: { url },
            })
          }
        },
      },
    }
  },
})

Multiple Extension Providers

Plugins can work with any provider implementation:

// This plugin works with any cache provider (Redis, Memory, etc.)
export const cachingPlugin = definePlugin({
  name: 'caching-plugin',
  extensions: {
    cache: true,
  },

  activate(options, { extensions }) {
    // The plugin doesn't care which provider is used
    // It could be Redis, Memcached, or in-memory
    const { cache } = extensions

    return {
      // ... implementation
    }
  },
})

// Configuration with Redis
export const config = defineConfig({
  extensions: {
    cache: {
      provider: new RedisCacheProvider(redisClient),
    },
  },
  plugins: [cachingPlugin()],
})

Extension Version Compatibility

Plugins can specify minimum extension versions:

export const myPlugin = definePlugin({
  name: 'my-plugin',

  extensions: {
    cache: {
      version: '^2.0.0', // Require cache extension v2 or higher
    },
  },

  activate(options, { extensions }) {
    const { cache } = extensions

    // Cache is guaranteed to have v2+ features
    return {
      // ... implementation
    }
  },
})

Official Plugins

Content Plugins

  • @deessejs/plugin-blog: Complete blog with posts, categories, and tags
  • @deessejs/plugin-changelog: Version history and release notes management
  • @deessejs/plugin-seo: Advanced SEO for technical articles and tutorials
  • @deessejs/plugin-richtext: Advanced rich text editor
  • @deessejs/plugin-media: Enhanced media management
  • @deessejs/plugin-sitemap: Automatic sitemap generation
  • @deessejs/plugin-rss: RSS feed generation

Management Plugins

  • @deessejs/plugin-students: Student and course management
  • @deessejs/plugin-affiliates: Affiliate and partnership management

Integration Plugins

  • @deessejs/plugin-auth-nextauth: NextAuth.js integration
  • @deessejs/plugin-storage-s3: AWS S3 storage
  • @deessejs/plugin-storage-cloudinary: Cloudinary integration
  • @deessejs/plugin-webhook: Webhook notifications

Analytics & Monitoring

  • @deessejs/plugin-analytics: Analytics tracking and reporting

Utility Plugins

  • @deessejs/plugin-i18n: Multi-language support
  • @deessejs/plugin-scheduler: Scheduled tasks
  • @deessejs/plugin-cache: Advanced caching
  • @deessejs/plugin-logger: Enhanced logging
  • @deessejs/plugin-backup: Database backups

Creating Your Own Plugin

Basic Plugin Structure

// my-plugin/index.ts
import { definePlugin } from '@deessejs/core'

export const myPlugin = definePlugin({
  name: 'my-plugin',
  version: '1.0.0',
  activate(options) {
    // Plugin initialization logic
    console.log('Plugin activated with options:', options)

    return {
      // Extend collections
      collections: {
        extend: {
          posts: {
            fields: [
              {
                name: 'customField',
                type: 'string',
              },
            ],
          },
        },
      },

      // Add admin pages
      admin: {
        pages: [
          {
            id: 'my-page',
            title: 'My Page',
            path: '/my-page',
            component: './pages/MyPage',
          },
        ],
      },

      // Add API routes
      api: {
        routes: [
          {
            path: '/api/my-endpoint',
            handler: './handlers/myEndpoint',
          },
        ],
      },
    }
  },
})

Plugin Package Structure

my-plugin/
├── index.ts                 # Main plugin file
├── client/                  # Client-side code
│   ├── components/
│   └── hooks/
├── server/                  # Server-side code
│   ├── routes/
│   └── middleware/
├── admin/                   # Admin components
│   ├── pages/
│   └── widgets/
├── types.ts                 # TypeScript types
└── package.json

Plugin Configuration

Plugin Options

export const config = defineConfig({
  plugins: [
    myPlugin({
      enabled: true,
      apiKey: process.env.API_KEY,
      // Plugin-specific options
    }),
  ],
})

Conditional Plugin Activation

export const config = defineConfig({
  plugins: [
    ...(process.env.FEATURE_FLAG === 'enabled'
      ? [myPlugin()]
      : []
    ),
  ],
})

Plugin Hooks

Plugins can hook into various DeesseJS events:

Collection Hooks

export const myPlugin = definePlugin({
  activate(options) {
    return {
      hooks: {
        beforeCreate: async ({ collection, data }) => {
          console.log('Creating item in', collection)
          return data
        },
        afterCreate: async ({ collection, item }) => {
          console.log('Created item', item)
        },
        beforeUpdate: async ({ collection, id, data }) => {
          return data
        },
        afterUpdate: async ({ collection, id, item }) => {
          // Trigger webhook, clear cache, etc.
        },
        beforeDelete: async ({ collection, id }) => {
          // Check permissions, etc.
        },
        afterDelete: async ({ collection, id }) => {
          // Cleanup related resources
        },
      },
    }
  },
})

Server Hooks

export const myPlugin = definePlugin({
  activate(options) {
    return {
      hooks: {
        serverInit: async ({ app, config }) => {
          // Runs when server starts
        },
        request: async ({ req, res }) => {
          // Runs on each request
        },
      },
    }
  },
})

Plugin Discovery

Finding Plugins

  • Official Plugins: Browse the @deessejs org on npm
  • Community Plugins: Search npm for deessejs-plugin
  • GitHub: Search GitHub for deessejs-plugin

Evaluating Plugins

Before installing a plugin, check:

  • Maintenance: Last update date
  • Downloads: Popularity and adoption
  • License: Compatibility with your project
  • Documentation: Quality of docs
  • Issues: Open issues and PRs

Plugin Best Practices

Development

  • Follow TypeScript best practices
  • Provide comprehensive documentation
  • Include example configurations
  • Write tests for your plugin
  • Use semantic versioning

Performance

  • Minimize bundle size
  • Lazy load components when possible
  • Cache expensive operations
  • Avoid blocking the main thread

Security

  • Validate all inputs
  • Sanitize user data
  • Use secure defaults
  • Document security considerations
  • Keep dependencies updated

Compatibility

  • Support multiple Next.js versions
  • Test across different environments
  • Provide migration guides for breaking changes
  • Maintain backward compatibility when possible

Next Steps

On this page