Jiffoo Docs

Plugin Development

Current plugin manifest and SDK model for Jiffoo Mall

Plugin Development

Jiffoo Mall plugins now use one shared manifest contract across the platform installer, runtime, and SDK. The authoritative manifest shape is manifest.json, validated by the shared plugin contract and consumed by both Admin/API and @jiffoo/plugin-sdk.

Runtime Modes

  • external-http: recommended for third-party plugins. You deploy an HTTP service and the platform proxies requests to it.
  • internal-fastify: reserved for monorepo or platform-maintained plugins loaded inside the API service.

Install the SDK

npm install @jiffoo/plugin-sdk

Minimal Manifest

{
  "schemaVersion": 1,
  "slug": "my-payment-plugin",
  "name": "My Payment Plugin",
  "version": "1.0.0",
  "description": "A custom payment integration for Jiffoo Mall",
  "runtimeType": "external-http",
  "externalBaseUrl": "https://my-payment-plugin.example.com",
  "permissions": [],
  "category": "payment",
  "capabilities": ["payment.process", "payment.refund"],
  "minApiVersion": "v1",
  "sdkVersion": "1.2.0"
}

Key rules:

  • runtimeType = "internal-fastify" requires entryModule
  • runtimeType = "external-http" requires externalBaseUrl
  • slug must use lowercase letters, numbers, and hyphens
  • version and sdkVersion use strict semver
  • themeExtensions.embeds[].targetPosition must be head-end or body-end

Minimal External Plugin

import express from 'express';
import {
  createContextMiddleware,
  createRoute,
  definePlugin,
  verifySignature,
} from '@jiffoo/plugin-sdk';

const app = express();
app.use(express.json());

const plugin = definePlugin({
  schemaVersion: 1,
  slug: 'my-payment-plugin',
  name: 'My Payment Plugin',
  version: '1.0.0',
  description: 'A custom payment integration for Jiffoo Mall',
  runtimeType: 'external-http',
  externalBaseUrl: 'https://my-payment-plugin.example.com',
  permissions: [],
  category: 'payment',
  capabilities: ['payment.process', 'payment.refund'],
});

app.get('/health', (_req, res) => {
  res.json({ status: 'healthy', version: plugin.getManifest().version });
});

app.use('/api', createContextMiddleware());

const processPaymentRoute = createRoute(
  '/api/process-payment',
  async (req, res) => {
    res.json({ success: true, requestId: req.pluginContext?.requestId });
  },
  { method: 'POST', requiresAuth: true }
);

plugin.addRoute(processPaymentRoute);

app.post('/api/process-payment', async (req, res) => {
  const isValid = verifySignature(
    process.env.PLUGIN_SECRET!,
    req.method,
    req.path,
    req.body,
    req.headers['x-platform-timestamp'] as string,
    req.headers['x-platform-signature'] as string
  );

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  await processPaymentRoute.handler(req as any, res as any, (req as any).pluginContext);
});

Lifecycle Hooks

For external-http plugins you can expose lifecycle endpoints with defineLifecycleHooks() and declare them in the manifest:

{
  "lifecycle": {
    "onInstall": true,
    "onEnable": true,
    "onDisable": true
  }
}
import { defineLifecycleHooks } from '@jiffoo/plugin-sdk';

const lifecycle = defineLifecycleHooks({
  onInstall: async (context) => {
    console.log(`Installed for ${context.installationId}`);
  },
  onEnable: async (context) => {
    console.log(`Enabled ${context.instanceKey}`);
  },
});

app.post('/__lifecycle/:hookName', lifecycle.httpHandler());

Theme Extensions

Plugins can publish storefront UI extensions through the same manifest:

{
  "themeExtensions": {
    "blocks": [
      {
        "extensionId": "upsell-card",
        "name": "Upsell Card",
        "dataEndpoint": "/api/upsell-card"
      }
    ],
    "embeds": [
      {
        "extensionId": "tracking-pixel",
        "name": "Tracking Pixel",
        "targetPosition": "body-end",
        "dataEndpoint": "/api/tracking-pixel"
      }
    ]
  }
}

Validation and Packaging

npx @jiffoo/plugin-sdk validate
npx @jiffoo/plugin-sdk build
npx @jiffoo/plugin-sdk pack

The SDK and platform both validate against the same contract, so manifest failures should now match between local validation and Admin upload.

On this page