Skip to main content
Version: 0.1.0

Schema Tables

Four reserved table names read the type registry rather than content nodes. They are how you ask RaisinDB about its own schema from SQL — which node types exist, what fields an archetype has, what a workspace permits.

TableContentsWrites
NodeTypesRegistered node typesDDL only — CREATE/ALTER/DROP NODETYPE
ArchetypesPage templatesDDL only — CREATE/ALTER/DROP ARCHETYPE
ElementTypesContent blocksDDL only — CREATE/ALTER/DROP ELEMENTTYPE
WorkspacesWorkspace definitionsNone — read-only
SELECT name, base_node_type, extends, title, fields, meta FROM Archetypes
SELECT name, allowed_children FROM NodeTypes
SELECT name, extends, description, fields, meta FROM ElementTypes
SELECT name, allowed_node_types, allowed_root_node_types FROM Workspaces

These names are reserved: a content workspace cannot be called nodetypes, archetypes, elementtypes, or workspaces, because the schema table shadows it.

Why this matters in server-side functions

Inside a server-side function, raisin.sql is the only route to schema information — the function runtime has no workspaces or types binding, and raisin.asAdmin() re-exposes only nodes and SQL. A function that needs to answer "what may be created here?" reads these tables.

Nothing is cached. Deploy a new archetype and it is visible to the next query, with no catalog to rebuild.

Three rules

Full-table SELECTs only

An equality filter on the primary key plans a point-lookup that bypasses the schema-table read path and silently returns no rows. Read the table and filter in your own code.

-- WRONG — returns nothing, with no error
SELECT fields FROM Archetypes WHERE name = 'news:ArticlePage'

-- RIGHT — read all, filter client-side
SELECT name, fields FROM Archetypes

ElementTypes has no title column

Selecting it errors the whole query. Use name and description.

fields and meta are raw, not merged

The stored fields plus extends are exposed as-is. If you need the inheritance-merged schema, walk the extends chain yourself, or use the resolved-archetype endpoint in the HTTP API.

Workspaces

Read-only, and repo-scoped rather than branch-scoped — workspaces are shared across branches and carry no revision history, so time-travel and branch filters do not apply to this table.

ColumnTypeDescription
nameTEXTWorkspace name (primary key)
descriptionTEXTOptional description
allowed_node_typesJSONBNode types permitted anywhere in the workspace
allowed_root_node_typesJSONBNode types permitted at the workspace root
depends_onJSONBWorkspaces this one depends on
initial_structureJSONBNodes seeded when the workspace is created
configJSONBWorkspace configuration (default branch, node type pins)
created_atTEXTCreation timestamp
updated_atTEXTLast modification timestamp

Writes are rejected. Creating a workspace also builds its nodes table, seeds initial_structure and registers it in the SQL catalog — a row write would do none of that, so define workspaces in your package's workspaces/*.yaml and install it, or use the management API.

-- Rejected:
INSERT INTO Workspaces (name) VALUES ('reports')
-- 'Workspaces' is read-only: workspaces are defined by package install
-- (workspaces/*.yaml) or the management API, not by SQL.

Answering "what can be created here?"

allowed_node_types / allowed_root_node_types are the coarse, server-enforced containment rule. Combined with NodeTypes.allowed_children — structural composition, which applies to a typed parent — they are what an admin UI's create menu is built from:

-- What may exist in this workspace at all, and at its root?
SELECT name, allowed_node_types, allowed_root_node_types FROM Workspaces

-- What does a given parent type accept as children?
SELECT name, allowed_children FROM NodeTypes

The two answer different questions and both apply:

  • Workspace — scope-wide containment. A generic folder has no opinion of its own (the same folder type holds tags in one workspace and pages in another), so only the workspace can answer what belongs inside it.
  • allowed_children — structural composition for a type that genuinely constrains its children regardless of workspace.
An empty allowed_children means "no constraint" to the server

The server skips the check when the list is empty. UIs that offer choices often read empty as "this type is a leaf" instead — if you are building an offering menu, decide which reading you want and be explicit about it.