Skip to content

Documentation

fields Component

Documentation for the dynamic field renderer with widget loading, validation store integration, and revision history.

3/27/2026
9 min read Edit on GitHub

The fields component is responsible for rendering collection fields dynamically, loading appropriate widgets, handling validation, and managing revision history.


Collection Entry Editing Ecosystem

The fields component is part of a larger system for editing collection entries. The following components work together:

flowchart TB subgraph Header["Header Actions"] HE[header-edit
Mobile] RS[right-sidebar
Desktop] end subgraph Content["Content Editing"] F[fields Component] WL[widget-loader] W[Widgets] end subgraph Support["Supporting Components"] TS[translation-status] TP[TokenPicker] REV[Revisions] end subgraph Stores["State Stores"] VS[validationStore] CV[collectionValue] DC[dataChangeStore] TRP[translationProgress] end HE --> VS RS --> VS F --> WL WL --> W W --> CV W --> VS F --> REV TS --> TRP TP --> W CV --> DC

Component Roles

Component Path Role
header-edit src/components/header-edit.svelte Mobile save/cancel/delete actions, validation gating
right-sidebar src/components/right-sidebar.svelte Desktop save/status/scheduling actions
fields src/components/collection-display/fields.svelte Dynamic field rendering, validation
widget-loader src/components/collection-display/widget-loader.svelte Async widget loading with error handling
translation-status src/components/collection-display/translation-status.svelte Translation progress per language
TokenPicker src/components/TokenPicker.svelte Dynamic token insertion for fields
Revisions (within fields) Historical snapshots with revert capability

👥 Collaborative Editing (Phase 2 - Yjs Integration)

SveltyCMS implements real-time collaborative editing using Yjs CRDTs. This ensures that multiple users can edit the same entry simultaneously without data loss or conflicts.

Collaborative Architecture

The collaborative system uses a Single Source of Truth (SSoT) model during the editing session, synchronized via a WebSocket provider (e.g., Hocuspocus).

flowchart TB
    subgraph Clients["Concurrent Editors"]
        C1[User A]
        C2[User B]
    end

    subgraph Sync["Synchronization Layer"]
        P[Yjs Provider<br/>WebSocket/Hocuspocus]
        YD[Y.Doc<br/>Shared Document State]
        AW[Awareness<br/>Cursors/Presence]
    end

    subgraph CMS["SveltyCMS Core"]
        F[fields Component]
        CV[collectionValue Store]
        DB[(Database)]
    end

    C1 <--> P
    C2 <--> P
    P <--> YD
    YD <--> AW
    YD --"Reconciles"--> F
    F --"Updates"--> CV
    CV --"Saves (Debounced)"--> DB

Key Components of Collaboration

Feature Implementation Description
Shared State Y.Doc A Conflict-free Replicated Data Type (CRDT) document that holds all entry fields.
Presence Awareness Tracks remote cursors, active users, and field-level focus highlights.
Sync Provider Hocuspocus The recommended enterprise WebSocket provider for synchronizing Yjs docs with the server.
Widget Binding y-svelte Widgets bind their inputs directly to the Y.Doc sub-types (e.g., Y.Text for inputs, Y.Xml for RichText).

Data Flow for Real-time Sync

  1. Initialization: When an entry is opened, fields.svelte initializes a Y.Doc and connects to the provider.
  2. Local Change: A user types in a widget. The widget updates its corresponding sub-type in the Y.Doc.
  3. Propagation: Yjs automatically propagates the delta to all other connected clients and the server.
  4. Reconciliation: fields.svelte watches the Y.Doc for changes and updates the global collectionValue store to maintain compatibility with non-collaborative parts of the system (like Save actions).
  5. Awareness: Remote cursors are rendered using absolute positioning relative to the field containers, providing visual feedback of where others are working.

Architecture

flowchart TB
    subgraph fields["fields Component"]
        direction TB
        FD[Field Definitions]
        WL[Widget Loader]
        VL[Validation Logic]
        RH[Revision History]
    end

    subgraph Widgets["Widget System"]
        TW[Text Widget]
        RTW[RichText Widget]
        MW[Media Widget]
        RW[Relation Widget]
        CW[Custom Widgets]
    end

    subgraph Stores["State Stores"]
        CV[collectionValue]
        VS[validationStore]
        TP[translationProgress]
        DC[dataChangeStore]
    end

    FD --> WL
    WL --> Widgets
    Widgets --> CV
    CV --> VL
    VL --> VS
    VS --> DC
    RH --> CV

Features

Feature Description
Dynamic Widget Loading Lazy-loads widgets based on field type using widget-loader
Validation Store Integration Real-time field validation with validationStore
Translation Progress Visual indicators for multilingual content completion
Revision History View and revert to previous versions
Role-Based Filtering Show/hide fields based on user permissions
Two-Way Binding Sync between local and global state

header-edit & right-sidebar (Save Actions)

The save functionality is split between mobile and desktop views:

header-edit (Mobile)

Visible on screens < 1024px. Provides:

  • Save Button: Disabled when validationStore.isValid === false
  • Cancel/Delete: With confirmation modals
  • Clone: Duplicate entry functionality
  • Scheduling: Publish/unpublish scheduling
// Save validation gating
const canSave = $derived(validationStore.isValid && dataChangeStore.hasChanges);

right-sidebar (Desktop)

Visible on screens >= 1024px. Additionally provides:

  • Entry Status: Draft/Published/Scheduled
  • Metadata Display: Created/Updated timestamps
  • Scheduling Panel: Date/time pickers for publish schedule

Both components integrate with validationStore to prevent saving invalid entries.


translation-status

Displays translation completion progress for multilingual collections:

flowchart LR
    F[fields] --> TS[translation-status]
    TS --> |tracks| TP[translationProgress]
    TP[translationProgress]
    TS --> |displays| Languages
    Languages --> Progress[% Complete]

Features {#features-2}

  • Per-Language Progress: Shows completion % for each language
  • Field-Level Tracking: Tracks individual translatable fields
  • Widget-Aware: Handles complex widgets (e.g., SEO) with nested language data
  • Language Switcher: Click to switch content language

Progress Calculation

// Translation progress per language
translationProgress[language] = {
  total: new SvelteSet<string>(), // All translatable fields
  translated: new SvelteSet<string>(), // Completed translations
};

// Progress percentage
const progress = (translated.size / total.size) * 100;

TokenPicker

A floating panel for inserting dynamic tokens into input fields:

Features {#features-3}

  • Token Categories: Entry, User, Site, System tokens
  • Modifier Support: date, upper, lower, truncate, etc.
  • Live Preview: Shows resolved token value
  • Smart Detection: Detects existing tokens in active input
  • Draggable Window: Repositionable UI

Usage

Tokens are inserted with {{ }} syntax:

{
  {
    entry: title;
  }
} // Simple token
{
  {
    entry: date | date(short);
  }
} // With modifier
{
  {
    user: displayName | upper;
  }
} // Uppercase modifier

Validation Integration

The fields component integrates with the global validationStore for real-time validation feedback.

Validation Flow

sequenceDiagram
    participant F as fields
    participant W as Widget
    participant VS as validationStore
    participant UI as Save Button

    F->>W: Render with field config
    W->>W: User input
    W->>VS: setError(fieldName, message)
    VS->>UI: isValid = false
    Note over UI: Save disabled

    W->>W: User fixes input
    W->>VS: clearError(fieldName)
    VS->>UI: isValid = true
    Note over UI: Save enabled

Required Field Validation

The component automatically validates required fields:

$effect(() => {
  const values = currentCollectionValue;

  filteredfields.forEach((field) => {
    if (field.required) {
      const fieldName = getFieldName(field, false);
      const value = values[fieldName];

      const isEmpty =
        value === null ||
        value === undefined ||
        (typeof value === "string" && value.trim() === "") ||
        (Array.isArray(value) && value.length === 0);

      if (isEmpty) {
        validationStore.setError(fieldName, `${field.label || fieldName} is required`);
      } else {
        validationStore.clearError(fieldName);
      }
    }
  });
});

Widget-Level Validation

Individual widgets can implement their own validation using Valibot schemas:

// In widget input.svelte
import { validationStore } from "@stores/store.svelte";
import { parse } from "valibot";

function validateInput() {
  try {
    parse(validationSchema, currentValue);
    validationStore.clearError(fieldName);
  } catch (error) {
    const message = error.issues?.[0]?.message || "Invalid input";
    validationStore.setError(fieldName, message);
  }
}

Widget Loading Flow

sequenceDiagram
    participant F as fields
    participant WL as widget-loader
    participant CL as ComponentLoader (import)
    participant W as Widget Component

    F->>WL: Render field
    WL->>CL: Execute dynamic import()
    CL-->>WL: Resolve Svelte Component
    WL->>W: Mount with props
    W->>F: bind:value updates

widget-loader Error Handling

The widget-loader component handles loading failures gracefully:

  • Loading State: Shows Native UI while widget loads
  • Error State: Displays error with retry button
  • Fallback State: Shows warning for unavailable widgets

Props

interface fieldsProps {
  fields: FieldDefinition[];
  collection: Collection;
  entry: Entry;
  entryId: string;
  collectionValue: ValueStore;
  revisions?: Revision[];
  contentLanguage?: string;
}

Tabs

The component uses a tabbed interface with up to 4 tabs (conditional based on settings):

Tab 0: Edit

The primary editing interface with all field widgets.

Tab 1: Revisions

Only visible if collection.revision is enabled. Shows historical snapshots with:

  • Version timeline
  • Compare functionality
  • Revert capability

Tab 2+: Live Preview (Editable Website plugin)

When the Editable Website & Live Preview plugin is enabled on a collection (plugins: ["editable-website"] and livePreview configured), a Live Preview tab appears via the entry_edit injection zone.

Requires a marketplace license (€14.99) or active 14-day trial.

Features:

  • Signed preview URL via POST /api/preview/authorize
  • Bidirectional sync — CMS edits push to iframe; preview can send svelty:save back to the form
  • Click-to-edit — click data-svelty-field elements in the preview to focus CMS fields
  • Device preview — desktop / tablet / mobile widths
  • Copy URL and open in new tab

Works with the optional SvelteKit Site Starter (routes/(site)) or any external frontend implementing the protocol.

Note

For handshake security, postMessage types, and external frontend setup, see Live Preview Architecture.

Tab 3: API (Admin Only)

Only visible for admin users. Shows:

  • API URL for the current entry: /api/collection/{collectionId}/{entryId}
  • Copy button for the URL
  • Raw JSON view of the entry data

Plugin Extensibility

The fields component supports plugin extensions through the entry_edit injection zone, allowing plugins to add custom tabs and functionality to the entry editing interface.

Plugin Slots

Plugins can register components in the entry_edit zone to:

  • Add Custom Tabs: SEO optimization, AI content generation, preview tools
  • Inject UI Elements: Custom actions, metadata panels, workflow controls
  • Extend Functionality: Third-party integrations, analytics, custom validators

Example Plugin Integration

// Plugin registering a custom SEO tab
import type { Plugin } from "@src/plugins/types";
import SEOTab from "./SEOTab.svelte";

export const seoPlugin: Plugin = {
  metadata: {
    id: "seo-optimizer",
    name: "SEO Optimizer",
    version: "1.0.0",
    enabled: true,
  },
  ui: {
    slots: [
      {
        zone: "entry_edit",
        slot: "tabs",
        component: SEOTab,
        order: 100,
      },
    ],
  },
};

Available Slots in entry_edit Zone

Slot Name Location Use Case
tabs Additional tabs after Edit/Revisions/Preview Custom editing interfaces
header_actions Header toolbar area Quick actions, status controls
field_extensions Below field groups Field-level enhancements
Tip

For complete plugin development guide, see Plugin Architecture.


Best Practices

  1. Field Configuration: Always set db_fieldName for consistent validation keys
  2. Required fields: Use required: true for mandatory fields (handled automatically)
  3. Custom Validation: Implement Valibot schemas in widget index.ts for type-specific validation
  4. Error Messages: Provide clear, user-friendly validation messages
  5. Permissions: Use permissions field config to control access per role
  6. Token Support: Use TokenPicker for dynamic content like slugs, dates, metadata

Related Documentation

componentsfieldswidgetsvalidationrevisions
Was this page helpful?