CoreAPI Reference

The 46 methods on the Go CoreAPI surface, grouped by domain.

The CoreAPI surface

The CoreAPI is the single Go interface that exposes every kernel capability to plugins, scripts, and the MCP server. It's defined in internal/coreapi/api.go as a Go interface with 56 methods across 16 domains. Three adapters wrap that one interface:

  • Internal Go callers — kernel handlers in internal/cms import the implementation directly. The capability guard is bypassed; kernel code is trusted.
  • gRPC SquillaHost service — the wire surface compiled-Go extensions speak. Defined in proto/coreapi/squilla_coreapi.proto; generated client in pkg/plugin/coreapipb. Every call passes through capabilityGuard on the server side.
  • Tengo modules — the script-side surface. Each domain gets a core/<name> module in internal/coreapi/tengo_*.go. Same capability guard, plus the script sandbox.

The MCP server is a fourth caller, but it dispatches through the internal Go path with a typed internal-caller context, so the capability guard does not enforce token-level permissions for MCP. Token-level enforcement happens via the scope gate in MCP dispatch (read | content | full) before the tool handler is invoked.

MCP ≠ cap guard
MCP runs every tool invocation as an internal caller, so the CoreAPI capability strings are an architectural feature — they describe what an extension or script could do, not what an MCP token can. For tokens, scope decides everything: a read token cannot create nodes regardless of which CoreAPI capabilities its underlying user has, and a full token can do anything regardless of which capabilities the kernel asserts on the path. Pick scopes carefully when issuing tokens.

Domains and methods

Nodes (5)

  • GetNode(ctx, id) → Node — nodes:read
  • QueryNodes(ctx, query) → NodeList — nodes:read
  • CreateNode(ctx, input) → Node — nodes:write; emits node.created async after commit
  • UpdateNode(ctx, id, input) → Node — nodes:write; emits node.updated
  • DeleteNode(ctx, id) → error — nodes:delete; emits node.deleted

Node Types (5)

  • RegisterNodeType(ctx, input), UpdateNodeType, DeleteNodeTypenodetypes:write
  • GetNodeType, ListNodeTypesnodetypes:read

Taxonomies (5) + Terms (6) + ListTaxonomyTerms (1)

  • Taxonomies — RegisterTaxonomy, UpdateTaxonomy, DeleteTaxonomy (nodetypes:write); GetTaxonomy, ListTaxonomies (nodetypes:read).
  • Terms — ListTerms, GetTerm (nodes:read); CreateTerm, UpdateTerm (nodes:write); DeleteTerm (nodes:delete).
  • ListTaxonomyTerms(ctx, nodeType, taxonomy) → []string — thin convenience read used heavily by editor autocomplete.

Settings (6)

  • GetSetting, GetSettings(prefix), GetSettingLoc, GetSettingsLocsettings:read. The Loc variants take an explicit locale; the others use the request's resolved language.
  • SetSetting, SetSettingLocsettings:write; emits setting.updated with {key, language_code, value} after commit. Sensitive-shaped keys are auto-encrypted.

Events (2)

  • Emit(ctx, action, payload) → error — events:emit. Fire-and-forget; the bus dispatches in a goroutine.
  • Subscribe(ctx, action, handler) → UnsubscribeFunc — events:subscribe. The handler is invoked with each matching event payload until the returned function is called.

Email (1)

  • SendEmail(ctx, req) → error — email:send. Internally fans out via the email.send request-style event using PublishRequest; provider extensions subscribe and the first non-nil error wins. If no extension declares email.provider, the call fails fast with "no provider".

Menus (6)

  • GetMenu, GetMenusmenus:read
  • CreateMenu, UpdateMenu, UpsertMenumenus:write. UpsertMenu is idempotent on slug — use it from seed scripts.
  • DeleteMenumenus:delete

Routes (2)

  • RegisterRoute(ctx, method, path, meta), RemoveRoute(ctx, method, path)routes:register

Filters (2)

  • RegisterFilter(ctx, name, priority, handler)filters:register; returns an UnsubscribeFunc that removes only this handler from the chain.
  • ApplyFilters(ctx, name, value) → any — filters:apply; passes value through every registered handler in priority order, returns the post-chain value.

Media (4)

  • UploadMedia(ctx, req)media:write
  • GetMedia(ctx, id), QueryMedia(ctx, query)media:read
  • DeleteMedia(ctx, id)media:delete

All four route through whichever active extension declares provides: ["media-provider"]. The bundled media-manager fills the slot; an S3-style provider can replace it by activating with a higher priority.

Users (2, read-only)

  • GetUser(ctx, id), QueryUsers(ctx, query)users:read

Scripts and extensions cannot create, update, or delete users via the CoreAPI. User mutations live behind admin-authed handlers in the kernel.

HTTP (1)

  • Fetch(ctx, req) → FetchResponse — http:fetch. Outbound HTTP from extensions and scripts; default 30-second timeout, configurable per-call. Honors a kernel-side allow-list and blocks private network ranges by default to prevent SSRF.

Log (1)

  • Log(ctx, level, message, fields) → error — log:write. Levels: info, warn, debug, err. Output lands in the kernel's structured log stream with caller info auto-prefixed.

Data Store (6)

  • DataGet(ctx, table, id), DataQuery(ctx, table, query)data:read
  • DataCreate(ctx, table, data), DataUpdate(ctx, table, id, data)data:write
  • DataDelete(ctx, table, id)data:delete (each capability is enforced strictly; there is no fallback from data:delete to data:write)
  • DataExec(ctx, sql, args...)internal-only. Not exposed through the gRPC client interface, so extensions cannot call it. The MCP core.data.exec tool is a separate path, env-gated by SQUILLA_MCP_ALLOW_RAW_SQL=true (case-insensitive). Use sparingly.

File Storage (2)

  • StoreFile(ctx, path, data)files:write
  • DeleteFile(ctx, path)files:delete

Capability matrix

The kernel defines 27 capability strings across the 14 enforcement domains, plus admin_access as a separate gate used by extension admin proxy routes. Internal callers bypass the guard; everyone else must declare what they intend to use. The full set:

  • nodes — read, write, delete
  • nodetypes — read, write
  • settings — read, write
  • events — emit, subscribe
  • email — send
  • menus — read, write, delete
  • routes — register
  • filters — register, apply
  • media — read, write, delete
  • users — read
  • http — fetch
  • log — write
  • data — read, write, delete
  • files — write, delete
  • admin_access — separate; gates extension admin proxy routes (extension-level, not Tengo).

The gRPC adapter rejects calls outside the declared set with a typed error code so callers can branch on it. The Tengo adapter wraps the same error as a string-typed result that the script can check with is_error.

Data store table scoping

The data-store CoreAPI applies two layers of access control on top of the capability guard. Both layers must pass for the call to succeed; either failing returns a typed error.

  1. Hard-coded kernel-private deny list. The kernel reserves these tables for itself: users, sessions, password_reset_tokens, roles, role_capabilities, audit_log, schema_migrations, site_settings, mcp_tokens, mcp_audit_log, mcp_token_audit, languages. Denied unconditionally for non-internal callers, regardless of any data_owned_tables declaration.
  2. Per-extension allowlist. The manifest's data_owned_tables array. Every read/write/delete checks that the requested table is in the calling extension's owned list. Tables outside both lists (i.e. owned by another extension) are denied.

MCP tools run as internal callers and bypass both layers — a token with full scope can therefore touch anything via core.data.* tools. A token with content scope can use the data-store tools but is gated to extension-owned tables (the kernel-private deny list still applies).

Raw SQL

The MCP tool core.data.exec runs arbitrary parameterised SQL statements against the database. It's protected by two checks: the token must be scope full, and the kernel must have SQUILLA_MCP_ALLOW_RAW_SQL=true set (matched case-insensitively). When the env flag is unset, the tool is registered but every call returns "raw SQL access not enabled". The CoreAPI DataExec method behind it is internal-only — it's never reachable from a gRPC plugin or Tengo script regardless of capabilities, so the only path to raw SQL from outside the kernel is the MCP tool with both gates open.