Core Plugins

Local Data

Load YAML, JSON, or CSV files as reactive data sources.


Overview

Local data consists of CSV, JSON, or YAML files in your project directory. Their content can be used to organize and populate UI content. Files are loaded on-demand and cached in memory until the page reloads.


Setup

Data support is included in manifest.js with all core plugins, or can be selectively loaded. manifest.json is required to register data sources.

<!-- Meta -->
<link rel="manifest" href="/manifest.json">

<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/mnfst@latest/lib/manifest.min.js"></script>

Create Local Data

Create CSV, JSON, or YAML files anywhere in your project directory. Each format works identically—choose based on preference.

key,value
headquarters.name,Empire Headquarters
headquarters.location,Death Star
contact.email,forcechoke69@aol.com
contact.phone,+1-555-0100
id,name,role,image
1,Darth Vader,Lord,/assets/examples/vader.webp
2,Admiral Piett,Fleet Commander,/assets/examples/piett.webp
[
    {
        "name": "Darth Vader",
        "role": "Lord",
        "image": "/assets/examples/vader.webp"
    },
    {
        "name": "Admiral Piett", 
        "role": "Fleet Commander",
        "image": "/assets/examples/piett.webp"
    }
]
-   name: Darth Vader
    role: Lord
    image: /assets/examples/vader.webp
-   name: Admiral Piett
    role: Fleet Commander
    image: /assets/examples/piett.webp

Local files can use any structure - arrays, objects, or nested combinations. See localization for details on language-specific data sources.

CSV Formatting

CSV files support two parsing modes, automatically detected based on structure.

Key-Value Mode (nested object):

  • First column is key, second column is value
  • Supports dot notation for nesting (contact.name{ contact: { name: "..." } })
  • Returns a nested object structure
  • Use for structured configuration or hierarchical data
  • List-like (array of objects) use numeric path segments, e.g. features.0.name, features.0.description, features.1.name, features.1.description, interpreted as a real array and supporting x-for="item in $x.sourceName.features" in Alpine

Tabular Mode (array of objects):

  • First column header is id (case-insensitive)
  • Returns an array of objects, one per row
  • Use for lists of similar items (team members, products, etc.)

CSV files can also include locale columns for multilingual content. See localization for details.


Register Local Data

Register local data in the project's manifest.json. Under the data property, declare each file with its custom filepath from the project root:

manifest.json
{ "data": { "team": "/data/team.json", "contact": "/data/contact.csv" } }

API Sources

Register an HTTP endpoint as a data source the same way as a file. Currently read-only, only GET requests are fully supported. For full CRUD with realtime sync, use Appwrite databases.

For an endpoint with no auth or transformation, register the URL string directly:

manifest.json
{ "data": { "weather": "https://api.example.com/weather" } }

For headers, query params, or response shaping, use an object with git-ignored .env variable references as needed:

{
    "data": {
        "products": {
            "url": "${API_BASE_URL}/products",
            "headers": {
                "Authorization": "Bearer ${API_TOKEN}",
                "Content-Type": "application/json"
            },
            "params": {
                "limit": 50,
                "status": "active"
            },
            "transform": "data.products",
            "defaultValue": []
        }
    }
}
API_BASE_URL=https://api.example.com
API_TOKEN=sk_1234567890abcdef
Property Required Default Description
url Yes Endpoint URL
headers No {} Request headers for auth and content type
params No {} Query parameters appended to the URL
transform No Dot-notation path to extract nested data (e.g. "data.products" unwraps { data: { products: [...] } })
defaultValue No [] Fallback if the request fails

API sources behave the same as local sources, accessed via $x.sourceName with the same loading/error/ready state and $search / $query / $route helpers (see Display Content below).


Display Content

Data sources are accessed in HTML using our $x magic method with dot notation. The structure follows this pattern:

$x.sourceName.property.subProperty

Structure breakdown:

  • $x - Magic method prefix
  • sourceName - Data source name from manifest.json (e.g., team, features, pricing)
  • property - Object property or array name
  • subProperty - Nested property (optional at any level)

Examples:

  • $x.team - Access the team data source
  • $x.team.managers - Access the managers array or object
  • $x.team.managers[0].name - Display the first manager's name
  • $x.team.filter(p => p.role === 'Junior Vice President') - Filter team members by role

Text

Use Alpine's x-text to display text from data sources:

<h4 x-text="$x.team.managers[0].name"></h4>
<p x-text="$x.team.managers[0].role"></p>

HTML

Use Alpine's x-html for content that includes HTML tags:

<div x-html="$x.team.managers[0].content"></div>

Attributes

Use Alpine's x-bind to bind data to HTML attributes:

HTML
<img :src="$x.team.managers[0].image" :alt="$x.team.managers[0].name">
<a :href="$x.headquarters.contact.email">Contact</a>

Lists

Use Alpine's x-for in a template to iterate through data arrays:


<template x-for="person in $x.team.managers" :key="person.name">
    <div class="card">
        <img :src="person.image" :alt="person.name">
        <div>
            <p x-text="person.name"></p>
            <small x-text="person.role"></small>
        </div>
    </div>
</template>

The <template> tag (which can only have one child element) creates a loop through the data source array. Use x-for="item in $x.sourceName" where item is an arbitrary name for the current loop item.

Prerender (SEO): Static lists are kept in prerendered HTML for crawlers. The prerender script collapses lists it infers are dynamic (search/query, URL params, auth, or getter names like filteredTeam), or you can force collapse with data-prerender="dynamic" on the <template>.

<template x-for> iterating data-source values (e.g. group in $x.docs) defaults to client-side rendering — the baked clones are stripped and Alpine re-renders the list at runtime — so locale switches and other reactive updates work without producing duplicates. To freeze that list into the static HTML for SEO instead, add data-static to the <template> or any ancestor. Manifest will keep the baked clones, remove the template so Alpine has nothing left to iterate, and strip per-item loop bindings (the resolved values are already in the DOM). Element-level bindings that reference global state (e.g. :class using $x.foo.$route('path')) remain live; only the iteration is frozen.

data-static extends to other prerender-time templates too — anything that emits its rendered output as DOM siblings, such as <template x-anchors> for table-of-contents links. Without data-static, the runtime plugin re-runs and produces a duplicate render alongside the baked one. Wrapping the template in [data-static] removes the source <template> after prerender, so the runtime plugin sees nothing to render and the baked output stands alone.

Use data-hydrate on any wrapper to preserve a subtree as-is during prerender transforms — bindings stay attached and Alpine hydrates them at runtime.


Search & Query

Use $search for real-time text filtering and $query for advanced filtering, sorting, and pagination. Both methods work client-side on data already loaded in the browser.


<div x-data="{ 
    searchTerm: '', 
    sortBy: 'name',
    get filteredTeam() {
        if (!$x.team) return [];
        let results = this.searchTerm 
            ? $x.team.$search(this.searchTerm, 'name', 'role')
            : $x.team;
        return this.sortBy !== 'all' 
            ? $x.team.$query([['orderAsc', this.sortBy]])
            : results;
    }
}">
    <!-- Search Input -->
    <input 
        type="text" 
        placeholder="Search team members..." 
        x-model="searchTerm"
    />
    
    <!-- Sort Buttons -->
    <button @click="sortBy = 'name'"> Sort by Name </button>
    <button @click="sortBy = 'role'"> Sort by Role </button>
    <button @click="sortBy = 'all'; searchTerm = ''"> Reset </button>
    
    <!-- Results List -->
    <template x-for="person in filteredTeam" :key="person.name">
        <div>
            <p x-text="person.name"></p>
            <small x-text="person.role"></small>
        </div>
    </template>
    <small x-show="searchTerm && filteredTeam.length === 0">No team members found</small>
</div>

Both $search and $query operate client-side (in the browser) for local data sources:

  • $search(term, ...attributes): Real-time text filtering across specified attributes. Returns filtered array immediately.
  • $query(queries): Advanced filtering, sorting, and pagination using query arrays. Processes data in browser.

For cloud-hosted data with backend filtering, see Appwrite databases.

Query Syntax

Each query is an array with the format ['method', 'attribute', 'value']. Use these patterns:

// Comparison operators
['equal', 'role', 'Lord']                    // role equals 'Lord'
['notEqual', 'role', 'Commander']            // role does not equal 'Commander'
['greaterThan', 'priority', 5]              // priority greater than 5
['greaterThanOrEqual', 'priority', 5]       // priority greater than or equal to 5
['lessThan', 'priority', 10]                // priority less than 10
['lessThanOrEqual', 'priority', 10]        // priority less than or equal to 10
['between', 'priority', 5, 10]              // priority between 5 and 10 (inclusive)

// Null checks
['isNull', 'deletedAt']                     // deletedAt is null
['isNotNull', 'email']                       // email is not null

// String operations
['contains', 'name', 'Vader']               // name contains 'Vader' (case-insensitive)
['startsWith', 'name', 'Darth']             // name starts with 'Darth' (case-insensitive)
['endsWith', 'name', 'Vader']               // name ends with 'Vader' (case-insensitive)

// Sorting
['orderAsc', 'name']                        // Sort ascending by name
['orderDesc', 'name']                       // Sort descending by name
['orderRandom']                             // Random order

// Pagination
['limit', 10]                               // Return maximum 10 results
['offset', 20]                              // Skip first 20 results

// Combine multiple queries (all applied together)
[
    ['equal', 'role', 'Lord'],
    ['orderAsc', 'name'],
    ['limit', 5]
]
<!-- Filter, sort, limit: processes data in browser (client-side) -->
<button @click="$x.team.$query([
    ['equal', 'role', 'Lord'],
    ['orderAsc', 'name']
])">Show Lords Only</button>

<!-- Multiple filters -->
<button @click="$x.team.$query([
    ['contains', 'name', 'Vader'],
    ['orderDesc', 'name'],
    ['limit', 5]
])">Top 5 Vader Matches</button>

Route-Specific

Use the $route() function to find content based on the current URL path like /team/darth-vader:

<h1 x-text="$x.team.managers.$route('path').name"></h1>
<p x-text="$x.team.managers.$route('path').role"></p>

The $route('path') function searches the collection for an item where the specified property (e.g., path) matches any segment of the current URL path. When found, it returns a reactive proxy to that item, allowing access to its properties.

How it works:

  • Compares the property value against URL path segments (e.g., /team/darth-vader or /team/mgmt/darth-vader/bio → matches "darth-vader")
  • Automatically filters out language codes from the path (e.g., /fr/team/darth-vader → matches "darth-vader")
  • Searches recursively through nested arrays and objects
  • Returns a reactive proxy that updates when the URL changes
  • Returns empty values if no match is found (prevents errors)

Array Methods

Data sources support all standard JavaScript array methods for filtering, mapping, and transforming data:

Filter

HTML
<!-- Show only team members with "Lord" role -->
<template x-for="person in $x.team.managers.filter(p => p.role === 'Lord')" :key="person.name">
    <div x-text="person.name"></div>
</template>

Map

HTML
<!-- Transform team data to display names only -->
<template x-for="name in $x.team.managers.map(p => p.name)" :key="name">
    <div x-text="name"></div>
</template>

Find

HTML
<!-- Find specific team member -->
<div x-text="$x.team.managers.find(p => p.role === 'Lord')?.name || 'Not found'"></div>

Other Methods

Data sources support standard JavaScript array methods:

Transformation (return new array / value):

  • map() — transform each item
  • filter() — filter items by condition
  • reduce() / reduceRight() — reduce to a single value
  • slice() — extract a portion of the array
  • concat() — combine with another array
  • flat() / flatMap() — flatten nested arrays
  • sort() / reverse() — return ordered copies
  • join() — convert to string

Search:

  • find() / findIndex() — locate the first matching item / its index
  • includes() — check whether the array includes a value
  • indexOf() / lastIndexOf() — find the index of a value

Iteration:

  • forEach() — execute a function for each item

Testing:

  • some() / every() — check whether any / all items match

Modification (in-memory only):

  • push() / pop() / shift() / unshift() / splice()

State Properties

Data sources expose state properties for UI reactivity:

  • $x.sourceName.$loading - Boolean indicating if data is loading
  • $x.sourceName.$error - Error message string (null if no error)
  • $x.sourceName.$ready - Boolean indicating if data has loaded successfully
<!-- Loading state -->
<div x-show="$x.team.$loading">Loading team data...</div>

<!-- Error state -->
<div x-show="$x.team.$error" x-text="$x.team.$error" class="text-error"></div>

<!-- Ready state -->
<div x-show="$x.team.$ready && !$x.team.$loading">
    Team loaded: <b x-text="$x.team.length"></b> members
</div>

These properties are reactive and update automatically as data loads or errors occur.


Safe Async

The $try magic wraps an async callback in try/catch and optionally routes the error message to a named property on the current x-data scope. Useful when calling cloud-data mutations or any other async operation that can fail.

<div x-data="{ saveError: null }">
    <button @click="$try(() => $x.products.$create({ name: 'New' }), 'saveError')">
        Save
    </button>
    <small x-show="saveError" x-text="saveError" class="text-error"></small>
</div>
Argument Description
fn Async callback to await. The callback's result is returned on success
errorVar (optional) Name of a property on the current x-data scope. On error, the error message is written there; on success it's cleared to null

On error, $try returns undefined instead of throwing.