Plugin Structure
Plugin Structure
Plugin Developer Agent Kit — documentation written for AI agents, readable by humans.
A plugin provides headless components, contracts, and actions. It can be a standalone npm package or inline within a project.
plugin.yaml
The plugin manifest declares all contracts, actions, services, contexts, and configuration:
name: my-plugin
contracts:
- name: product-page
contract: product-page.jay-contract
component: productPage
description: Complete product detail page with SSR
- name: product-search
contract: product-search.jay-contract
component: productSearch
description: Product listing with filters and pagination
dynamic_contracts:
# Single contract: prefix used as the contract name directly
- prefix: product-page
component: productPage
generator: productPageContractGenerator
# Multiple contracts: prefix/name format (e.g., list/recipes, list/articles)
- prefix: list
component: dynamicList
generator: listContractGenerator
actions:
- name: searchProducts
action: search-products.jay-action
- name: addToCart
action: add-to-cart.jay-action
webhooks:
- name: onProductChange
- name: onInventoryUpdate
services:
- name: my-store
marker: MY_STORE_SERVICE_MARKER
description: Provides product catalog API (query, filter, sort)
contexts:
- name: my-cart
marker: MY_CART_CONTEXT
description: Client-side cart state (add/remove items, totals)
routes:
- path: /admin/dashboard
jayHtml: ./pages/admin/page.jay-html
component: ./pages/admin/page.ts
description: Admin dashboard with product stats
commands:
- name: upload-public
command: commands/upload-public.jay-command
- name: sync-catalog
command: commands/sync-catalog.jay-command
validators:
- name: media-optimization
handler: validateMediaOptimization
description: Ensures media URLs use resize parameters
setup: setup-handler
agentkit: agentkit-handler
description: Configure My Plugin
Contract Entry Fields
name— Contract name (used incontract="..."in jay-html)contract— Path to.jay-contractfile (relative to plugin root)component— Export name of the component (e.g.,productPage)description— What this component does and when to use it
Dynamic Contract Entry Fields
Dynamic contracts are generated at setup time from site-specific data (e.g., CMS collection schemas, extended product fields).
prefix— Identifier for this dynamic contract group. Used as the contract name for single contracts, or asprefix/namefor multiple.component— Export name of the headless component that serves these contractsgenerator— Export name of the generator function that produces contract YAML
Single contract — generator returns one { yaml } without a name:
dynamic_contracts:
- prefix: product-page
component: productPage
generator: productPageContractGenerator
Referenced as contract="product-page" in jay-html.
Multiple contracts — generator yields { name, yaml } for each:
dynamic_contracts:
- prefix: list
component: dynamicList
generator: listContractGenerator
Referenced as contract="list/recipes", contract="list/articles" etc.
Contracts are materialized by jay-stack agent-kit or jay-stack setup and stored in agent-kit/materialized-contracts/.
Linking to static contracts from generated YAML — materialized contracts live in a different directory than the plugin source. Use the plugin's package path (not relative paths) for link: references to static contracts:
# In the generated contract YAML:
tags:
- tag: gallery
type: sub-contract
link: '@my-org/my-plugin/media-gallery' # package path — works from any directory
# NOT: link: ./media-gallery # relative path — breaks in materialized location
Action Entry Fields
name— Action name (used withjay-stack action <plugin>/<action>)action— Path to.jay-actionmetadata file
Webhook Entry Fields
name— Export name of themakeWebhook()constant (e.g.,onProductChange)
Webhooks are exposed at POST /_jay/webhooks/{webhookName} on the renderer server. The webhookName comes from the makeWebhook('plugin.event-name') call, not from the export name. The export name in plugin.yaml tells the framework which export to load from the plugin module.
Service Entry Fields
name— Service name (for identification in plugins-index)marker— Exported service marker constant (e.g.,MY_STORE_SERVICE_MARKER)description— What APIs this service providesdoc— (optional) Path to a markdown file documenting the service API
Services are server-side APIs created with createJayService. Other plugins and page components consume them via .withServices(MARKER).
Context Entry Fields
name— Context name (for identification in plugins-index)marker— Exported context marker constant (e.g.,MY_CART_CONTEXT)description— What reactive state this context providesdoc— (optional) Path to a markdown file documenting the context API
Contexts are client-side reactive state. Other plugins and page components consume them via .withContexts(MARKER).
Documentation Files
When doc is specified, the markdown file must exist and (for NPM packages) be exported in package.json:
services:
- name: my-store
marker: MY_STORE_SERVICE_MARKER
description: Product catalog API
doc: ./docs/my-store-service.md
{
"exports": {
"./docs/my-store-service.md": "./docs/my-store-service.md"
}
}
Route Entry Fields
path— Route path (e.g.,/admin/products,/dashboard/[section])jayHtml— Path to the page's jay-html template (relative to plugin root, or export subpath for NPM)css— (optional) Path to the page's CSS filecomponent— Path to the page component (relative to plugin root, or exported member name for NPM)description— What this page does
Plugin routes are served by the dev server alongside project routes. If a project defines the same route path, the project's page takes precedence.
Command Entry Fields
name— Command name (used withjay-stack run <plugin>/<command>)command— (optional) Path to.jay-commandmetadata file (declares description and input schema)
Commands are CLI operations run via jay-stack run. Use makeCliCommand() to create handlers with service injection. See commands-guide.md.
Validator Entry Fields
name— Validator name (shown in validation output asplugin-name/validator-name)handler— Export name (NPM plugins) or relative path (local plugins) to the validator functiondescription— (optional) What this validator checks
NPM plugins: handler is the export name from the package entry point (e.g., validateMediaOptimization). The function must be exported from lib/index.ts.
Local plugins: handler is a relative path to the module (e.g., ./validators/media-validator). The module must export a validate function.
Validators run during jay-stack validate against every parsed jay-html file in the project. See validation.md for implementation details.
Setup and agent-kit fields
setup— Export name (NPM) or relative path (local) forjay-stack setup <plugin>. Creates config files, validates credentials and services.agentkit— Export name (NPM) or relative path (local) forjay-stack agent-kit. Generates discovery data: add-menu catalogs, reference files, skills, thumbnails.description— (optional, top-level) Human-readable description of what setup validates
NPM plugins: setup and agentkit are export names from the package entry point.
Local plugins: relative paths to the handler modules.
jay-stack validate-plugin checks that declared handlers exist and are correctly exported.
See setup-guide.md for implementation details.
Package Layout
Standalone NPM Package
my-plugin/
├── plugin.yaml
├── package.json
├── lib/
│ ├── contracts/
│ │ ├── product-page.jay-contract
│ │ └── product-search.jay-contract
│ ├── actions/
│ │ ├── search-products.jay-action
│ │ └── add-to-cart.jay-action
│ ├── commands/
│ │ └── upload-public.jay-command
│ ├── webhooks/
│ │ └── on-product-change.ts
│ ├── validators/
│ │ └── media-validator.ts
│ ├── components/
│ │ ├── product-page.ts
│ │ └── product-search.ts
│ ├── services/
│ │ └── products-db.ts
│ └── init.ts
├── agent-kit/ # Optional: plugin-contributed guides
│ ├── designer/
│ │ └── my-plugin-usage.md
│ └── developer/
│ └── my-plugin-config.md
└── dist/
Inline Plugin (within a project)
my-project/
├── src/
│ ├── pages/
│ │ └── ...
│ └── plugins/
│ └── my-plugin/
│ ├── plugin.yaml
│ ├── product-page.jay-contract
│ ├── product-page.ts
│ └── init.ts
See examples/jay-stack/fake-shop for a working example.
Dual Entry Points
Jay plugins are fullstack — they run on both server and client. The build produces two bundles:
- Server (
dist/index.js) — actions, services, SSR rendering,init(). Built withvite build --ssr. - Client (
dist/index.client.js) — components for hydration, context tokens,init(). Built withvite build.
Create two entry files:
| File | Exports |
|---|---|
lib/index.ts |
Actions, services, components (SSR), init, service markers |
lib/index.client.ts |
Components (hydration), context markers, init |
Actions and service providers are server-only. Components appear in both entries.
Build Scripts
{
"scripts": {
"build": "npm run clean && npm run definitions && npm run build:client && npm run build:server && npm run build:copy-assets && npm run build:types && npm run validate",
"definitions": "jay-cli definitions lib",
"build:client": "vite build",
"build:server": "vite build --ssr",
"build:copy-assets": "cp lib/*.jay-contract* dist/",
"build:types": "tsup lib/index.ts lib/index.client.ts --dts-only --format esm",
"validate": "jay-stack-cli validate-plugin",
"clean": "rimraf dist"
}
}
The vite.config.ts uses isSsrBuild to switch entry points:
import { resolve } from 'path';
import { defineConfig } from 'vite';
import { jayStackCompiler } from '@jay-framework/compiler-jay-stack';
const jayOptions = { tsConfigFilePath: resolve(__dirname, 'tsconfig.json'), outputDir: 'build' };
export default defineConfig(({ isSsrBuild }) => ({
plugins: [...jayStackCompiler(jayOptions)],
build: {
minify: false,
ssr: isSsrBuild,
emptyOutDir: false,
lib: {
entry: isSsrBuild
? { index: resolve(__dirname, 'lib/index.ts') }
: { 'index.client': resolve(__dirname, 'lib/index.client.ts') },
formats: ['es'],
},
rollupOptions: {
external: [
'@jay-framework/component',
'@jay-framework/fullstack-component',
'@jay-framework/stack-client-runtime',
'@jay-framework/stack-server-runtime',
'@jay-framework/reactive',
'@jay-framework/runtime',
],
},
},
}));
package.json Exports
For NPM packages, declare exports for both server and client entry points:
{
"name": "@my-org/my-plugin",
"type": "module",
"main": "dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./client": {
"types": "./dist/index.client.d.ts",
"default": "./dist/index.client.js"
},
"./plugin.yaml": "./plugin.yaml",
"./my-contract.jay-contract": "./dist/my-contract.jay-contract"
},
"files": ["dist", "plugin.yaml"]
}
The ./client export is required — the framework uses it for browser-side hydration code. The . export handles server-side rendering and action execution.
Plugin-Contributed Agent-Kit Guides
A plugin can include guides that are merged into the project's agent-kit during jay-stack agent-kit. Create an agent-kit/ folder with subfolders for each role:
my-plugin/
└── agent-kit/
├── designer/
│ └── my-plugin-usage.md # How to use contracts in jay-html
├── developer/
│ └── my-plugin-config.md # How to configure the plugin
└── plugin/
└── my-plugin-extending.md # How to extend the plugin
For NPM packages, include agent-kit in the files array:
{
"files": ["dist", "plugin.yaml", "agent-kit"]
}
No plugin.yaml declaration needed — the CLI discovers guides by scanning the agent-kit/ directory. Files are copied as-is into the project's agent-kit/{role}/.
File format convention: The first line after the # heading is used as the description in the INSTRUCTIONS.md index table. Write it as a short sentence explaining when to use this guide:
# Scroll Carousel
Horizontal slider with prev/next buttons and edge detection. Headless component — requires import.
## Import
...
The INSTRUCTIONS.md table will show:
| scroll-carousel.md | my-plugin | Horizontal slider with prev/next buttons and edge detection. Headless component — requires import. |
Reference Declarations
Plugins can declare reference data generated by jay-stack agent-kit:
# In plugin.yaml
references:
- name: product-catalog
description: All products with IDs, slugs, names, and prices
file: product-catalog.json
- name: collection-schemas
description: Collection field schemas for filtering
file: collection-schemas.json
The agent-kit command generates references/<plugin>/INDEX.md from these declarations.
About this document
This page is part of the Jay Stack Agent Kit — documentation generated from the framework source and written primarily for AI agents. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation. Learn more about the Agent Kit →