Platform overview
Plugins are the way to extend Agentful without patching core files. A plugin is
installed from a validated repository, staged under .shop/plugins, and
activated only after its manifest and runtime contributions match.
Extension Surfaces
Section titled “Extension Surfaces”Render plugin UI at named product and layout slots declared in the manifest.
Add full pages under /plugins/:pluginId/... without modifying the core router.
Expose new AI-admin tools only when the manifest declares the tool and permission.
Subscribe to durable commerce events with at-least-once delivery and idempotent handlers.
Add protected plugin routes under the plugin namespace.
Create plugin-owned tables using guarded migrations and reserved prefixes.
Management Flow
Section titled “Management Flow”Core MCP/API tools handle lifecycle:
plugins.listplugins.getplugins.healthplugins.installplugins.upgradeplugins.enableplugins.disableplugins.uninstallplugins.purge
Uninstall disables runtime behavior and removes staged files while preserving plugin data by default. Destructive data removal requires explicit confirmation.
Runtime Safety
Section titled “Runtime Safety”Agentful blocks common plugin hazards:
- Non-HTTPS repository sources.
- Secret-like files in packages.
- Package install hooks.
- Local, SSH, or HTTP dependency sources.
- Manifest paths escaping the plugin package.
- Runtime contributions not declared in the manifest.
- Protected core table alteration from plugin migrations.
- Production build commands unless a controlled build image is configured.
SDK Shape
Section titled “SDK Shape”Plugin authors import from @shop/plugin-sdk while the internal package scope
is still being renamed.
import { defineShopPlugin } from '@shop/plugin-sdk';
export default defineShopPlugin({ register(context) { context.mcpTool({ name: 'loyalty.points_preview', title: 'Preview loyalty points', description: 'Estimate points for an order.', permission: 'users:read', async handler(args) { return { points: Math.floor(Number(args.priceInCents ?? 0) / 100) }; }, });
context.onEvent('orders.paid', async (event, helpers) => { // Use helpers.eventId or helpers.deliveryId for idempotency. console.log(event.type, helpers.pluginId); }); },});