Testing Locally

The third-party plugin developer program isn't open yet.

This page documents the plugin SDK and manifest format that power VeloCMS's own first-party integrations. VeloCMS is a hosted platform, so a plugin built from this page can't be installed on a VeloCMS site, and submitting or publishing one as an outside developer isn't something you can do today. There's no date for that to change. See Marketplace Policies for what's live now.

Plugin testing does not require a running VeloCMS instance. The @velocms/plugin-sdk/test-helpers package exports a mockVelocms factory that returns an in-memory context object with the same shape as the real PluginContext. You can pre-seed settings, inspect log calls, intercept fetch requests, and simulate KV store operations.

The mockVelocms factory

import { mockVelocms } from "@velocms/plugin-sdk/test-helpers";

const ctx = mockVelocms({
  tenant: { id: "t1", slug: "test-blog", locale: "en-US" },
  settings: { api_key: "test-key-us1", list_id: "abc123" },
  fetchResponse: { ok: true, status: 200, data: { id: "member_id" } },
  kvStore: { existing_key: "existing_value" },
});

Asserting on log output

ctx.log.info("something happened", { detail: "data" });

// ctx._logs records every log call
assert.equal(ctx._logs.length, 1);
assert.equal(ctx._logs[0]?.level, "info");
assert.equal(ctx._logs[0]?.message, "something happened");

Intercepting fetch calls

const ctx = mockVelocms({
  fetchResponse: { ok: true, status: 200, data: { success: true } },
});

await myHandler(payload, ctx as never);

// ctx._fetchCalls records every fetch invocation
assert.equal(ctx._fetchCalls.length, 1);
assert.ok(ctx._fetchCalls[0]?.url.includes("api.example.com"));
assert.equal(ctx._fetchCalls[0]?.options?.method, "POST");

Testing handler return values

For hooks like beforePostPublish where the return value modifies the publish action, assert that your handler mutates the payload correctly:

import { beforePostPublish } from "../src/index.js";

test("adds auto-tags from AI analysis", async () => {
  const ctx = mockVelocms({ settings: { openai_key: "sk-test" } });
  const payload = { post: { title: "Sourdough baking tips", tags: [], /* ... */ } };

  const result = await beforePostPublish(payload as never, ctx as never);

  assert.ok(Array.isArray(result.suggestedTags));
  assert.ok(result.suggestedTags!.length > 0);
});

Running tests

# Using velocms CLI (recommended)
velocms test

# Or directly with Vitest
npx vitest run

# Watch mode during development
velocms test --watch

End-to-end testing

Unit tests cover the handler logic in isolation. End-to-end testing — verifying that the hook actually fires when a post is published on a real tenant — runs against the hosted VeloCMS platform, and today only the VeloCMS team does that for its first-party integrations. VeloCMS is a hosted service and there is no local VeloCMS server to run on your own machine, so outside developers test with the unit-test helpers above. The velocms dev workflow, which hot-reloads a built dist/ on each compile cycle, is part of that team tooling.