Concepts

Collections and schema versions

Code-owned content models, public and private visibility, and immutable schema versions.

A collection defines the shape of a set of entries: its fields, validation rules, slug behavior, and how it is exposed. Collections are defined in code with @myna-sh/sdk/schema and deployed with myna schema push — the dashboard displays deployed schemas but never edits them. This keeps one source of schema truth, reviewable in the same pull requests as the code that consumes it.

import { collection, field } from "@myna-sh/sdk/schema";

export const posts = collection({
  name: "posts",            // immutable key
  label: "Blog posts",      // editable display name
  kind: "collection",       // or "singleton"
  visibility: "public",     // default: "private"
  titleField: "title",
  path: "/blog/{slug}",
  fields: {
    title: field.text({ required: true, maxLength: 120 }),
    slug: field.slug({ from: "title", required: true }),
    body: field.markdown({ required: true }),
  },
});

Collections and singletons

kind is collection or singleton:

  • A collection holds many entries. It gets slugs when it says its content lives at a URL — by declaring a field.slug(), or a path template that has to build one. Slugs are unique within the collection, validated (lowercase-kebab-case), auto-generated from the configured source field when omitted, and old slugs are preserved as aliases when an entry's slug changes.
  • A collection that declares neither has no slugs: its entries are addressed by id and slug is null. Nothing is required of you to get that — a collection of testimonials, feature flags, or pricing tiers is not a set of pages, and Myna does not ask it to invent URLs. Naming a slug when creating an entry in such a collection is refused rather than ignored, because the caller believes they have addressed something.
  • A singleton holds one document with no slug — site settings, a homepage, a footer. The public content API reads it at GET /v1/projects/:project/collections/:collection/singleton.

Public and private visibility

visibility is public or private, defaulting to private:

Visibility Public content API Management API
public Published entries readable without credentials (when the project's public API is enabled) Full access with scopes
private Reads require a credential with content:read (AUTHENTICATION_REQUIRED otherwise) Full access with scopes

GET /v1/projects/:project/collections is dual-mode: callers with schema:read get the full management view; everyone else gets only the public collection list. Switching a collection from public to private is classified conditionally_destructive in the schema diff because it can break consumers; public-ward changes are safe.

Immutable keys

A collection's key (the name in code) is immutable, as is every field key. Renaming in the schema source is not a rename to Myna — it is a remove plus an add, a destructive pair that drops the old data. Labels, descriptions, defaults, and other UI metadata are editable freely; keys are contracts.

Schema versions

Every push that changes a collection's schema creates an immutable collection_version with an increasing version number, the canonical schemaJson, and a schema_hash (SHA-256 of the canonical JSON). Versions are never edited in place, and (collection, schema_hash) is unique: pushing a schema identical to the deployed one is a no-op, and pushing a schema that matches an earlier version's hash rolls back by pointing the collection's current version at that existing version rather than minting a duplicate.

Entry revisions record the collectionVersionId they were written under, so historical content always pairs with the schema that validated it.

$ myna schema diff
# POST /v1/projects/:project/schema/diff — a plan, no writes
$ myna schema push --allow-destructive
# POST /v1/projects/:project/schema/push

diff classifies every operation as safe, conditionally_destructive, or destructive and validates all current draft and published entry heads against the proposed schemas. push refuses destructive changes without allowDestructive (SCHEMA_CHANGE_DESTRUCTIVE, HTTP 409) and refuses schemas that existing entries violate (VALIDATION_FAILED, HTTP 422) — fix or migrate the content first. See error codes and CLI commands.

Version history is inspectable at GET /v1/projects/:project/collections/:collection/versions, each version carrying actor attribution and an optional changeSummary.

Path templates

A collection may declare a path template such as /blog/{slug}. Myna does not route your site — the template exists so tooling can point at real pages: preview creation substitutes an entry's slug into the template to build the {path} placeholder of your customer preview URL.