Skip to content

Metacore

manifest.json reference

Legacy v2 reference

This page documents the flat v2 manifest (APIVersion = "2.0.0"). The canonical contract is now Module Contract v3 (apiVersion: asteby.com/v3) — see the Manifest concept and the kernel's v3 spec. The kernel dual-reads v2 for compatibility, so this reference still applies to existing addons and to SDK tooling output until it finishes migrating to v3.

The manifest is the single contract between an addon and the metacore kernel. It is consumed by Go (kernel/manifest) and mirrored by the TS SDK via tygo. This (legacy) document reflects APIVersion = "2.0.0".

When this spec evolves, APIVersion is bumped and a migration path is documented. Addons opt into a compatibility window via the top-level kernel field.

Table of contents

Top-level fields

FieldTypeRequiredSection
key, name, version, kernelstringyes (key/name/version)Identity
description, category, author, website, license, icon_*stringnoIdentity
tenant_isolationenumno (default "shared")Tenant isolation
model_definitionsarraynoModels
navigationarraynoNavigation
actionsobjectnoActions
toolsarraynoTools
capabilitiesarrayrecommendedCapabilities
hooks, lifecycle_hooksobjectnoHooks
settingsarraynoSettings
frontendobjectnoFrontend
backendobjectnoBackend
signatureobjectstamped at publishSignature
eventsstring[]noEvents
i18nobjectnoLocale → namespace tree, merged into the host's i18next via I18nProvider.

1. Identity

json
{
  "key": "fiscal_mexico",
  "name": "Facturación Electrónica México",
  "version": "1.0.0",
  "kernel": ">=2.0.0 <3.0.0"
}
FieldTypeRequiredNotes
keystringyesRegex ^[a-z][a-z0-9_]{1,63}$. Globally unique. Defines the Postgres schema addon_<key> and the route namespace /m/<key>.
namestringyesDisplay name.
descriptionstringnoShort description, shown in the marketplace card.
versionstringyesStrict semver.
categorystringnoOne of integration, utility, finance, crm, operations, ai.
kernelstringrecommendedSemver range the host kernel must satisfy. Empty = legacy.
author, website, licensestringnoMarketplace metadata.
icon_type, icon_slug, icon_colorstringnoTriplet for richer rendering. icon_type: "lucide", "brand" (simple-icons), or "url".

2. Tenant isolation

json
"tenant_isolation": "shared"
ValueBehaviour
"shared" (default)Single schema addon_<key>, organization_id column + Postgres RLS.
"schema-per-tenant"One schema per installation (addon_<key>_<orgshort>), created on install and dropped on uninstall. Use for regulated data.
"database-per-tenant"Reserved for future use.

Empty is treated as shared for backwards compatibility.

3. model_definitions[]

Each entry is materialized as CREATE TABLE addon_<key>.<table_name>.

json
"model_definitions": [{
  "table_name": "tickets",
  "model_key": "tickets",
  "label": "Tickets",
  "org_scoped": true,
  "soft_delete": true,
  "columns": [
    { "name": "title",  "type": "string", "size": 255, "required": true },
    { "name": "status", "type": "string", "size": 20, "default": "'open'", "index": true },
    { "name": "total",  "type": "decimal", "default": 0 },
    { "name": "opened_at", "type": "timestamp", "default": "now()" }
  ]
}]

Column types

TypePostgresNotes
stringvarchar(<size>)size required, max 10485760.
texttextUnbounded.
uuiduuid
intinteger
bigintbigint
decimalnumeric
boolboolean
timestamptimestamptzAlways with timezone.
jsonbjsonb

Column options

FieldTypeMeaning
requiredboolNOT NULL constraint.
indexboolCreates a btree index.
uniqueboolUNIQUE constraint.
defaultanySee whitelist below.
refstringForeign key target. "orders" or "addon_tickets.comments".

default whitelist

default goes raw into the DDL. Only these literals pass validation:

FormExample
Numeric42, -3, 3.14
Quoted string"'open'", "'es-MX'" (no embedded ', ", ;, \)
Builtin call"now()", "gen_random_uuid()", "uuid_generate_v4()", "current_timestamp"
Boolean / nulltrue, false, "null"

Anything else (including arbitrary SQL) is rejected by metacore validate.

Key regex

Every user-supplied identifier (key, model_key, table_name, column name) must match ^[a-z][a-z0-9_]{1,63}$. This blocks both SQL injection and Postgres quoting ambiguities.

4. navigation[]

json
"navigation": [{
  "title": "sidebar.tickets",
  "icon": "Ticket",
  "target": "sidebar.operations",
  "items": [{
    "title": "sidebar.tickets.board",
    "url": "/m/tickets",
    "icon": "Kanban",
    "model": "tickets"
  }]
}]
  • target (optional): id of an existing core sidebar group. When it matches, items are merged in; otherwise a new group is created.
  • model: when present, the host knows the route is dynamic CRUD on that table. No frontend code is required.

5. actions{} (UI-triggered)

json
"actions": {
  "tickets": [{
    "key": "resolve",
    "label": "Resolve",
    "confirm": true,
    "requiresState": ["open", "in_progress"],
    "fields": [
      { "name": "note", "type": "text", "required": true }
    ]
  }]
}

Executed as POST /api/models/tickets/:id/actions/resolve. The host dispatches to a webhook declared in hooks, a WASM export, or a compiled ActionInterceptor. modal: "custom_slug" opens a custom frontend modal.

6. tools[] (LLM-triggered)

Semantic counterpart to actions. Conversational hosts register these in their agent-tool registry on install.

json
"tools": [{
  "id": "create_order",
  "name": "Crear pedido",
  "description": "Crea un pedido cuando el cliente expresa intención de comprar. NO llamar para cotizaciones.",
  "category": "action",
  "endpoint": "/webhooks/create_order",
  "method": "POST",
  "input_schema": [
    { "name": "product_sku", "type": "string", "required": true,
      "extraction_hint": "Código tipo SKU-123 o nombre del producto",
      "normalize": "uppercase" },
    { "name": "quantity", "type": "number", "default_value": "1",
      "extraction_hint": "Si el cliente dice 'una' o 'un par' inferir 1 o 2" }
  ],
  "trigger_keywords": ["pedido", "comprar", "quiero"],
  "trigger_intents": ["order.create"],
  "timeout": 15
}]
FieldPurpose
descriptionPrompt the LLM sees. Be specific; include negative cases.
trigger_keywords / trigger_intentsHints for the routing layer.
input_schema[i].extraction_hintNatural-language instruction for the LLM when extracting that field.
input_schema[i].normalizePost-extraction transform: uppercase, lowercase, trim, phone_e164.
input_schema[i].validationRegex the value must match after normalization.
cache_ttlSeconds; non-zero marks the tool as idempotent-GET-like.

7. capabilities[]

Sandboxed permissions the addon requests. Enforced at runtime by kernel/security/context.go. See capabilities.md for the full kind catalog and validation rules.

json
"capabilities": [
  { "kind": "db:read",    "target": "users", "reason": "Display author names" },
  { "kind": "http:fetch", "target": "api.factura.com", "reason": "Timbrar CFDI" },
  { "kind": "event:emit", "target": "fiscal.stamped" }
]

The addon's own schema (addon_<key>.*) is always accessible — never declare it.

8. hooks{} and lifecycle_hooks{}

json
"hooks": {
  "tickets::resolve": "/webhooks/resolve_ticket"
}
  • hooks: "<model>::<action>" → <webhook path or URL>. The host POSTs an HMAC-signed envelope (see addon-publishing.md).
  • lifecycle_hooks: per-model CRUD triggers:
    json
    "lifecycle_hooks": {
      "tickets": [
        { "event": "after_create",
          "target": { "type": "webhook", "url": "/webhooks/ticket_created" },
          "async": true }
      ]
    }
    Target types: webhook, wasm_call, agent_task.

9. settings[]

Per-installation configurable values. Stored by the host in metacore_installations.settings.

json
"settings": [
  { "key": "slack_webhook", "label": "Slack webhook", "type": "text", "secret": true },
  { "key": "default_locale", "label": "Locale",
    "type": "select",
    "default_value": "es-MX",
    "options": [
      { "value": "es-MX", "label": "Español (México)" },
      { "value": "en-US", "label": "English (US)" }
    ] }
]

secret: true ensures the value never leaves the server on GETs and is stored in the secrets manager when the host supports it.

10. frontend{}

json
"frontend": {
  "entry": "https://cdn.example.com/addons/tickets@1.0.0/remoteEntry.js",
  "format": "federation",
  "expose": "./plugin",
  "container": "metacore_tickets",
  "integrity": "sha384-..."
}
FieldMeaning
entryURL or relative path of remoteEntry.js.
format"federation" (recommended) or "script" (legacy window global).
exposeFederation module name to import (e.g. ./plugin).
containerGlobal container name. Must match the name option of @originjs/vite-plugin-federation. Default: metacore_<key>.
integrityOptional SRI hash.

11. backend{}

json
"backend": {
  "runtime": "wasm",
  "entry": "backend/backend.wasm",
  "exports": ["resolve_ticket", "ping"],
  "memory_limit_mb": 64,
  "timeout_ms": 10000
}
RuntimeBehaviour
"webhook" (default)Hooks dispatch as outbound HMAC-signed HTTP.
"wasm"Sandboxed in-process module per wasm-abi.md.
"binary"Reserved.

12. signature{}

Stamped by the marketplace at publish time. Contains developer_id, algorithm (ed25519), digest (sha256 of the bundle), signature value and per-file checksums. Addons never author this block; it is produced by metacore sign and verified by the host on install.

13. events[]

List of topic names the addon will publish. Hosts with an event bus register the schema; subscribers declare capabilities: [{kind: "event:subscribe"}].

json
"events": ["ticket.created", "ticket.resolved"]

See also

  • dynamic-ui.md — how the SDK turns metadata derived from this manifest into a working CRUD UI.
  • addon-cookbook.md — recipes for foreign keys, soft delete, custom actions, events and more.
  • capabilities.md — full catalog of kind values and target patterns.
  • quickstart.md — hands-on walkthrough end-to-end.

Metacore is open-source. Apache-2.0.