This is the central schema repository for the Meshery platform. Schemas here drive Go struct generation, TypeScript type generation, and RTK Query client generation. Mistakes in schema design propagate into generated code across multiple downstream repos (meshery/meshery, layer5io/meshery-cloud).
The source of truth for a construct's API contract depends on where it is in the migration lifecycle:
- Pre-migration — While a construct is being migrated from a downstream repository (e.g.,
layer5io/meshery-cloud) into meshery/schemas, the downstream implementation is the reference for field discovery: field names, types, JSON tags, and database column mappings. - Post-migration — Once a construct has been fully migrated and its schema is defined here, meshery/schemas becomes the permanent, authoritative source of truth. Downstream repositories must conform to the contract defined here, not the reverse.
For constructs that have been migrated:
- When a downstream repository's implementation diverges from the schema contract defined here, this repository is correct and the downstream code must be updated.
- When cross-construct consistency requires a change that conflicts with current downstream implementation, make the breaking change here and open issues in affected repositories documenting the required migration.
- Do not weaken schema contracts, skip validation rules, or introduce inconsistent patterns to accommodate legacy downstream code.
make build # generate Go structs + TypeScript types + RTK clients
make validate-schemas # run repository schema validation rules
npm run build # build TypeScript distribution (dist/)Generated artifacts (models/, typescript/generated/) are committed by automation on master. The TypeScript distribution in dist/ is produced by the npm build/publish workflow and is not committed to this repo. Do not edit generated artifacts by hand, and do not manually commit regenerated output in normal PRs unless the change explicitly requires it.
This is the most critical design rule in this repo. Every agent or contributor MUST follow it.
The YAML file for an entity represents the full server-side object as returned in API responses. It is NOT a request body schema.
Required properties of every entity .yaml:
additionalProperties: falseat the top level- All server-generated fields defined in
properties:id,created_at,updated_at,deleted_at - Server-generated fields that are always present belong in
required
# CORRECT: keychain.yaml
type: object
additionalProperties: false
required:
- id
- name
- owner
- created_at
- updated_at
properties:
id:
$ref: ../../v1alpha1/core/api.yml#/components/schemas/uuid
name:
type: string
owner:
$ref: ../../v1alpha1/core/api.yml#/components/schemas/uuid
created_at:
$ref: ../../v1alpha1/core/api.yml#/components/schemas/created_at
updated_at:
$ref: ../../v1alpha1/core/api.yml#/components/schemas/updated_at
deleted_at:
$ref: ../../v1alpha1/core/api.yml#/components/schemas/nullTimeFor any entity that has POST or PUT operations, define a {Construct}Payload schema in api.yml that:
- Contains only client-settable fields — no
created_at,updated_at,deleted_at - Makes
idoptional withjson:"id,omitempty"for upsert patterns - Is the schema referenced by all
requestBodyentries forPOST/PUT
# CORRECT: in api.yml
components:
schemas:
KeychainPayload:
type: object
description: Payload for creating or updating a keychain.
required:
- name
properties:
id:
$ref: ../../v1alpha1/core/api.yml#/components/schemas/uuid
description: Existing keychain ID for updates; omit on create.
x-oapi-codegen-extra-tags:
json: "id,omitempty"
name:
type: string
owner:
$ref: ../../v1alpha1/core/api.yml#/components/schemas/uuid
x-oapi-codegen-extra-tags:
json: "owner,omitempty"# WRONG — forces clients to supply server-generated fields
post:
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Keychain"
# CORRECT — separate payload type for writes
post:
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/KeychainPayload"When uncertain, model new schemas on these:
schemas/constructs/v1beta1/connection/—connection.yaml+ConnectionPayloadinapi.ymlschemas/constructs/v1beta1/key/—key.yaml+KeyPayloadinapi.ymlschemas/constructs/v1beta1/team/—team.yaml+teamPayload/teamUpdatePayloadinapi.ymlschemas/constructs/v1beta1/environment/—environment.yaml+environmentPayloadinapi.yml
Before opening a PR, verify:
-
<construct>.yamlhasadditionalProperties: false -
<construct>.yamllists all server-generated fields inpropertiesand appropriate ones inrequired -
api.ymldefines{Construct}Payloadwith only client-settable fields - All
POST/PUTrequestBodyentries reference{Construct}Payload -
GETresponses reference the full{Construct}entity schema
- Property names: preserve published wire-format casing; new non-DB properties use
camelCase, DB-backed properties use the exact snake_case database column name - ID-suffix fields:
lowerCamelCase+Id(modelId,registrantId) - New enum values: lowercase words (
enabled,ignored,duplicate); preserve published enum literals as-is within the same API version - Object names: singular nouns (
model,component,design) components/schemasnames: PascalCase nouns (Model,Component,KeychainPayload)- Files/folders: lowercase (
api.yml,keychain.yaml,templates/keychain_template.json) - Endpoint paths:
/apiprefix, kebab-case, plural nouns (/api/workspaces,/api/environments) - Path params: camelCase with
Idsuffix ({subscriptionId},{connectionId},{orgId}— NOT{orgID}, NOT{org_id}) operationId: lower camelCase verbNoun (createKeychain,updateEnvironment— NOTCreateKeychain, NOTUpdateEnvironment)
Every element in the API has exactly one correct casing. The table below is the single authoritative reference:
| Element | Casing | Example | Counter-example |
|---|---|---|---|
| Schema property names (non-DB) | camelCase | schemaVersion, displayName |
schema_versionSchemaVersion |
| ID-suffix properties | camelCase + Id |
modelId, registrantId |
modelIDmodel_id |
| DB-backed / DB-mirrored fields | exact snake_case db column name | created_at, updated_at, user_id, first_name, plan_id |
createdAtfirstNameplanId |
| New enum values | lowercase | enabled, ignored |
EnabledENABLED |
components/schemas names |
PascalCase | ModelDefinition, KeychainPayload |
modelDefinition |
| File and folder names | lowercase | api.yml, keychain.yaml |
Keychain.yaml |
| Path segments | kebab-case, plural nouns | /api/role-holders |
/api/roleHolders |
| Path parameters | camelCase + Id |
{orgId}, {workspaceId} |
{orgID}{org_id} |
operationId |
lower camelCase verbNoun | getAllRoles, createWorkspace |
GetAllRolesget_all_roles |
| Go type names | PascalCase (generated) | Connection, KeychainPayload |
— |
| Go field names | PascalCase (generated) | CreatedAt, UpdatedAt |
— |
| TypeScript type names | PascalCase (generated) | Connection, KeychainPayload |
— |
The database naming is the compatibility boundary. If a property has x-oapi-codegen-extra-tags.db and that db value is snake_case, then the schema property name and JSON tag must use that exact snake_case name. Do not camelize DB-backed fields in-place within an existing API version.
Partial casing migrations are forbidden. Do not rename selected fields within the same resource from snake_case to camelCase while leaving other published fields unchanged. If the wire format must change, introduce a new API version and migrate the resource consistently there.
Existing enum wire values are compatibility-sensitive. Use lowercase for newly introduced enum literals, but do not recase published enum values in-place within the same API version. The validator exempts legacy enum values that already exist on the baseline branch.
Pagination envelopes are fixed API contract fields — use page, page_size, and total_count, not pageSize or totalCount.
page_size properties must have minimum: 1. A page size of zero is never valid. The validator enforces this (Rule 41) on all properties named page_size, pagesize, or pageSize.
The schema validator (validation/ Go package, invoked via go run ./cmd/validate-schemas) enforces per-property constraints as advisory rules (Rules 37–42). These do not block CI but are reported on --warn runs and should be resolved in new schemas.
| Rule | What it checks |
|---|---|
| 37 | Every property has a description |
| 38 | String properties have minLength, maxLength, pattern, format, or const |
| 39 | Numeric properties have minimum, maximum, or const |
| 40 | ID-like properties (id, *_id, *Id) have format: uuid or $ref to a UUID type |
| 41 | Page-size properties (page_size, pagesize, pageSize) have minimum: 1 |
| 42 | format values must be from the known OpenAPI 3.0 / JSON Schema set (e.g., date-time, email, uri, uuid) |
Some ID properties hold external system identifiers (e.g., Stripe subscription IDs, coupon codes) that are not UUIDs. To exempt these from Rule 40, annotate the property with x-id-format: external:
billing_id:
type: string
description: Billing ID from the external payment processor.
x-id-format: external
maxLength: 500
pattern: '^[A-Za-z0-9_\-]+$'The annotation is self-documenting: the exemption lives with the schema property where the domain knowledge is, not in a hardcoded allowlist. Use it only for properties that genuinely hold non-UUID external identifiers.
These rules govern how endpoints are structured. They are enforced in part by make validate-schemas.
| Use case | Method | Example |
|---|---|---|
| Create a resource | POST |
POST /api/workspaces → 201 |
| Upsert a resource | POST |
POST /api/keys → 200 |
| Update an existing resource | PUT or PATCH |
PUT /api/workspaces/{workspaceId} → 200 |
| Non-CRUD action on a resource | POST to a sub-resource path |
POST /api/invitations/{invitationId}/accept |
| Bulk delete | POST to a /delete sub-resource |
POST /api/designs/delete → 200 |
| Single delete | DELETE |
DELETE /api/keys/{keyId} → 204 |
Do NOT use DELETE with a request body for bulk operations. REST semantics do not define a request body for DELETE; many HTTP clients and proxies strip it silently. Use a POST /api/{resources}/delete sub-resource instead:
# WRONG — DELETE with a request body
delete:
operationId: deletePatterns
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatternIds'
# CORRECT — POST sub-resource for bulk delete
post:
operationId: deletePatterns
summary: Bulk delete patterns by ID
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatternIds'
responses:
"200":
description: Patterns deleted| Code | Meaning | When to use |
|---|---|---|
| 200 | OK | Request succeeded; body contains the result (queries, upserts, actions) |
| 201 | Created | A new resource was created; body contains the new resource |
| 202 | Accepted | Request received; operation will complete asynchronously |
| 204 | No Content | Request succeeded; no response body (e.g., a single-resource DELETE) |
Use 201 (not 200) for POST endpoints that exclusively create a new resource. Use 200 for upsert operations where the resource may already exist.
Response descriptions and response message text must not include the word successfully. Use neutral wording such as Connection deleted, Webhook processed, or Plans response.
Endpoints are grouped into logical categories under /api:
| Category prefix | Domain |
|---|---|
/api/identity/ |
Users, orgs, roles, teams, invitations |
/api/integrations/ |
Connections, environments, credentials |
/api/content/ |
Designs, views, components, models |
/api/entitlement/ |
Plans, subscriptions, features |
/api/auth/ |
Tokens, keychains, keys |
New endpoints must be placed in the appropriate category. Path segments must be kebab-case plural nouns matching the resource name.
schemas/constructs/v1beta1/<construct>/
api.yml # OpenAPI spec: endpoints + all schema definitions
<construct>.yaml # Entity (response) schema
templates/
<construct>_template.json # Example instance
<construct>_template.yamlAuto-generated Go structs (models/<version>/<construct>/<construct>.go) are committed by the artifact-generation workflow on master. Do not edit them by hand; the manually written helpers below are the files contributors should maintain directly:
models/v1beta1/<construct>/
<construct>.go # Auto-generated — DO NOT edit
<construct>_helper.go # Manual — add SQL driver, Entity interface, TableName(), etc.Always add // This is not autogenerated. at the top of helper files.
Use x-generate-db-helpers: true on a schema component to auto-generate Scan/Value SQL driver methods for types stored as JSON blobs in a single DB column. Do NOT use this for types mapped to full DB tables.
Required on every operation. The bundler and validate-schemas Rule 14 reject operations that omit it. Use the annotation to declare which bundled outputs include the path:
x-internal: ["cloud"]— cloud-only (_openapi_build/cloud_openapi.yml)x-internal: ["meshery"]— Meshery-only (_openapi_build/meshery_openapi.yml)x-internal: ["cloud", "meshery"]— both bundled outputs
See The Dual-Schema Pattern above for the canonical entity/payload rules and reference examples used throughout this repo.
When manually implementing sql.Scanner and driver.Valuer for map-like types:
core.Map marshals nil maps to the JSON string "null" instead of SQL NULL. Prefer the same behavior for new or updated helpers unless the column is explicitly nullable and the nil-vs-empty distinction is required and documented.
// CORRECT — matches core.Map pattern
func (m MapObject) Value() (driver.Value, error) {
b, err := json.Marshal(m)
if err != nil {
return nil, err
}
return string(b), nil
}
// WRONG — writes SQL NULL, inconsistent with core.Map
func (m MapObject) Value() (driver.Value, error) {
if m == nil {
return nil, nil // <- do not do this
}
...
}When src is nil (SQL NULL), new or updated Scan implementations should explicitly zero the receiver. Some legacy helpers return early, but clearing the receiver avoids stale data if the same struct is reused across rows.
// CORRECT
case nil:
*m = nil
return nil
// WRONG — leaves stale data
case nil:
return nilThese patterns are deliberate. Do not suggest changes during code review:
SqlNullTimevsNullTime— Some entities useSqlNullTimefor backward compatibility with v1beta1 and downstream GORM/Pop consumers. Do not suggest switching unless the entire entity is being migrated.- Core Go package — All core types (both generated scalars like
Uuid,Time,Idand manual utilities likeMap,NullTime,MapObject) live in a single package:github.com/meshery/schemas/models/core. Generator output path overrides and Go import overrides map all schema core versions (v1alpha1/core,v1beta1/core,v1beta2/core) to this single package. Schemax-go-type-importfor any core type must usemodels/corewith aliascore. x-enum-casing-exempt: true— Enums with this annotation contain published values that will never be lowercased (e.g.,PlanName,FeatureName). Do not suggest lowercasing.page_size/total_count— Pagination envelope fields use snake_case as a published API contract, not because they are database-backed. Do not suggestpageSize/totalCount.- Deprecated v1beta1 constructs — Files with
x-deprecated: trueare kept for backward compatibility. Known casing violations are fixed in v1beta2. Do not flag issues in deprecated constructs. - Same field name, different casing across constructs — A property like
subTypemay be camelCase in one construct (not DB-backed) andsub_typein another (DB-backed withdb: "sub_type"). Both are correct. Casing is determined per-property by whether it maps to a database column in that specific construct, not by what other constructs use. x-id-format: external— ID properties annotated with this hold external system identifiers (e.g., Stripe IDs) that are not UUIDs. The validator skipsformat: uuidenforcement for these. Do not remove the annotation or addformat: uuid.
- ❌ Hand-editing generated Go code in
models/directory - ❌ Hand-editing generated TypeScript code in
typescript/generated/directory - ❌ Hand-editing built files in
dist/directory - ❌ Using deprecated
core.jsonreferences - ❌ Adding redundant
x-oapi-codegen-extra-tagswhen using schema references - ❌ Forgetting to update template files in the
templates/subdirectory with default values - ❌ Not testing the build process after schema changes
- ❌ Placing template files outside the
templates/subdirectory - ❌ Using
.d.tsextension in TypeScript import paths - ❌ Assuming schema property names are PascalCase (check actual generated
.d.tsfiles) - ❌ Adding
x-generate-db-helperson individual properties — it must be at the schema component level - ❌ Using
x-generate-db-helperson amorphous types without a fixed schema — usex-go-type: "core.Map"instead - ❌ Using the full entity schema as a
POST/PUTrequestBody— always use a separate*Payloadschema - ❌ Omitting
additionalProperties: falsefrom entity<construct>.yamlfiles - ❌ Adding new
Value()implementations that return(nil, nil)unless SQL NULL behavior is explicitly required and documented - ❌ In new
Scan()implementations, returning without zeroing the receiver whensrcis nil - ❌ Using PascalCase for new
operationIdvalues — always lower camelCase (getPatterns, notGetPatterns) - ❌ Using SCREAMING_CASE path parameters (
{orgID},{roleID}) — always camelCase withIdsuffix ({orgId},{roleId}) - ❌ Using
DELETEwith a request body for bulk operations — usePOST /api/{resources}/deleteinstead - ❌ Returning 200 from a
POSTthat exclusively creates a new resource — use 201 - ❌ Using all-lowercase
id/urlsuffixes in parameter names — always capitalize (workspaceId, notworkspaceid;pageUrl, notpageurl) - ❌ Template files with wrong value types — if schema says
type: array, use[]not{}; iftype: string, use""not{} - ❌ Adding
format: uuidto ID properties that hold external system identifiers (Stripe IDs, etc.) — usex-id-format: externalinstead - ❌ Setting
minimum: 0on page-size properties — page size must be at least 1 - ❌ Omitting
tagsfrom operations — every operation must have at least one tag for API documentation and client generation
- Modified only schema JSON/YAML files (not generated code)
- Updated corresponding template files in
templates/subdirectory with default values - Used non-deprecated
v1alpha1/core/api.ymlreferences - If adding new schemas, referenced them from
api.yml(the construct index file) - Removed redundant tags when using schema references
- If a schema type is stored as a JSON blob in a DB column AND has a dedicated schema definition, used
x-generate-db-helpers: trueat the schema component level (not per-property) - Ran
make buildsuccessfully - Ran
go test ./...successfully - Ran
npm run buildsuccessfully - Verified only schema JSON/YAML files are in the commit
- If updating
typescript/index.ts, verified import paths are correct - (New entity)
<construct>.yamlhasadditionalProperties: false - (New entity)
<construct>.yamlincludes all server-generated fields inpropertiesandrequired - (New entity with writes)
api.ymldefines a{Construct}Payloadwith only client-settable fields - (New entity with writes) All
POST/PUTrequestBodyentries reference{Construct}Payload, not{Construct} - (New SQL driver)
Value()always marshals — never returns(nil, nil) - (New SQL driver) Prefer
Scan()implementations that set*m = nilwhensrcis nil; some legacy drivers may still return early - (New endpoint)
operationIdis lower camelCase verbNoun - (New endpoint) Path parameters are camelCase with
Idsuffix (e.g.,{workspaceId}, not{workspaceID}) - (New endpoint) No
DELETEoperation has arequestBody— bulk deletes usePOST .../delete - (New
POSTfor creation only) Response code is 201, not 200 - (New property) String properties have
description,maxLength, and where appropriateminLengthorpattern - (New property) Numeric properties have
minimum,maximum, orconst - (New property) ID properties have
format: uuid(or$refto UUID type), ORx-id-format: externalif they hold non-UUID external identifiers - (New property) Page-size properties have
minimum: 1 - (New endpoint) Operation has at least one
tagsentry matching the construct's top-level tag definition
If you're unsure about any schema modification:
- Check existing schemas for patterns (e.g.,
environment.yaml,connection.yaml) - Look at
schemas/constructs/v1alpha1/core/api.ymlfor available core schema definitions - Examine any construct's
api.ymlto see how subschemas are referenced and endpoints are defined - Check generated
.d.tsfiles for actual type/property names - Review this document for guidelines
- Test your changes with
make buildbefore committing