🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@velocms/plugin-sdk

Package Overview
Dependencies
Maintainers
2
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@velocms/plugin-sdk

Plugin SDK for VeloCMS — types, manifests, lifecycle hooks for first-party + community plugins

latest
Source
npmnpm
Version
1.0.0-alpha.2
Version published
Maintainers
2
Created
Source

@velocms/plugin-sdk

TypeScript SDK for VeloCMS plugin development.

Installation

npm install @velocms/plugin-sdk --save-dev

Quick Start

import type { PluginManifest, HookContext, AfterPostCreatePayload } from "@velocms/plugin-sdk";

// 1. Define your manifest
export const manifest: PluginManifest = {
  $schema: "https://velocms.org/schemas/plugin-v2.json",
  name: "@myorg/my-plugin",
  displayName: "My Plugin",
  version: "1.0.0",
  description: "Sends a Slack notification on every new post.",
  author: { name: "My Org", email: "plugins@myorg.com" },
  type: "integration",
  category: "social",
  icon: "./icon.png",
  engines: { velocms: ">=1.0.0" },
  capabilities: {
    content: { read: true },
    network: true,
    network_allowlist: ["hooks.slack.com"],
  },
  pricing: { model: "free" },
  entry: { runtime: "./dist/runtime.js" },
  permissions_displayed_to_user: [
    "Read your posts",
    "Make HTTP requests to Slack",
  ],
};

// 2. Export hook handlers
export async function afterPostCreate(
  payload: AfterPostCreatePayload,
  ctx: HookContext
): Promise<void> {
  await ctx.fetch("https://hooks.slack.com/services/YOUR/WEBHOOK/URL", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      text: `New post published: ${payload.post.title}`,
    }),
  });
}

Phase 2.A: Event Bus

SDK version 1.0.0-alpha.2 adds a real-time event bus. Plugins can subscribe to VeloCMS system events and emit custom namespaced events.

Declare the capability

export const manifest: PluginManifest = {
  // ...
  capabilities: {
    events: {
      subscribe: ["post.published", "member.subscribed"],
      emit_custom: true,  // only if you call ctx.events.emit()
    },
  },
};

Subscribe to system events

Register subscriptions in onAppStart — VeloCMS fires this hook once when your plugin's runtime loads, which is where ctx.events.on() registrations belong (registering inside a request-scoped hook like afterPostCreate would re-subscribe the same handler on every invocation).

import type { OnAppStartPayload, HookContext } from "@velocms/plugin-sdk";

export async function onAppStart(
  _payload: OnAppStartPayload,
  ctx: HookContext
): Promise<void> {
  ctx.events.on("post.published", async ({ post }) => {
    await ctx.fetch("https://hooks.slack.com/services/xxx/yyy/zzz", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ text: `New post: ${post.title}` }),
    });
  });

  ctx.events.on("member.subscribed", async ({ member, source }) => {
    await ctx.kv.set("last_signup_source", source);
  });
}

Emit a custom event

Custom events must be namespaced to your plugin's org prefix (@org/). Other plugins can subscribe to your events by name. Emitting requires capabilities.events.emit_custom: true in your manifest.

// Emitting (from within any hook handler that receives ctx)
await ctx.events.emit("@myorg/slack-notifier:webhook-sent", {
  webhookUrl: "https://hooks.slack.com/...",
  postTitle: post.title,
});

// Subscribing (from another plugin's onAppStart)
ctx.events.on("@myorg/slack-notifier:webhook-sent", async (payload) => {
  await ctx.kv.set("last_slack_event", JSON.stringify(payload));
});

Full example: Slack Notifier

A plugin is just a module exporting a manifest plus one function per hook name it wants to handle — there is no definePlugin() wrapper. VeloCMS looks up handlers by matching the exported function name to the HookName union.

import type {
  PluginManifest,
  OnAppStartPayload,
  AfterPostPublishPayload,
  HookContext,
} from "@velocms/plugin-sdk";

export const manifest: PluginManifest = {
  $schema: "https://velocms.org/schemas/plugin-v2.json",
  name: "@myorg/slack-notifier",
  displayName: "Slack Notifier",
  version: "1.0.0",
  description: "Posts to Slack whenever a new post is published.",
  author: { name: "My Org", email: "plugins@myorg.com" },
  type: "integration",
  category: "social",
  icon: "./icon.png",
  engines: { velocms: ">=1.0.0" },
  capabilities: { network: true, network_allowlist: ["hooks.slack.com"] },
  pricing: { model: "free" },
  entry: { runtime: "./dist/runtime.js" },
  permissions_displayed_to_user: ["Make HTTP requests to Slack"],
};

export async function onAppStart(
  _payload: OnAppStartPayload,
  ctx: HookContext
): Promise<void> {
  ctx.log.info("Slack Notifier activated");
}

export async function afterPostPublish(
  payload: AfterPostPublishPayload,
  ctx: HookContext
): Promise<void> {
  const webhookUrl = await ctx.kv.get("slack_webhook_url");
  if (!webhookUrl) return;
  await ctx.fetch(webhookUrl, {
    method: "POST",
    body: JSON.stringify({ text: `New post: ${payload.post.title}` }),
  });
}

System event catalog

EventPayload
post.created{ post: HookPost }
post.updated{ post: HookPost; changedFields: string[] }
post.published{ post: HookPost }
post.unpublished{ post: HookPost }
post.deleted{ postId: string; slug: string }
member.subscribed{ member: HookMember; source: string }
member.unsubscribed{ memberId: string }
member.tier_changed{ member: HookMember; previousTier: string }
comment.posted{ commentId: string; postId: string; authorEmail?: string }
comment.approved{ commentId: string; postId: string }
comment.deleted{ commentId: string; postId: string }
page.published{ page: PageHookData }
media.uploaded{ mediaId: string; filename: string; mimeType: string }

Delivery semantics

  • Best-effort, not guaranteed. If the Railway container restarts mid-flight, events in transit may be missed. Plugin handlers must be idempotent.
  • 5-second timeout per handler. A hung handler is killed; the next handler still runs.
  • Circuit-breaker. 3 consecutive handler errors → circuit opens for 5 minutes. Events are not dispatched to a circuit-open handler. Reactivating the plugin resets the circuit.
  • 30-day audit log. All events are persisted in plugin_events for 30 days. Use this for debugging via the PocketBase admin panel.

Documentation

Full SDK reference: velocms.org/developers/sdk

Publishing to the Marketplace

  • Build your plugin: npm run build
  • Upload to velocms.org/developers/submit
  • The automated review pipeline will scan your bundle
  • Manual review by the VeloCMS team (2-5 business days)
  • Published to the marketplace

License

MIT

Keywords

velocms

FAQs

Package last updated on 20 Jul 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts