Moul

Collections & Schemas

Understand Moul collections, schema modeling, collection types, and the 10 supported dynamic field types.

In Moul, a Collection (internally called a Moul) represents a dynamic SQLite database table together with its schema, field validation constraints, and access control rules.

Collections can be created, updated, inspected, or deleted dynamically at runtime via the HTTP API, the Web Admin Console (/_moul_/), the TUI (moul), or AI assistants via MCP without restarting the server.


Collection Types

Moul supports four distinct collection types tailored for different application architectures:

TypeDescriptionBuilt-in Capabilities
baseStandard dynamic relational database table.Dynamic CRUD, custom fields, HCL access rules, webhooks, SSE.
authUser account & authentication collection.Automatic password hashing, Email OTP, Passkey WebAuthn, OAuth2 social login, session refresh/logout.
workerAsynchronous background job queue.Oban-style job dispatch, state transitions, automatic retries with exponential backoff, priority sorting.
analyticFirst-party visitor and event tracking table.Automatic client header parsing (IP, User-Agent, Referrer, UTM), visitor session deduplication, optional GeoIP resolution.

Supported Dynamic Field Types

Moul supports 10 dynamic field types with automatic SQLite column mapping, runtime validation, and OpenAPI 3.0 type generation:

Field TypeDescriptionSQLite StorageValidation ConstraintsOpenAPI Type / Format
textCharacter string dataTEXTmin, max length, pattern (regex)type: string, minLength, maxLength
numberNumeric values (integer/float)NUMERICmin, max numeric boundstype: number, minimum, maximum
boolBoolean flag (true/false)INTEGER (1/0)Validates boolean or 1/0type: boolean
dateCalendar dateTEXTEnforces YYYY-MM-DD ISO formattype: string, format: date
datetimeTimestamp with timezoneTEXTEnforces ISO 8601 / RFC 3339 formattype: string, format: date-time
jsonArbitrary JSON object/arrayTEXTEnforces valid JSON syntaxtype: object
urlWeb URL stringTEXTEnforces valid HTTP/HTTPS URItype: string, format: uri
fileUploaded file or S3 keyTEXTFile size bounds, MIME typestype: string
selectConstrained enum stringTEXTMust match one of options arraytype: string, enum: [...]
relationForeign key associationTEXTValidates target record ID existstype: string or type: array

Schema Management API

Create a Collection (POST /api/moul)

Schema creation and management endpoints require administrative authorization via either:

  • _rootUsers Admin JWT Bearer Token: Authorization: Bearer <root_user_token> (obtained via POST /api/moul/_rootUsers/auth-with-password).
  • Administrative Master Key: X-Admin-Key: <MOUL_ADMIN_KEY> (or Authorization: Bearer <MOUL_ADMIN_KEY>).
curl -X POST "http://localhost:8090/api/moul" \
  -H "Authorization: Bearer <root_user_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "articles",
    "type": "base",
    "fields": [
      { "name": "title", "type": "text", "required": true, "options": { "min": 3, "max": 200 } },
      { "name": "slug", "type": "text", "required": true, "unique": true },
      { "name": "content", "type": "text", "required": false },
      { "name": "views", "type": "number", "options": { "min": 0 } },
      { "name": "published", "type": "bool" }
    ],
    "rules": {
      "listRule": "published = true || @request.auth.id != \"\"",
      "viewRule": "published = true || @request.auth.id != \"\"",
      "createRule": "@request.auth.id != \"\"",
      "updateRule": "@request.auth.id != \"\"",
      "deleteRule": "@request.auth.role = \"admin\""
    }
  }'
curl -X POST "http://localhost:8090/api/moul" \
  -H "X-Admin-Key: test-admin-key-1234" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "articles",
    "type": "base",
    "fields": [
      { "name": "title", "type": "text", "required": true, "options": { "min": 3, "max": 200 } },
      { "name": "slug", "type": "text", "required": true, "unique": true },
      { "name": "content", "type": "text", "required": false },
      { "name": "views", "type": "number", "options": { "min": 0 } },
      { "name": "published", "type": "bool" }
    ]
  }'
const response = await fetch('http://localhost:8090/api/moul', {
  method: 'POST',
  headers: {
    'X-Admin-Key': 'test-admin-key-1234',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'articles',
    type: 'base',
    fields: [
      { name: 'title', type: 'text', required: true },
      { name: 'slug', type: 'text', required: true, unique: true },
      { name: 'content', type: 'text' },
      { name: 'published', type: 'bool' },
    ],
    rules: {
      listRule: 'published = true',
    },
  }),
});
const collection = await response.json();
console.log('Collection created:', collection);
package main

import (
	"bytes"
	"encoding/json"
	"net/http"
)

func main() {
	payload := map[string]any{
		"name": "articles",
		"type": "base",
		"fields": []map[string]any{
			{"name": "title", "type": "text", "required": true},
			{"name": "published", "type": "bool"},
		},
	}
	body, _ := json.Marshal(payload)
	req, _ := http.NewRequest("POST", "http://localhost:8090/api/moul", bytes.NewReader(body))
	req.Header.Set("X-Admin-Key", "test-admin-key-1234")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
}

Collection Management Endpoints

  • GET /api/moul - List all dynamic collections.
  • GET /api/moul/:name - Get schema definition, field types, and rules for a collection.
  • PATCH /api/moul/:name - Update fields, schema, or access rules.
  • DELETE /api/moul/:name - Delete a collection and drop its SQLite table.

On this page