Plugin Validation
Plugin Validation
Plugin Developer Agent Kit — documentation written for AI agents, readable by humans.
Two separate validation commands:
jay-stack validate-plugin— validates your plugin's own structure (run during plugin development)jay-stack validate— validates a project's jay-html files, including running your plugin's custom validators (run from a project that uses your plugin)
validate-plugin
Run jay-stack validate-plugin to check your plugin for errors.
Usage
# Validate the current plugin
jay-stack validate-plugin
# Validate a specific plugin path
jay-stack validate-plugin --path ./src/plugins/my-plugin
# Verbose output
jay-stack validate-plugin -v
What It Checks
Contract Validation
- YAML structure is valid
- All required fields are present (
name,tags) - Tag types are valid (
data,variant,interactive,sub-contract) dataTypeis valid for the tag typetrackByreferences an existing data tag with string/number typerepeatedsub-contracts havetrackBy- Phase constraints are satisfied (children >= parent for arrays)
- No duplicate tag names at the same level
- Props have valid types and unique names
Plugin Structure
plugin.yamlexists and is valid YAML- Contract files referenced in
plugin.yamlexist - Component export names are valid strings (not file paths)
- Action metadata files (
.jay-action) exist
Type Generation
- Contracts compile to valid TypeScript types
- Generated ViewState, Refs, Props, and Params interfaces are correct
Common Errors
"trackBy references non-existent tag"
The trackBy field must reference a data tag within the same sub-contract:
# Wrong — trackBy references a tag that doesn't exist
- tag: items
type: sub-contract
repeated: true
trackBy: itemId # No tag named "itemId"
tags:
- tag: id # Should be "itemId" or trackBy should be "id"
type: data
dataType: string
"Child phase earlier than parent"
Array children must have phase >= parent:
# Wrong
- tag: items
type: sub-contract
repeated: true
trackBy: id
phase: fast
tags:
- tag: name
type: data
phase: slow # Error: slow < fast
"Interactive tag cannot have explicit phase"
Interactive tags are always fast+interactive:
# Wrong
- tag: button
type: interactive
elementType: HTMLButtonElement
phase: slow # Error: remove this line
"Component looks like a path"
The component field in plugin.yaml should be an export name, not a file path:
# Wrong
component: ./lib/components/product-page.ts
# Right
component: productPage
Head Tags Declaration
If your component provides head tags dynamically (via phaseOutput({ headTags })), declare them in plugin.yaml so validators don't warn about missing tags on pages using your component:
contracts:
- name: product-page
contract: product-page.jay-contract
component: productPage
headTags:
- title
- meta:description
- link:canonical
Values: title, meta:<name> (e.g., meta:description), link:<rel> (e.g., link:canonical).
Validators access this via ctx.headlessImports[].providedHeadTags.
Plugin Validators (jay-stack validate)
Plugins can provide custom jay-html validation rules that run during jay-stack validate in projects that use your plugin. Declare validators in plugin.yaml:
validators:
- name: media-optimization
handler: validateMediaOptimization # export name from package entry point
description: Ensures media URLs use resize parameters
Handler format:
- NPM plugins —
handleris an export name from the package entry point (e.g.,validateMediaOptimization). The function must be exported fromlib/index.ts. - Local plugins (
src/plugins/) —handleris a relative path to the module (e.g.,./validators/media-validator). The module must export avalidatefunction.
jay-stack validate-plugin checks that the handler exists and is correctly exported.
Writing a Validator
Export the validator function from the package entry point (for NPM) or from the handler module (for local):
import type { JayHtmlValidatorFn, JayHtmlValidationFinding } from '@jay-framework/compiler-shared';
export const validate: JayHtmlValidatorFn = (ctx) => {
const findings: JayHtmlValidationFinding[] = [];
// ctx.body — parsed DOM tree (HTMLElement from node-html-parser)
// ctx.head — parsed <head> metadata (title, meta tags, link tags)
// ctx.filePath — relative path to the jay-html file
// ctx.contract — page contract (if any), with tags including meta
// ctx.headlessImports — headless components used in this file
// .providedHeadTags — head tags the component declares in plugin.yaml
// ctx.projectRoot — absolute project root path
return findings;
};
Each finding has:
severity—'error'(fails validation) or'warning'message— what's wrongsuggestion— how to fix it (shown to agents and developers)element— (optional) which elementattribute— (optional) which attribute
Validator Utilities
parseTemplateParts(value)— split"{url}/v1/fit/w_300/file.jpg"into binding and static parts (import from@jay-framework/compiler-jay-html)walkElements(root, ctx, visitor)— depth-first traversal tracking data scope throughforEachand<jay:component>boundaries (import from@jay-framework/compiler-shared)resolveBinding(path, scope)— resolve a binding path to its contract tag (includingmeta) (import from@jay-framework/compiler-shared)
Contract Tag meta
Contract tags can carry a meta field — arbitrary key-value metadata that validators read:
tags:
- tag: imageUrl
type: data
dataType: string
meta:
vendor: wix-image
defaultTransform: w_300,h_200,q_80
Validators use resolveBinding to find the tag and inspect meta:
import { walkElements, resolveBinding } from '@jay-framework/compiler-shared';
import { parseTemplateParts } from '@jay-framework/compiler-jay-html';
export const validate: JayHtmlValidatorFn = (ctx) => {
const findings: JayHtmlValidationFinding[] = [];
walkElements(ctx.body, ctx, (el, scope) => {
if (el.rawTagName !== 'img') return;
const src = el.getAttribute('src');
if (!src) return;
for (const part of parseTemplateParts(src)) {
if (part.kind !== 'binding') continue;
const resolved = resolveBinding(part.value, scope);
if (resolved.tag?.meta?.vendor !== 'wix-image') continue;
findings.push({
severity: 'warning',
message: `Image binding {${part.value}} may need resize parameters`,
suggestion: 'Add /v1/fit/w_{WIDTH},h_{HEIGHT},q_80/file.jpg after the binding',
});
}
});
return findings;
};
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 →