Plugin Geliştirme

14 min readUpdated 27 Apr 2026

VeloCMS plugin'leri bir V8 isolate içinde çalışır. Bu, kodunuza yalnızca belirli bir API yüzeyine erişim izni veren ve başka hiçbir şeye karışmasına izin vermeyen katı bir sanal alan (sandbox) sınırı demek. Yani `require()`, `fs` veya `process.env` gibi şeyler yok. Elinizin altında sadece `velocms` global nesnesi (posts, media, settings, network, logger gibi) ve site sahibinin yetenek manifestosunda izin verdiği ne varsa o var. Bu mimarinin sebebi basit: bir başkasının blogunda üçüncü parti kod çalıştırmak ciddi bir güven meselesi. İşte `isolated-vm` de bu sorunu ayrı bir alt süreç (subprocess) yükü olmadan çözüyor.

Sandbox modeli

Her plugin çalıştırıldığında, sıfırdan bir `isolated-vm` Isolate'i oluşturulur. `VelocmsPluginAPI` köprüsü, etkileşim anında kopyalanan bir host nesnesi olarak içeri enjekte edilir, plugin kodunuz bu isolate içinde çalışır ve hook işlemi bittiğinde de isolate yok edilir. Bellek kullanımı isolate bellek limitiyle (varsayılan: 32MB) sınırlıdır. CPU süresi ise katı bir zaman aşımıyla (varsayılan: her hook için 5s) kısıtlanır. Eğer plugin'iniz bu limitlerden birini aşarsa, anında sonlandırılır ve hook zinciri onsuz yoluna devam eder.

Sandbox içinde yapamayacaklarınız şunlar: Node.js modüllerini import etmek, `require()` çağırmak, global `process` veya `__dirname` değişkenlerine erişmek, `setTimeout` ya da `setInterval` kullanmak (asenkron işlemler `velocms.network.fetch` üzerinden yürür) veya dosya sistemine dokunmak. Peki ne yapabilirsiniz? `velocms.content.posts.list()` ile içerikleri çekebilir, `velocms.settings` aracılığıyla ayarları okuyup yazabilir, izin verilen URL'lere HTTP istekleri atabilir ve `velocms.logger` ile yapılandırılmış loglar bırakabilirsiniz.

Yetenek manifestosu

Yetenekler, plugin'inizin `manifest.json` dosyasında beyan edilir ve plugin'iniz yayınlanmadan önce VeloCMS pazar yeri ekibi tarafından incelenir. Site sahibi, plugin'inizin tam olarak neye ihtiyaç duyduğunu görür ve eğer bu izinler içine sinmezse kurulumu reddedebilir. İşte tüm yeteneklerin listesi:

YetenekNe işe yararRisk seviyesi
`content:posts:read`Yazıları ve sayfaları okuma izni verir (`velocms.content.posts.list`, `velocms.content.posts.get`).Düşük
`content:posts:write`Yazı oluşturma/güncelleme izni verir (`velocms.content.posts.create`, `velocms.content.posts.update`).Orta
`settings:read`Site ayarlarını okuma izni verir (`velocms.settings.get`).Düşük
`settings:write`Site ayarlarını değiştirme izni verir (`velocms.settings.set`).Yüksek
`network:fetch`Manifest dosyasında belirtilen URL'lere HTTP istekleri yapma izni verir.Orta

Hook API'ı

Hook'lar, VeloCMS içerik yaşam döngüsünün belirli anlarında tetiklenir. Siz de plugin tanımınızda, bu hook isimlerine karşılık gelen handler fonksiyonları kaydedersiniz. Bütün hook'lar asenkrondur ve tipli bir payload alır; `beforePostPublish` ve `beforePostCreate` gibi bazı hook'lar ise değiştirilmiş bir payload verisi döndürebilir. Böyle bir durumda VeloCMS orijinal veri yerine sizin döndürdüğünüzü kullanır.

HookNe zaman tetiklenirPayload'u değiştirebilir mi
`beforePostCreate`Yeni bir yazı kaydedilmeden hemen önce.Evet
`afterPostCreate`Yeni bir yazı veritabanına kaydedildikten hemen sonra.Hayır
`beforePostPublish`Bir yazı yayınlanmadan hemen önce.Evet
`afterPostPublish`Bir yazı yayınlandıktan hemen sonra.Hayır

Hello World — baştan sona bir plugin

Hadi işe yarar en basit plugin'i yapalım: her yazı yayınlandığında çalışma log'una bir mesaj bırakan bir plugin. Sadece üç dosya lazım: `manifest.json`, `index.ts` ve bir de test dosyası.

manifest.json
{
      "id": "my-org.publish-logger",
      "name": "Publish Logger",
      "version": "1.0.0",
      "description": "Logs a structured message whenever a post is published.",
      "author": {
        "name": "My Org",
        "email": "[email protected]"
      },
      "capabilities": ["content:read"],
      "hooks": ["afterPostPublish"],
      "builtin": false,
      "engines": {
        "velocms": ">=1.0.0"
      },
      "pricing": {
        "model": "free"
      }
    }
json
index.ts
import type { VelocmsPluginAPI } from "@velocms/plugin-sdk";
    
    export async function afterPostPublish(
      payload: { post: { id: string; title: string; slug: string } },
      velocms: VelocmsPluginAPI
    ): Promise<void> {
      velocms.logger.info("Post published", {
        postId: payload.post.id,
        title: payload.post.title,
        url: `/blog/${payload.post.slug}`,
      });
    }
typescript

`mockVelocms` ile test etme

SDK, tamamen bellekte çalışan bir `VelocmsPluginAPI` oluşturan `mockVelocms` adında bir test yardımcısıyla birlikte gelir. Yani gerçek bir PocketBase bağlantısına falan gerek yok. Vitest içinde hook handler'larınızı hiçbir altyapı kurmadan test edebilirsiniz:

index.test.ts
import { describe, it, expect } from "vitest";
    import { createMockVelocms } from "@velocms/plugin-sdk/testing";
    import { afterPostPublish } from "./index";
    
    describe("publish-logger", () => {
      it("logs a message with post metadata when afterPostPublish fires", async () => {
        const velocms = createMockVelocms();
    
        await afterPostPublish(
          { post: { id: "post123", title: "Hello World", slug: "hello-world" } },
          velocms
        );
    
        expect(velocms.logger.info).toHaveBeenCalledWith(
          "Post published",
          expect.objectContaining({ postId: "post123" })
        );
      });
    });
typescript

Kill-switch (durdurma) mekanizması

Kurulu her plugin, site sahibi tarafından admin panelinden (Settings → Plugins) kaldırılmadan durdurulabilir. Bir plugin'i bu şekilde durdurmak, `site_settings.installed_plugins` içindeki `enabled` bayrağını `false` yapar. Böylece hook yükleyici, sonraki her çağrıda bu plugin'i atlar. Ne yeniden başlatma gerekir ne de yeniden deploy etme. Eğer bir plugin hataya neden olursa, VeloCMS art arda 3 hook hatasından sonra onu otomatik olarak devre dışı bırakır ve site sahibini e-posta ile bilgilendirir.

Pazar yerine gönderme

Plugin'iniz hazır olduğunda, `/developers` adresine gidin ve başvuru formunu doldurun. `manifest.json`, derlenmiş `index.js` dosyanız (veya TypeScript kaynak kodunuz) ve isteğe bağlı olarak bir `README.md` içeren bir ZIP dosyası yükleyeceksiniz. İnsan incelemesinden önce otomatik güvenlik taramaları yapıyoruz (beyan edilen yeteneklerle gerçekte erişilen API yüzeyini karşılaştırıyoruz). İlk başvurunuzun onaylanması 3-7 gün sürebilir, hazırlıklı olun.

Referans

Hook kataloğu

HookNe zaman tetiklenirPayload'u değiştirebilir miPayload tipi
`beforePostCreate`Yeni bir yazı kaydedilmeden hemen önce.Evet`{ post: Post }`
`afterPostCreate`Yeni bir yazı veritabanına kaydedildikten hemen sonra.Hayır`{ post: Post }`
`beforePostUpdate`Bir yazı güncellenmeden hemen önce.Evet`{ post: Post, oldPost: Post }`
`afterPostUpdate`Bir yazı güncellendikten hemen sonra.Hayır`{ post: Post }`
`beforePostPublish`Bir yazı yayınlanmadan hemen önce.Evet`{ post: Post }`
`afterPostPublish`Bir yazı yayınlandıktan hemen sonra.Hayır`{ post: Post }`
`beforePostDelete`Bir yazı silinmeden hemen önce.Hayır`{ postId: string }`
`afterPostDelete`Bir yazı silindikten hemen sonra.Hayır`{ postId: string }`

Yetenek manifestosu seçenekleri

YetenekNe işe yararRisk seviyesi
`content:posts:read`Yazıları ve sayfaları okuma izni verir (`velocms.content.posts.list`, `velocms.content.posts.get`).Düşük
`content:posts:write`Yazı oluşturma/güncelleme izni verir (`velocms.content.posts.create`, `velocms.content.posts.update`).Orta
`content:media:read`Medya kütüphanesini listeleme izni verir.Düşük
`content:media:write`Medya kütüphanesine dosya yükleme izni verir.Orta
`settings:read`Site ayarlarını okuma izni verir (`velocms.settings.get`).Düşük
`settings:write`Site ayarlarını değiştirme izni verir (`velocms.settings.set`).Yüksek
`network:fetch`Manifest dosyasında belirtilen URL'lere HTTP istekleri yapma izni verir.Orta

`VelocmsPluginAPI` yüzeyi

Aşağıdaki alanlar, her plugin sanal alanı içindeki global `velocms` nesnesinde mevcuttur. 'İsteğe bağlı' olarak işaretlenen alanlar, yalnızca ilgili yetenek `manifest.json` dosyasında beyan edildiğinde bulunur.

AlanTipİsteğe bağlıAçıklama
`content.posts``{ list, get, create, update, delete }`EvetYazı içeriğini yönetmek için metotlar. `read` ve `write` yeteneklerine bağlıdır.
`content.media``{ list, upload }`EvetMedya kütüphanesini yönetmek için metotlar.
`settings``{ get, set }`EvetSite ayarlarını okumak ve yazmak için metotlar.
`network.fetch``(url: string, options: RequestInit) => Promise<Response>`EvetHTTP istekleri yapmak için standart Fetch API'ı.
`logger``{ info, warn, error }`HayırYapılandırılmış logları plugin çalıştırma loglarına yazmak için.

Plugin durdurma (kill-switch) davranışı

DurumNasıl tetiklenirEtkisiKurtarma
Manuel olarak devre dışı bırakıldıSite sahibi admin panelinden plugin'i devre dışı bırakır.Plugin'in hiçbir hook'u çalıştırılmaz.Site sahibi admin panelinden tekrar etkinleştirir.
Otomatik olarak devre dışı bırakıldıBir hook art arda 3 kez hata verir.Plugin'in hiçbir hook'u çalıştırılmaz. Site sahibine e-posta gönderilir.Site sahibi admin panelinden tekrar etkinleştirir.

Sandbox limitleri

LimitVarsayılanAyarlanabilir miAşıldığında ne olur
Bellek32MBHayırIsolate sonlandırılır, hook hatası loglanır.
CPU Zamanı5 saniyeHayırIsolate sonlandırılır, hook hatası loglanır.
Log Çıktısı1MBHayırDaha fazla log mesajı yoksayılır.

Plugin manifestosu — minimum gerekli alanlar

manifest.json (minimum valid)
{
      "id": "your-org.plugin-name",
      "name": "Human Readable Name",
      "version": "1.0.0",
      "description": "One sentence.",
      "author": { "name": "Your Name", "email": "[email protected]" },
      "capabilities": [],
      "hooks": ["afterPostPublish"],
      "builtin": false,
      "engines": { "velocms": ">=1.0.0" },
      "pricing": { "model": "free" }
    }
json

Hook handler TypeScript imzası

Hook handler function signature
// Every hook handler follows this shape:
    export async function hookName(
      payload: HookPayload,          // typed per-hook (see Hook catalog above)
      velocms: VelocmsPluginAPI      // the sandbox API bridge
    ): Promise<void | Partial<HookPayload>> {
      // Return void for observation-only hooks (afterPost*, afterMember*, onDailyDigest)
      // Return Partial<HookPayload> to modify the subject (beforePostPublish only)
    }
typescript

`mockVelocms` ile test — tüm API

Full mockVelocms capabilities
import { createMockVelocms } from "@velocms/plugin-sdk/testing";
    
    const velocms = createMockVelocms({
      // Pre-seed posts for content:read testing
      posts: [{ id: "1", title: "Test Post", slug: "test-post", status: "published", ... }],
    
      // Pre-seed settings for settings API testing
      settings: { "my-plugin.api-key": "test-key-123" },
    });
    
    // The mock exposes vi.fn() spies on all methods:
    expect(velocms.logger.info).toHaveBeenCalledWith("...", expect.any(Object));
    expect(velocms.content.posts.list).toHaveBeenCalledOnce();
    
    // Reset between tests:
    velocms.reset();
typescript