Designing Your Content Schema

A schema in FoxNose is the blueprint for your content. It defines what fields your resources have, what type of data each field accepts, and how that data should be validated. Well-designed schemas are essential for both data quality and effective search.


Core Concepts

Schemas Live in Folders

Every collection folder has its own schema that defines the structure of resources within it. When you create a folder, you define its schema by adding fields—think of it like designing a database table, but with built-in support for search, localization, and AI.

Schema Versioning

Schemas are versioned, allowing you to evolve your data model safely:

  1. Create a draft version — Start with a new schema version or copy from an existing one
  2. Add and configure fields — Define your content structure
  3. Publish the version — Make it active and start creating content

Once published, a schema version is locked. To make changes, create a new draft version, modify it, then publish. This ensures your existing content always matches a known schema.


Field Types

FoxNose provides a comprehensive set of field types to model any content structure.

Text Fields

TypeUse CaseMax LengthVectorizable
stringTitles, names, short text255 charsYes
textArticles, descriptions, long contentUnlimitedYes

When to use each:

  • Use string for titles, labels, identifiers, and any short text
  • Use text for article bodies, descriptions, and content you want to search

Both types support vectorizable: true for semantic search.

Numeric Fields

TypeUse CaseExample
integerCounts, IDs, whole numbers42, -10, 0
numberPrices, ratings, measurements19.99, 4.5, 0.001

Both support minimum, maximum, and enum validation.

Boolean

Simple true/false flags:

{
  "key": "is_published",
  "type": "boolean",
  "required": true
}

Date and Time

TypeFormatExample
dateYYYY-MM-DD2024-03-15
timeHH:MM:SS[.SSS]Z14:30:00Z
datetimeISO 8601 (UTC)2024-03-15T14:30:00Z

All date/time types support from and to range validation.

JSON

For arbitrary structured data when you need flexibility:

{
  "key": "metadata",
  "type": "json"
}

Relationship Fields

Connect resources across folders:

TypeCardinalityUse Case
referenceOne-to-oneLink to a single resource (author, category)
relationOne-to-manyLink to multiple resources (tags, related articles)
// Single author reference
{
  "key": "author",
  "type": "reference",
  "meta": { "target": "8xk2m4pq" }
}

// Multiple tags
{
  "key": "tags",
  "type": "relation",
  "meta": { "target": "t9n3v7ws", "max_items": 10 }
}

The target value is the folder's unique key, which you can find in the dashboard or via the Management API.

Reference field features:

Constraints:

  • Reference fields cannot be placed inside object arrays (objects with multiple: true). This ensures data integrity for join operations.

Structural Fields

TypeUse Case
objectGroup related fields together
nestedEmbed a component schema (components only)

Objects let you create hierarchical field structures:

// Parent object
{
  "key": "seo",
  "type": "object"
}

// Child fields (with parent: "seo")
{
  "key": "title",
  "type": "string",
  "parent": "seo"
}
{
  "key": "description",
  "type": "text",
  "parent": "seo"
}

This creates a nested structure: seo.title, seo.description.


Field Configuration

Beyond type, fields have configuration options that control their behavior.

Making Fields Searchable

FoxNose supports two types of search, controlled by field flags:

FlagSearch TypeUse Case
searchable: trueFiltering and full-text searchUsing filters and keyword matching with typo tolerance
vectorizable: trueSemantic searchAI-powered meaning-based search

You can enable both on the same field:

{
  "key": "summary",
  "type": "text",
  "searchable": true,    // Enables keyword search
  "vectorizable": true   // Enables semantic search
}

This lets you use hybrid search—combining exact keyword matches with semantic understanding.

Custom embeddings: If you generate embeddings externally (e.g., via OpenAI or Cohere), use a vector type field instead. Store your embedding arrays directly in resources and query them via Vector Field Search.

Localization

Mark fields as localizable: true to store translations:

{
  "key": "title",
  "type": "string",
  "localizable": true
}

Localized fields store values per language:

{
  "title": {
    "en": "Introduction to AI",
    "fr": "Introduction à l'IA",
    "de": "Einführung in KI"
  }
}

See Managing Multilingual Content for the full localization workflow.

Validation

Control what values are accepted:

String validation:

{
  "key": "email",
  "type": "string",
  "meta": {
    "format": "email",           // Built-in format
    "max_length": 255
  }
}

{
  "key": "slug",
  "type": "string",
  "meta": {
    "pattern": "^[a-z0-9-]+$",   // Custom regex
    "max_length": 100
  }
}

{
  "key": "status",
  "type": "string",
  "meta": {
    "enum": ["draft", "review", "published"]  // Allowed values
  }
}

Available string formats: email, hostname, uuid, ipv4, ipv6, uri, uri-reference

Numeric validation:

{
  "key": "rating",
  "type": "number",
  "meta": {
    "minimum": 0,
    "maximum": 5,
    "multiple_of": 0.5   // Only 0, 0.5, 1, 1.5, etc.
  }
}

Date range validation:

{
  "key": "event_date",
  "type": "date",
  "meta": {
    "from": "2024-01-01",
    "to": "2025-12-31"
  }
}

Arrays (Multiple Values)

Any field can accept arrays with multiple: true:

{
  "key": "keywords",
  "type": "string",
  "multiple": true,
  "meta": {
    "min_items": 1,
    "max_items": 10,
    "unique_items": true
  }
}

Private Fields

Hide fields from the Flux API with private: true:

{
  "key": "internal_notes",
  "type": "text",
  "private": true
}

Private fields are only accessible through the Management API. Use them for editorial notes, internal IDs, or any data that shouldn't be public.

Required and Nullable

FlagMeaning
required: trueField must have a value when creating/updating
nullable: trueField explicitly accepts null as a value

Common Schema Patterns

Blog Article

Fields:
├── title (string, required, searchable, localizable)
├── slug (string, required, format: pattern)
├── summary (text, vectorizable, localizable)
├── body (text, searchable, vectorizable, localizable)
├── author (reference → authors folder)
├── tags (relation → tags folder)
├── category (string, enum: ["tech", "business", "lifestyle"])
├── published_at (datetime)
├── seo (object)
│   ├── meta_title (string, localizable)
│   └── meta_description (text, localizable)
└── internal_notes (text, private)

Product Catalog

Fields:
├── name (string, required, searchable, localizable)
├── description (text, vectorizable, localizable)
├── sku (string, required, unique pattern)
├── price (number, minimum: 0)
├── category (reference → categories folder)
├── tags (relation → tags folder)
├── specs (object)
│   ├── weight (number)
│   ├── dimensions (string)
│   └── material (string)
├── images (string, multiple, format: uri)
└── in_stock (boolean)

FAQ / Knowledge Base

Optimized for RAG and AI agents:

Fields:
├── question (string, required, searchable, vectorizable)
├── answer (text, required, searchable, vectorizable)
├── category (string, enum: [...])
├── keywords (string, multiple, searchable)
└── last_verified (date)

Why this works for AI:

  • Both question and answer are vectorizable—queries can match either
  • Keywords provide additional search surface
  • Category enables filtered search ("Find AI answers about billing")

Schema Evolution

Safe Changes

These changes won't break existing content:

  • Adding optional fields (without required: true)
  • Increasing max_length or max_items
  • Removing enum values (if no content uses them)
  • Adding new enum values
  • Making a required field optional

Breaking Changes

These require data migration:

  • Adding required fields (existing content won't have values)
  • Decreasing max_length (existing values might exceed)
  • Changing field types
  • Removing fields (consider deprecating first)

Migration Strategy

  1. Create a new draft schema version
  2. Add new optional fields
  3. Publish the new version
  4. Backfill data for new required fields via Management API
  5. Update field to required in next version if needed

Next Steps

Was this page helpful?