Help us shape the future of Go! Take the 2026 Go Developer Survey

sdk

package module
v0.13.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

Hatamoto Plugin SDK

hatamoto.dev/sdk is the plugin contract: hook names and payload types (events.go, hooks.go), the plugin.json manifest (manifest.go) and the export-name rules of the WASM ABI. The root package depends on nothing but the standard library, never imports Hatamoto core packages, and the module has its own go.mod.

go get hatamoto.dev/sdk@v0.13.0

Releases are plain vX.Y.Z tags of this repository, where X.Y.Z is the Version constant in version.go. This repository is a one-way export of the backend/pkg/sdk directory of the Hatamoto core repository, which stays the source of truth, so changes are made upstream in Hatamoto core and exported here. Report SDK bugs and send proposals as issues or pull requests on this repository (github.com/Zen-Labs-Corp/hatamoto-sdk); a pull request is applied upstream and arrives back in the next release.

hatamoto.dev/sdk/guest is the Go guest layer: a thin wrapper over the Extism Go PDK (github.com/extism/go-pdk, the module's only dependency) that provides OnAction / OnFilter registration and the JSON plumbing. It only builds the PDK side for GOOS=wasip1.

Everything about writing a plugin (export names, JSON schemas of every payload, manifest, limits, the Go quick start) is in the plugin documentation on https://hatamoto.dev/docs (the plugin API reference ships with Hatamoto core). The first-party plugins gtm and searchconsole are real examples built on this module.

Versioning

Semantic, with the pre-1.0 rules in STABILITY.md: while the SDK is 0.x a minor release may change behaviour, but only after the change was announced as a deprecation notice in an earlier SDK minor and recorded in the release notes. A patch never breaks a covered surface, except a security exception, which says so in its release note and advisory. Version is the current version; CompatibleWith lists every earlier 0.x minor from 0.5.0, and a host keeps serving all of them until 1.0. A plugin declares the oldest range it needs in plugin.json (today "sdk": "^0.11", so hosts before v0.31.0 still load it); a host refuses a plugin whose range matches neither.

SDK Change
0.13.0 locale field on the entity payloads (EntityRef, EntityEvent, EntitySummary, EntityDraft). Purely additive
0.12.0 site.scripts permission: a plugin's browser JavaScript (assets/plugin.js) is served and injected only once it is granted
0.11.0 builder.data filter + permission and builder_sources manifest field: plugin data sources (values and item lists) for the page builder
0.10.0 Entity host functions: parent_id on create/update, parent_id/path/url on Entity, entities.get by path or parent_id+slug(+schema_slug)
0.5.0 Native Go plugins removed. Plugins are sandboxed WASM modules; payloads are JSON with snake_case fields; guest package added; no pgx or router types
0.4.0 and earlier Native, compiled-in plugins (gone)

Test

go vet ./... && go test ./...
GOOS=wasip1 GOARCH=wasm go vet ./guest

Documentation

Overview

Package sdk is the Hatamoto plugin contract: hook names and payload DTOs, the plugin.json manifest, and the export-name rules of the WASM ABI. It has no dependencies beyond the standard library, so hosts and guests share it. Subpackage guest is the Go guest layer over the Extism PDK.

Versioning: semantic. Minor releases are additive only; breaking changes bump the major version (before 1.0, the minor). A manifest declares the range it was built against and the host refuses a plugin whose range does not contain Version.

Plugin authors start with the guest subpackage and the worked example in examples/plugins/hello of the Hatamoto repository; the payload schemas of every hook are in docs/PLUGIN_API.md. This module never imports Hatamoto's internal packages, and the server is not a library.

Index

Constants

View Source
const (
	MaxBuilderDataRequests = 16   // distinct requests per render
	MaxBuilderDataItems    = 48   // items per result (and per request limit)
	MaxBuilderDataString   = 2048 // bytes per string, valid UTF-8
	MaxBuilderDataAttrs    = 16   // Attrs keys per item
)

Caps of the builder.data contract. The host drops a whole result that breaks them.

View Source
const (
	ErrCapabilityDenied = "capability_denied" // capability not granted; HostError.Capability names it
	ErrInvalidArgument  = "invalid_argument"  // malformed or out-of-range arguments
	ErrNotFound         = "not_found"
	ErrRateLimited      = "rate_limited"
	ErrBlocked          = "blocked" // refused by policy (http.fetch allowlist, SSRF guard, SQL guard)
	ErrReentrant        = "reentrant"
	ErrTimeout          = "timeout"
	ErrUnavailable      = "unavailable" // the backing service is not configured or reachable
	ErrInternal         = "internal"
	ErrUnknownFunction  = "unknown_function"
)

Error codes of a failed host call.

View Source
const (
	FnEntitiesGet    = "entities.get"    // {id} | {path} | {slug, parent_id?, schema_slug? (with parent_id)} -> Entity
	FnEntitiesList   = "entities.list"   // {schema_slug, status?, search?, limit?, cursor?} -> {items, next_cursor}
	FnEntitiesCreate = "entities.create" // {schema_slug, name, slug?, locale?, parent_id?, attributes} -> Entity
	FnEntitiesUpdate = "entities.update" // {id, name?, slug?, locale?, parent_id? (null = root), attributes?} -> Entity
	FnEntitiesStatus = "entities.set_status"
	FnEntitiesDelete = "entities.delete"

	FnTermsList = "terms.list" // {kind?, search?, limit?} -> {items}
	FnTermsGet  = "terms.get"  // {kind, slug} -> Term
	FnTermsSave = "terms.save" // {kind, slug, name, description?} -> Term (create or update)
	FnTermsDel  = "terms.delete"

	FnMenusGet  = "menus.get"  // {slug} -> Menu with items
	FnMenusList = "menus.list" // {} -> {items}
	FnMenusSave = "menus.save" // {slug, name, location?} -> Menu (create or update)

	FnMenusAddItem = "menus.add_item"    // {menu_slug, label, link_type, url?, entity_id?, parent_id?, open_in_new_tab?}
	FnMenusDelItem = "menus.delete_item" // {id}

	FnMediaGet    = "media.get"    // {id}
	FnMediaList   = "media.list"   // {limit?, offset?}
	FnMediaUpload = "media.upload" // {filename, mime_type, data_b64} -> Media

	FnRedirectsList   = "redirects.list"
	FnRedirectsCreate = "redirects.create" // {from_path, to_path, status_code?}
	FnRedirectsDelete = "redirects.delete" // {id}

	FnFormsList        = "forms.list"
	FnFormsSubmissions = "forms.submissions" // {form_slug, limit?, offset?}

	FnSettingsGet = "settings.get" // {} -> the plugin's own settings object
	FnLog         = "log"          // {level, message}
	FnMailSend    = "mail.send"    // {to[], subject, text?, html?}
	FnHTTPFetch   = "http.fetch"   // {method?, url, headers?, body?, body_b64?, timeout_ms?} -> {status, headers, body, body_b64?}
	FnKVGet       = "kv.get"       // {key} -> {found, value}
	FnKVSet       = "kv.set"       // {key, value, ttl_seconds?}
	FnKVDelete    = "kv.delete"    // {key}
	FnKVList      = "kv.list"      // {prefix?, limit?} -> {keys}
	FnDBQuery     = "db.query"     // {sql, args?} -> {columns, rows, truncated}
	FnDBExec      = "db.exec"      // {sql, args?} -> {rows_affected}
)

Host function names, by capability.

View Source
const (
	DefaultMemoryPages = 256  // 16 MiB
	MinMemoryPages     = 64   // 4 MiB: a standard Go guest does not start below this
	MaxMemoryPages     = 1024 // 64 MiB

	// Per-hook timeouts (Limits.HookTimeoutsMS).
	DefaultFilterTimeoutMS = 250
	DefaultActionTimeoutMS = 10000
	MaxTimeoutMS           = 10000

	DefaultMaxInstances = 4
	MaxMaxInstances     = 16
	DefaultOutputBytes  = 1 << 20 // 1 MiB
	MaxOutputBytes      = 4 << 20 // 4 MiB
)

Limit defaults and bounds enforced by Manifest.Validate.

View Source
const (
	BuilderSourceValue = "value"
	BuilderSourceItems = "items"

	MaxBuilderSources = 8
)

Builder source kinds and value classes.

View Source
const (
	MaxRoutes    = 64
	MaxCronJobs  = 16
	MaxFuncs     = 64
	MaxCookies   = 8
	MinCronEvery = time.Minute
)

Limits on the manifest's lists.

View Source
const DefaultUIEntry = "index.html"

DefaultUIEntry is the AdminUI entry when none is declared.

View Source
const HostFunc = "hm_call"

HostFunc is the name of the one host function every plugin may import (wasm module "extism:host/user"). It takes a pointer to a JSON CallRequest in Extism memory and returns a pointer to a JSON CallResponse.

View Source
const MaxSitemapAdditions = 5000

MaxSitemapAdditions is the most URLs one plugin may add to one sitemap file per call; the host drops the whole answer beyond it.

View Source
const RejectPrefix = "hatamoto:reject:"

RejectPrefix starts the error message of a guest filter export that refuses the operation (entity.before_save only); the rest of the message is the reason surfaced to the API client. The guest package's Reject does this.

View Source
const Version = "0.13.0"

Version is the SDK semantic version. 0.5.0 replaced native Go plugins with sandboxed WASM modules; 0.6.0 adds capabilities (permissions, host functions, routes, cron, template funcs) and the form.submitted and render.page hooks; 0.7.0 renames the HeadContext and SitemapURLs per-site field to `language`; 0.8.0 makes sitemap.urls additions-only, turns the denied-capability error into {"code":"capability_denied","capability":"<name>"} and replaces limits.timeout_ms with per-hook limits.hook_timeouts_ms (see docs/PLUGIN_API.md); 0.9.0 slims the render.page payload (blocks by id, the host re-attaches content; `render_page.full_blocks` opts back in); 0.10.0 grows the entities host functions: `parent_id` on create and update, `parent_id`/`path`/`url` on the Entity, and entities.get by `path` or by `parent_id` + `slug`.

0.11.0 adds the builder.data filter, the builder.data permission, the builder_sources manifest field (plugin data sources for the page builder) and the builder.island permission (a widget pack that ships island.js).

0.12.0 adds the site.scripts permission: a plugin's browser JavaScript (assets/plugin.js) is only served and injected once the permission is granted.

0.13.0 adds the content `locale` field to the entity payloads (EntityRef, EntityEvent, EntitySummary, EntityDraft). Purely additive.

Variables

View Source
var (
	EntityCreated   = Action[EntityEvent]{"entity.created"}
	EntityUpdated   = Action[EntityEvent]{"entity.updated"}
	EntityPublished = Action[EntityEvent]{"entity.published"}
	EntityDeleted   = Action[EntityEvent]{"entity.deleted"}

	// ImportCompleted fires once after an import batch was stored. Imported
	// rows fire no entity.* action: a plugin that must see them all reacts
	// here and lists what changed through its host API.
	ImportCompleted = Action[ImportEvent]{"import.completed"}

	// AgentHit fires for every stored known-agent hit (any purpose; filter on
	// Purpose). The payload carries no IP address.
	AgentHit = Action[AgentHitEvent]{"agent.hit"}
	// AgentFirstCrawl fires once per entity, on the first served AI fetch
	// after it was recorded.
	AgentFirstCrawl = Action[AgentFirstCrawlEvent]{"agent.first_crawl"}

	// FormSubmitted fires after a public form submission was stored. It carries
	// the submitted fields, so it reaches only plugins granted forms.read.
	FormSubmitted = Action[FormSubmittedEvent]{"form.submitted"}

	JSONLDEnrich = Filter[JSONLDDoc]{"jsonld.enrich"}
	SitemapURLsF = Filter[SitemapURLs]{"sitemap.urls"}

	// RenderHead lets a plugin add tags to the <head> of server-rendered
	// pages. Tags are structured; the host builds and escapes the markup.
	RenderHead = Filter[HeadContext]{"render.head"}

	// RenderPage is the request-aware page filter: it sees the page's blocks
	// and the cookies the plugin declared, and may replace or hide blocks and
	// set declared cookies. A page served through it is never shared-cached.
	RenderPage = Filter[PageContext]{"render.page"}

	// EntityBeforeSave runs before an entity is created, updated or restored.
	// Rejecting (see RejectPrefix) refuses the save with HTTP 422 and the
	// reason. Any other error, a trap or a timeout is logged and the filter's
	// change is dropped; the save proceeds (fail open, like every filter).
	EntityBeforeSave = Filter[EntityDraft]{"entity.before_save"}

	// BuilderData answers the data-source requests a page render collected:
	// the plugin receives only requests naming its own declared
	// builder_sources (ext.<plugin>.<name>) and fills Results for them. One
	// call serves a whole render, never one per item. Needs the builder.data
	// permission; without the grant the filter is not registered. Any error,
	// trap or timeout leaves the affected values empty and the render
	// degraded (fail open).
	BuilderData = Filter[BuilderDataRequest]{"builder.data"}
)

Built-in hooks. Hook names and payload shapes are part of the SDK's compatibility surface (see docs/PLUGIN_API.md).

View Source
var (
	ActionNames = []string{
		EntityCreated.Name, EntityUpdated.Name, EntityPublished.Name, EntityDeleted.Name, ImportCompleted.Name,
		AgentHit.Name, AgentFirstCrawl.Name, FormSubmitted.Name,
	}
	FilterNames = []string{
		JSONLDEnrich.Name, SitemapURLsF.Name, RenderHead.Name, RenderPage.Name, EntityBeforeSave.Name, BuilderData.Name,
	}
)

ActionNames and FilterNames list every built-in hook, in a stable order.

View Source
var CompatibleWith = []string{"0.5.0", "0.6.0", "0.7.0", "0.8.0", "0.9.0", "0.10.0", "0.11.0", "0.12.0"}

CompatibleWith lists earlier versions whose plugins this SDK still serves. Caret ranges before 1.0 stop at the next minor, so a purely additive minor lists its predecessors here. 0.4 plugins were native Go.

View Source
var NamePattern = regexp.MustCompile(`^[a-z][a-z0-9-]{1,39}$`)

NamePattern restricts plugin names: they become URL segments, schema suffixes and settings keys.

View Source
var PageBlockFields = []string{"variant", "anchor", "label"}

PageBlockFields are the content keys a slim block carries as Fields, when their value is a short string, number or boolean.

Permissions lists every capability, in a stable order.

Functions

func ExportName

func ExportName(prefix, hook string) string

ExportName derives an export name from a hook name: the prefix followed by the hook name with every "." replaced by "_".

func Satisfies

func Satisfies(version, rng string) (bool, error)

Satisfies reports whether version lies within the range rng. Ranges are space-separated AND comparators: "^0.1", ">=0.1.0 <1.0.0", "~0.1.2", "=0.1.0". Caret and tilde follow npm semantics (^0.1 means >=0.1.0 <0.2.0).

func ValidPermission

func ValidPermission(p Permission) bool

ValidPermission reports whether p is a known capability.

Types

type Action

type Action[T any] struct{ Name string }

Action is a typed name token for a fire-and-forget hook. Actions run asynchronously after the event happened; a plugin's failure cannot affect the originating request. A plugin handles action "entity.published" by exporting the function "on_entity_published".

func (Action[T]) Export

func (a Action[T]) Export() string

Export is the wasm export a plugin provides to handle the action.

type AdminUI

type AdminUI struct {
	// Title is the sidebar label and the page heading.
	Title string `json:"title"`
	// Entry is the HTML file under ui/ (default "index.html").
	Entry string `json:"entry,omitempty"`
	// Roles may open the page; empty means owner only.
	Roles []string `json:"roles,omitempty"`
}

AdminUI declares a plugin's admin page.

type AgentFirstCrawlEvent

type AgentFirstCrawlEvent struct {
	EntityID    string     `json:"entity_id"`
	AgentID     string     `json:"agent_id"`
	Operator    string     `json:"operator"`
	Purpose     string     `json:"purpose"`
	FirstAt     time.Time  `json:"first_at"`
	PublishedAt *time.Time `json:"published_at,omitempty"`
}

AgentFirstCrawlEvent is the payload of agent.first_crawl. PublishedAt is nil when the entity has no publish time.

type AgentHitEvent

type AgentHitEvent struct {
	AgentID      string    `json:"agent_id"`
	Operator     string    `json:"operator"`
	Purpose      string    `json:"purpose"`
	Verification string    `json:"verification"`
	Method       string    `json:"method"`
	Path         string    `json:"path"`
	EntityID     string    `json:"entity_id"`
	Source       string    `json:"source"`
	Status       int       `json:"status"`
	Bytes        int       `json:"bytes"`
	DurationMS   int       `json:"duration_ms"`
	OccurredAt   time.Time `json:"occurred_at"`
}

AgentHitEvent is the payload of agent.hit. It deliberately carries no IP address (and no user agent): Verification is the verifier's verdict at storage time, normally "pending". EntityID is empty when the path is not an entity page; Status is 0 when unknown.

type Alternate

type Alternate struct {
	Rel      string `json:"rel"`
	HrefLang string `json:"hreflang"`
	Href     string `json:"href"`
}

Alternate is an hreflang alternate of a sitemap URL.

type Block

type Block struct {
	ID      string         `json:"id"`
	Type    string         `json:"type"`
	Title   string         `json:"title,omitempty"`
	Fields  map[string]any `json:"fields,omitempty"`
	Content map[string]any `json:"content,omitempty"`
	Hidden  bool           `json:"hidden,omitempty"`
}

Block is one section of a page. A block the plugin returns with Hidden set is dropped from the page, like one whose id is listed in Hide.

Slim mode (the default since SDK 0.9): the plugin receives Fields and no Content. A returned block without Content is untouched: the host puts the original content back by id, so return it as received (or reorder it). A returned block with Content replaces the block's content (and takes the returned Type and Title); the host sanitises it when rendering, like any block. Fields in a result are ignored. In full mode (`render_page.full_blocks`) the plugin receives Content and the returned blocks are used as they are.

type BuilderDataItem

type BuilderDataItem struct {
	Title       string            `json:"title,omitempty"`
	URL         string            `json:"url,omitempty"`
	Excerpt     string            `json:"excerpt,omitempty"`
	Image       string            `json:"image,omitempty"`
	Category    string            `json:"category,omitempty"`
	Author      string            `json:"author,omitempty"`
	AuthorURL   string            `json:"author_url,omitempty"`
	PublishedAt string            `json:"published_at,omitempty"`
	Attrs       map[string]string `json:"attrs,omitempty"`
}

BuilderDataItem is one entry of an items source. It feeds the loop item scope (loop.item.title, .url, ..., and loop.item.attributes.<k> from Attrs). URLs and images go through the host's usual URL safety checks.

type BuilderDataQuery

type BuilderDataQuery struct {
	ID     string `json:"id"`
	Source string `json:"source"`
	Limit  int    `json:"limit,omitempty"`
}

BuilderDataQuery asks one source for data. Source is "ext.<plugin>.<name>"; Limit bounds the items of an items source (at most MaxBuilderDataItems).

type BuilderDataRequest

type BuilderDataRequest struct {
	Path       string              `json:"path,omitempty"`
	Language   string              `json:"language,omitempty"`
	SchemaSlug string              `json:"schema_slug,omitempty"`
	EntityID   string              `json:"entity_id,omitempty"`
	Requests   []BuilderDataQuery  `json:"requests,omitempty"`
	Results    []BuilderDataResult `json:"results,omitempty"`
}

BuilderDataRequest is the builder.data filter payload. The host sends Requests (only those naming the plugin's own ext.<plugin>.<name> sources) and the plugin returns the same struct with Results set, one per request it answers, matched by ID. Results for ids that were not sent are ignored.

type BuilderDataResult

type BuilderDataResult struct {
	ID    string            `json:"id"`
	Value string            `json:"value,omitempty"`
	Items []BuilderDataItem `json:"items,omitempty"`
	Error string            `json:"error,omitempty"`
}

BuilderDataResult answers one query: Value for a value source, Items for an items source. A non-empty Error marks the request failed (empty output).

type BuilderSource

type BuilderSource struct {
	// Name is the source's short name, unique within the plugin.
	Name string `json:"name"`
	// Label is shown in the editor (1 to 60 characters).
	Label string `json:"label"`
	// Kind is BuilderSourceValue (one string for a prop) or
	// BuilderSourceItems (a list for a loop).
	Kind string `json:"kind"`
	// Class is the prop class a value source fits: text, url or image
	// (default text). Only for value sources.
	Class string `json:"class,omitempty"`
}

BuilderSource is one data source a plugin offers to the page builder.

type CallRequest

type CallRequest struct {
	Fn   string          `json:"fn"`
	Args json.RawMessage `json:"args,omitempty"`
}

CallRequest is the input of hm_call: the capability function and its arguments (see docs/PLUGIN_API.md for each function's shape).

type CallResponse

type CallResponse struct {
	OK     bool            `json:"ok"`
	Result json.RawMessage `json:"result,omitempty"`
	Error  *HostError      `json:"error,omitempty"`
}

CallResponse is the output of hm_call. Exactly one of Result and Error is set.

type CronEvent

type CronEvent struct {
	Name        string `json:"name"`
	ScheduledAt string `json:"scheduled_at"` // RFC 3339
}

CronEvent is the input of an `on_cron_<name>` export.

type CronJob

type CronJob struct {
	Name  string `json:"name"`
	Every string `json:"every"`
}

CronJob is a scheduled call. Every is a Go duration of at least one minute.

type EntityDraft

type EntityDraft struct {
	Operation  string `json:"operation"`
	ID         string `json:"id"`
	SchemaSlug string `json:"schema_slug"`
	Name       string `json:"name"`
	Slug       string `json:"slug"`
	// Locale is the entity's BCP 47 tag; the host does not read it back.
	Locale     string          `json:"locale,omitempty"`
	Attributes json.RawMessage `json:"attributes"`
}

EntityDraft is the value filtered by entity.before_save. Operation is one of "create", "update" or "restore"; ID is empty for create.

type EntityEvent

type EntityEvent struct {
	ID         string `json:"id"`
	SchemaSlug string `json:"schema_slug"`
	Slug       string `json:"slug"`
	Locale     string `json:"locale"`
	Name       string `json:"name"`
	Status     string `json:"status"`
	Version    int    `json:"version"`
}

EntityEvent is the payload of the entity.* actions.

type EntityRef

type EntityRef struct {
	ID         string `json:"id"`
	SchemaSlug string `json:"schema_slug"`
	Slug       string `json:"slug"`
	Locale     string `json:"locale"`
	Name       string `json:"name"`
	Status     string `json:"status"`
	Version    int    `json:"version"`
}

EntityRef identifies an entity inside hook payloads and builders.

type EntitySummary

type EntitySummary struct {
	ID         string `json:"id"`
	SchemaSlug string `json:"schema_slug"`
	Slug       string `json:"slug"`
	Locale     string `json:"locale"`
	Name       string `json:"name"`
}

EntitySummary describes the entity behind a page view.

type Filter

type Filter[T any] struct{ Name string }

Filter is a typed name token for a hook that transforms a value. Filters run synchronously in ascending priority order. A plugin handles filter "jsonld.enrich" by exporting "filter_jsonld_enrich": it receives the value as JSON and returns the (possibly modified) value as JSON.

func (Filter[T]) Export

func (f Filter[T]) Export() string

Export is the wasm export a plugin provides to handle the filter.

type FormSubmittedEvent

type FormSubmittedEvent struct {
	FormID       string         `json:"form_id"`
	FormSlug     string         `json:"form_slug"`
	SubmissionID string         `json:"submission_id"`
	Fields       map[string]any `json:"fields"`
	SourcePath   string         `json:"source_path,omitempty"`
	SubmittedAt  time.Time      `json:"submitted_at"`
}

FormSubmittedEvent is the payload of form.submitted. It carries the submitted fields, so the host only delivers it to plugins granted forms.read. No IP address or user agent is included.

type HTTPRequest

type HTTPRequest struct {
	Method string `json:"method"`
	Path   string `json:"path"` // below /api/v1/p/<plugin> or /p/<plugin>, starts with "/"; always the path the route was matched and authorised on
	// Pattern is the manifest route path that matched (e.g. "/items/{id}") and
	// PathValues its wildcard values, so a plugin need not re-route by hand.
	Pattern    string              `json:"pattern,omitempty"`
	PathValues map[string]string   `json:"path_values,omitempty"`
	Query      map[string][]string `json:"query,omitempty"`
	Headers    map[string]string   `json:"headers,omitempty"` // allowlisted request headers, lower-case names
	Body       string              `json:"body,omitempty"`
	BodyB64    string              `json:"body_b64,omitempty"` // set instead of Body when the body is not UTF-8
	User       *HTTPUser           `json:"user,omitempty"`     // nil on public routes
}

HTTPRequest is the input of an `http_handle` export: a request to one of the plugin's declared routes.

type HTTPResponse

type HTTPResponse struct {
	Status  int               `json:"status,omitempty"`
	Headers map[string]string `json:"headers,omitempty"`
	Body    string            `json:"body,omitempty"`
	BodyB64 string            `json:"body_b64,omitempty"`
}

HTTPResponse is the output of `http_handle`. Only allowlisted headers (Content-Type, Cache-Control, Location, Content-Disposition, ETag) are forwarded; Status defaults to 200.

type HTTPUser

type HTTPUser struct {
	ID   string `json:"id"`
	Role string `json:"role"`
}

HTTPUser is the signed-in user behind an admin route request.

type HeadContext

type HeadContext struct {
	Path             string    `json:"path"`
	Language         string    `json:"language"`
	SchemaSlug       string    `json:"schema_slug"`
	EntityID         string    `json:"entity_id"`
	Tags             []HeadTag `json:"tags,omitempty"`
	ReplaceSiteGraph bool      `json:"replace_site_graph"`
}

HeadContext is the payload of the render.head filter. Path, Language, SchemaSlug and EntityID describe the page; a filter appends to Tags. A filter that supplies its own Organization/WebSite graph as jsonld tags sets ReplaceSiteGraph so the host omits its generic site graph. The flag is preserved as long as later filters return the context they were given. It is fail-open: if the chain times out or panics, or no valid jsonld tag results, the generic graph is kept. Since 0.2.2.

type HeadTag

type HeadTag struct {
	Kind   string            `json:"kind"`
	Attrs  map[string]string `json:"attrs,omitempty"`
	JSONLD json.RawMessage   `json:"jsonld,omitempty"`
}

HeadTag is one head tag. Kind is meta, link, script-src or jsonld. The host drops attributes outside a per-kind allowlist (meta: name, property, content; link: rel, href, type, hreflang, crossorigin; script-src: src, crossorigin) and unsafe URL schemes. For jsonld, JSONLD holds the document.

type HostError

type HostError struct {
	Code       string `json:"code"`
	Capability string `json:"capability,omitempty"`
	Message    string `json:"message"`
}

HostError is the typed error of a failed host call.

A call refused for a missing grant has Code ErrCapabilityDenied and the permission in Capability: {"code":"capability_denied","capability":"kv"}.

func (*HostError) Error

func (e *HostError) Error() string

type ImportEvent

type ImportEvent struct {
	Source  string `json:"source"`
	Created int    `json:"created"`
	Updated int    `json:"updated"`
	Failed  int    `json:"failed"`
}

ImportEvent is the payload of import.completed: one per imported batch, instead of an entity.created or entity.published per row.

type JSONLDDoc

type JSONLDDoc struct {
	Entity  EntityRef      `json:"entity"`
	BaseURL string         `json:"base_url"`
	Doc     map[string]any `json:"doc"`
}

JSONLDDoc is the value filtered by jsonld.enrich. Mutate Doc (or return a modified copy).

type Limits

type Limits struct {
	// MemoryPages is the cap on each wasm linear memory, in 64 KiB pages.
	// A standard Go guest needs at least MinMemoryPages.
	MemoryPages uint32 `json:"memory_pages,omitempty"`
	// HookTimeoutsMS overrides the time one call of a hook may run, in
	// milliseconds. Keys are hook names ("render.page"), or "http_handle",
	// "on_cron_<job>" and "func_<name>" for the other exports. A hook not
	// listed gets DefaultFilterTimeoutMS (filters and template funcs) or
	// DefaultActionTimeoutMS (actions, cron, routes). Zero is refused: a
	// guest without a deadline cannot be stopped.
	HookTimeoutsMS map[string]uint64 `json:"hook_timeouts_ms,omitempty"`
	// MaxInstances caps concurrent calls (and so live instances) of the plugin.
	MaxInstances int `json:"max_instances,omitempty"`
	// MaxOutputBytes caps what one call may return. Larger output fails the
	// call (fail open); it never reaches the JSON decoder.
	MaxOutputBytes uint32 `json:"max_output_bytes,omitempty"`
}

Limits bounds a plugin's resource use. Zero fields take the Default* values.

type Manifest

type Manifest struct {
	Name        string `json:"name"`
	Version     string `json:"version"` // the plugin's own semver
	SDK         string `json:"sdk"`     // SDK range the plugin was built against, e.g. "^0.5"
	Description string `json:"description,omitempty"`

	// Config is handed to the guest (pdk.GetConfig).
	Config map[string]string `json:"config,omitempty"`

	// AllowedHosts is the plugin's outbound HTTP allowlist for http.fetch
	// (exact hosts, "*.example.com", "host:port"). "*" means any public
	// address on ports 80 and 443 and needs the http.fetch.any permission,
	// which the owner grants separately. Without the http.fetch permission
	// granted it has no effect.
	AllowedHosts []string `json:"allowed_hosts,omitempty"`

	// Permissions are the capabilities the plugin asks for (see Permissions).
	// The owner grants them; nothing is granted by default.
	Permissions []Permission `json:"permissions,omitempty"`

	// Routes are served under /api/v1/p/<name>/... (signed in, CSRF, roles)
	// or, with Public set, /p/<name>/... (anonymous, rate limited), by the
	// plugin's `http_handle` export.
	Routes []Route `json:"routes,omitempty"`

	// Cron lists scheduled calls of the plugin's `on_cron_<name>` exports.
	Cron []CronJob `json:"cron,omitempty"`

	// Cookies are the cookie names a render.page filter may read and set.
	Cookies []string `json:"cookies,omitempty"`

	// RenderPage tunes the render.page filter (see PageOptions).
	RenderPage *PageOptions `json:"render_page,omitempty"`

	// Funcs are template functions: the theme calls plugin_<name>_<func>
	// and the host calls the plugin's `func_<func>` export with JSON args.
	Funcs []string `json:"funcs,omitempty"`

	// UI is an optional admin page the plugin ships in its package's ui/
	// directory. It runs in a sandboxed iframe and reaches the host only
	// through a bridge limited to the plugin's own routes and granted host
	// functions.
	UI *AdminUI `json:"ui,omitempty"`

	// BuilderSources declares the data sources the plugin answers through the
	// builder.data filter (at most MaxBuilderSources). A source's id in the
	// builder is ext.<plugin>.<name>. Needs the builder.data permission.
	BuilderSources []BuilderSource `json:"builder_sources,omitempty"`

	Limits Limits `json:"limits,omitempty"`

	// SettingsSchema is an optional JSON Schema (draft 2020-12) object
	// describing the plugin's settings.
	SettingsSchema json.RawMessage `json:"settings_schema,omitempty"`
}

Manifest is the content of a plugin's plugin.json.

func (Manifest) Compatible

func (m Manifest) Compatible() (bool, error)

Compatible reports whether this SDK satisfies the manifest's SDK range.

func (Manifest) FullPageBlocks

func (m Manifest) FullPageBlocks() bool

FullPageBlocks reports whether the host sends this plugin render.page blocks with their whole content: when it asks for it, or when it was built for an SDK range that includes neither 0.9 nor the current SDK (those plugins expect the old payload).

func (Manifest) HookTimeout

func (m Manifest) HookTimeout(export string) time.Duration

HookTimeout is how long one call of the plugin's export may run: the manifest's override (keyed by hook name or export name), else the default for the export's kind: DefaultFilterTimeoutMS for filter_* and func_*, DefaultActionTimeoutMS for everything else.

func (Manifest) Validate

func (m Manifest) Validate() error

Validate checks the manifest is well formed and its limits are in range. It does not check SDK compatibility with the running host (see Compatible).

func (Manifest) WithDefaults

func (m Manifest) WithDefaults() Manifest

WithDefaults returns m with zero limits replaced by the defaults.

type PageContext

type PageContext struct {
	Path       string            `json:"path"`
	Cookies    map[string]string `json:"cookies"`
	Entity     *EntitySummary    `json:"entity,omitempty"`
	Blocks     []Block           `json:"blocks"`
	Hide       []string          `json:"hide,omitempty"`
	SetCookies []SetCookie       `json:"set_cookies,omitempty"`
}

PageContext is the value filtered by render.page for one public page view. The result may reorder or replace Blocks, hide blocks by id (Hide) and set cookies (SetCookies). Cookies holds only the cookie names the plugin declared in its manifest (cookies); SetCookies may only use declared names.

Since SDK 0.9 a plugin gets slim blocks (id, type, title, hidden and the scalar Fields in PageBlockFields) unless it asks for the whole page with `render_page.full_blocks` in its manifest or was built for an SDK before 0.9.

type PageOptions

type PageOptions struct {
	// FullBlocks sends every block with its whole content (the pre-0.9
	// payload). It is slower: the cost of a call grows with the page's size.
	FullBlocks bool `json:"full_blocks,omitempty"`
}

PageOptions are the manifest options of the render.page filter.

type Permission

type Permission = string

Permission names a host capability a plugin may be granted. A plugin lists the ones it needs in plugin.json; the owner grants them (a subset is fine); a host function called without its grant fails with ErrCapabilityDenied and the plugin carries on.

const (
	PermEntitiesRead   Permission = "entities.read"
	PermEntitiesWrite  Permission = "entities.write"
	PermTermsRead      Permission = "terms.read"
	PermTermsWrite     Permission = "terms.write"
	PermMenusRead      Permission = "menus.read"
	PermMenusWrite     Permission = "menus.write"
	PermMediaRead      Permission = "media.read"
	PermMediaWrite     Permission = "media.write"
	PermRedirectsRead  Permission = "redirects.read"
	PermRedirectsWrite Permission = "redirects.write"
	PermFormsRead      Permission = "forms.read"
	PermSettings       Permission = "settings"
	PermLog            Permission = "log"
	PermMailSend       Permission = "mail.send"
	PermHTTPFetch      Permission = "http.fetch"
	// PermHTTPFetchAny lets http.fetch call any public address (allowed_hosts
	// "*"), for plugins that call URLs the owner types in. It is separate from
	// PermHTTPFetch so the permission prompt can warn about it, and an update
	// that newly asks for it is reviewed again.
	PermHTTPFetchAny Permission = "http.fetch.any"
	PermKV           Permission = "kv"
	PermDB           Permission = "db"
	// PermBuilderData lets the plugin answer builder.data requests for the
	// builder_sources it declares. Without the grant the filter is not
	// registered.
	PermBuilderData Permission = "builder.data"
	// PermBuilderIsland is required of a package whose widgets ship an
	// island.js. The script runs on the site's public pages with the
	// visitor's privileges, so the owner grants it explicitly; without the
	// grant the widget still renders but its island is not served.
	PermBuilderIsland Permission = "builder.island"
	// PermSiteScripts is required for a plugin's browser JavaScript to run on
	// the public site: its assets/plugin.js is linked on pages, and the
	// script-src tags of its render.head filter are emitted, only while
	// granted. That script runs with the visitor's privileges, including a
	// signed-in admin's, so the owner grants it explicitly. plugin.css needs
	// no grant.
	PermSiteScripts Permission = "site.scripts"
)

The capability set. Names are part of the SDK's compatibility surface.

type Reject

type Reject struct{ Reason string }

Reject is the host-side error for a refused save. Reason is surfaced to the API client.

func (*Reject) Error

func (e *Reject) Error() string

type Route

type Route struct {
	Method string `json:"method"`
	Path   string `json:"path"`
	// Public routes are mounted at /p/<name> and need no sign-in.
	Public bool `json:"public,omitempty"`
	// Roles may call a non-public route; empty means owner only.
	Roles []string `json:"roles,omitempty"`
}

Route is one HTTP route of a plugin. Path is relative to the plugin's mount and uses net/http patterns ("/leads", "/leads/{id}", "/files/{rest...}").

type SetCookie

type SetCookie struct {
	Name   string `json:"name"`
	Value  string `json:"value"`
	MaxAge int    `json:"max_age,omitempty"`
	Path   string `json:"path,omitempty"`
}

SetCookie is a cookie a render.page filter asks the host to set. The host forces Path=/ unless set, HttpOnly, Secure on HTTPS and SameSite=Lax; MaxAge is seconds (capped at one year).

type SitemapURL

type SitemapURL struct {
	Loc        string      `json:"loc"`
	LastMod    string      `json:"lastmod"`
	ChangeFreq string      `json:"changefreq"`
	Priority   float64     `json:"priority"`
	Alternates []Alternate `json:"alternates,omitempty"`
}

SitemapURL is one <url> entry.

type SitemapURLs

type SitemapURLs struct {
	Group    string       `json:"group"`
	Language string       `json:"language"`
	Add      []SitemapURL `json:"add"`
}

SitemapURLs is the value filtered by sitemap.urls for one sitemap file.

The host sends only Group and Language, and in Add the entries that sitemap.urls filters before this one already added (empty for the first): never the file's own URL list, because a Go guest spends about 0.5 s per 5,000 URLs to decode and encode one. A plugin appends the entries it wants to add to Add and returns it; the host merges them into the file and drops any whose Loc the file already has. A plugin cannot remove or change existing entries.

Group identifies which file: a group slug for a group sitemap and "misc" for the misc sitemap holding static routes. Language is the BCP 47 site language. Filters that add a site-wide URL should target one file (usually Group == "misc") or it appears in many.

Directories

Path Synopsis
Package guest is the Go side of the plugin ABI: a thin layer over the Extism Go PDK that turns typed handlers into the JSON-in/JSON-out exports the host calls (see docs/PLUGIN_API.md).
Package guest is the Go side of the plugin ABI: a thin layer over the Extism Go PDK that turns typed handlers into the JSON-in/JSON-out exports the host calls (see docs/PLUGIN_API.md).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL