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
- Variables
- func ExportName(prefix, hook string) string
- func Satisfies(version, rng string) (bool, error)
- func ValidPermission(p Permission) bool
- type Action
- type AdminUI
- type AgentFirstCrawlEvent
- type AgentHitEvent
- type Alternate
- type Block
- type BuilderDataItem
- type BuilderDataQuery
- type BuilderDataRequest
- type BuilderDataResult
- type BuilderSource
- type CallRequest
- type CallResponse
- type CronEvent
- type CronJob
- type EntityDraft
- type EntityEvent
- type EntityRef
- type EntitySummary
- type Filter
- type FormSubmittedEvent
- type HTTPRequest
- type HTTPResponse
- type HTTPUser
- type HeadContext
- type HeadTag
- type HostError
- type ImportEvent
- type JSONLDDoc
- type Limits
- type Manifest
- type PageContext
- type PageOptions
- type Permission
- type Reject
- type Route
- type SetCookie
- type SitemapURL
- type SitemapURLs
Constants ¶
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.
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" ErrInternal = "internal" ErrUnknownFunction = "unknown_function" )
Error codes of a failed host call.
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.
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.
const ( BuilderSourceValue = "value" BuilderSourceItems = "items" MaxBuilderSources = 8 )
Builder source kinds and value classes.
const ( MaxRoutes = 64 MaxCronJobs = 16 MaxFuncs = 64 MaxCookies = 8 MinCronEvery = time.Minute )
Limits on the manifest's lists.
const DefaultUIEntry = "index.html"
DefaultUIEntry is the AdminUI entry when none is declared.
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.
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.
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.
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 ¶
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).
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.
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.
var NamePattern = regexp.MustCompile(`^[a-z][a-z0-9-]{1,39}$`)
NamePattern restricts plugin names: they become URL segments, schema suffixes and settings keys.
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.
var Permissions = []Permission{ PermEntitiesRead, PermEntitiesWrite, PermTermsRead, PermTermsWrite, PermMenusRead, PermMenusWrite, PermMediaRead, PermMediaWrite, PermRedirectsRead, PermRedirectsWrite, PermFormsRead, PermSettings, PermLog, PermMailSend, PermHTTPFetch, PermHTTPFetchAny, PermKV, PermDB, PermBuilderData, PermBuilderIsland, PermSiteScripts, }
Permissions lists every capability, in a stable order.
Functions ¶
func ExportName ¶
ExportName derives an export name from a hook name: the prefix followed by the hook name with every "." replaced by "_".
func Satisfies ¶
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 ¶
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".
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 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 ¶
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.
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 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"}.
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 ¶
Compatible reports whether this SDK satisfies the manifest's SDK range.
func (Manifest) FullPageBlocks ¶
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 ¶
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 ¶
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 ¶
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.
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.
Source Files
¶
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). |