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-sdkMinimal 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"requiresentryModuleruntimeType = "external-http"requiresexternalBaseUrlslugmust use lowercase letters, numbers, and hyphensversionandsdkVersionuse strict semverthemeExtensions.embeds[].targetPositionmust behead-endorbody-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 packThe SDK and platform both validate against the same contract, so manifest failures should now match between local validation and Admin upload.