Schema-Driven UI Development in Meshery
Meshery aims to decouple the UI logic from hardcoded structures and become fully schema-driven. This allows the UI to dynamically adapt based on changes in the underlying schema without requiring constant manual updates. This document explains how to integrate schema definitions from the meshery/schemas repository into the Meshery UI using a code-generation approach.
Overview
Mesheryβs schemas define the structure of data (constructs) using JSON Schema and their behavior (API operations) using OpenAPI specifications. From these definitions, Meshery auto-generates:
- Go structs for backend validation and API handling
- TypeScript types and objects for frontend development
- Templates in JSON and YAML formats
This approach ensures the schemas remain the single source of truth.
Repository Structure
All schema definitions live in the meshery/schemas repository.
schemas/
constructs/
v1beta1/
design/
design.json # JSON schema for the noun
design_template.json # JSON template (generated)
design_template.yaml # YAML template (generated)
openapi.yml # OpenAPI spec defining operations
subschemas/ # Optional reusable schema parts
<construct>.json: Defines structure (noun) β e.g., what aDesignlooks like.openapi.yml: Describes operations (verbs) β e.g., how toGET,POST, orDELETEa design.Templates: Valid, default-filled, resolved objects from the schema.
Schema-Driven UI Development Workflow
Meshery follows a schema-first approach where the UI is driven by JSON schemas and OpenAPI specifications. Hereβs how you can contribute to and use these schemas in the Meshery UI.
Step 1: Define or Update the Schema
If youβre introducing or modifying a UI feature that requires a new schema:
-
Navigate to the appropriate schema directory:
schemas/constructs/<version>/<construct>/ -
Define the schema structure in
<construct>.jsonusing JSON Schema v7. -
Define API operations related to this schema in
openapi.ymlusing OpenAPI v3. This helps generate consistent API types and models that align with your UI needs.
Step 2: Generate TypeScript Types and Schema Objects
Use the following command to generate TypeScript types and JavaScript schema objects:
make generate-types
This will:
-
Generate
.tsand.d.tsfiles under:typescript/constructs/<version>/<construct>/ -
Create:
- Typed interfaces for schema validation and component props
- Schema objects usable for dynamic UI (e.g., forms, validations)
Step 3: Build and Export Types/Schema Objects
After generation:
-
Open
typescript/index.ts- Export the newly generated types and schema objects from here.
-
Example:
export * from "./constructs/v1beta1/design";
-
Build the TypeScript package to make the changes usable:
npm run build
Step 4: Use Schema Package in UI
To consume the schema in the UI:
-
Install the schema package locally:
npm install <path-to-schemas-repo>Examples:
-
Relative path:
npm install ../../schemas -
Absolute path:
npm install /home/user/code/schemas
-
-
This will update your
package.jsonto something like:"@meshery/schema": "file:../../schemas" -
Now you can import types and schema objects in your UI components:
Example: Importing and Using a Type
import { DesignTypes } from "@meshery/schema";
// Type safety!
const renderDesignCard = (design: Design) => <div>{design.name}</div>;
Example: Accessing Runtime Schema
import { DesignSchema } from "@meshery/schema";
const validateDesign = (data) => {
const isValid = ajv.validate(DesignSchema, data);
return isValid;
};
Can't find a Schema Import in the UI?
If you're trying to import a schema object or type in the UI but it's missing, it's likely because it hasn't been exported yet from the schemas package.
To keep the package lightweight, only actively used types and objects are exported by default. If you need access to a new schema, simply export it in the Meshery Schemas repository and regenerate the package.
Integration Points in UI
A. RJSF JSON Schemas
Meshery uses react-jsonschema-form to render forms dynamically based on JSON schemas. All of Mesheryβs RJSF schemas are defined in the @sistent/sistent package, which extends schemas from the @meshery/schema package.
This approach enables us to generate forms that automatically adapt to the schema structure without hardcoding field properties like type, enum, description, and others.
import { DesignSchema } from "@meshery/schema";
const designSchema = {
...DesignSchema,
properties: {
...DesignSchema.properties,
name: {
...DesignSchema.properties.name,
description: DesignSchema.properties.description,
ui: {
label: DesignSchema.properties.name.title,
placeholder: DesignSchema.properties.name.description,
},
},
// Other properties with UI-specific enhancements
},
};
B. General Form UI
OpenAPI schemas (especially request bodies for POST/PUT operations) serve as the foundation for building form logic. These definitions include:
- Field validations (e.g.
required,format,maxLength) - Field types and formats (e.g.
string,integer,date-time) - Descriptions and examples
- Enum constraints and conditional logic
- Custom extensions like
x-rjsf-*for layout
This ensures alignment between frontend form behavior and backend expectations.
import { DesignTypes } from "@meshery/schema";
const DesignForm = ({ design }: { design: DesignTypes }) => (
<form>
<input type="text" value={design.name} />
{/* More fields derived from the schema */}
</form>
);
C. UI-Specific Descriptions and Enhancements
Any UI-specific metadataβsuch as name, type, hints, descriptions, defaults, etc.βis defined directly within the relevant schema object. Elements like tooltips, descriptions, and other metadata are frequently needed across the UI, so having a single source of truth in the schema object ensures consistency and reduces duplication.
For example, if we have a Design schema, the UI retrieves details like the designβs name, description, and other properties directly from the schema object.
D. Type Safety for Component Props
Generated TypeScript types from the schema ensure UI components are type-safe and consistent with backend contracts.
import { DesignTypes } from "@meshery/schema";
const DesignCard = ({ design }: { design: DesignTypes }) => (
<div>
<h2>{design.name}</h2>
<p>{design.description}</p>
</div>
);