Local Data
Locally store dynamic content.
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><!-- Meta -->
<link rel="manifest" href="/manifest.json">
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/mnfst@latest/lib/manifest.min.js"
data-plugins="data"></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,command@empire.gov
contact.phone,+1-555-0100id,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.webpLocal 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 isvalue - 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 supportingx-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:
{
"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:
{
"data": {
"weather": "https://api.example.com/weather"
}
}For headers, query params, or response shaping, use an object witha 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/weather
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 (named$xto mirror Alpine'sx-directive namespace, keeping the data layer visually consistent with the rest of the framework)sourceName- Data source name frommanifest.json(e.g.,team,features,pricing)property- Object property or array namesubProperty- Nested property (optional at any level)
Examples:
$x.team- Access theteamdata source$x.team.managers- Access themanagersarray or object$x.team.managers[0].name- Display the first manager's name (using a JS index counter)$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>{
"managers": [
{
"name": "Darth Vader",
"role": "Lord",
"image": "/assets/examples/vader.webp"
},
...
]
}HTML
Use Alpine's x-html for content that includes HTML tags:
<div x-html="$x.team.managers[0].content"></div>{
"managers": [
{
"name": "Darth Vader",
"role": "Lord",
"image": "/assets/examples/vader.webp"
"content": "<p>Dark Lord of the Sith with <strong>unlimited power</strong>.</p>"
},
...
]
}Attributes
Use Alpine's x-bind to bind data to HTML attributes:
<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>. Use data-hydrate on any wrapper to preserve that subtree as-is during prerender transforms, so Alpine hydrates it 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>{
"managers": [
{
"path": "darth-vader",
"name": "Darth Vader",
"role": "Lord",
"image": "/assets/examples/vader.webp"
},
{
"path": "admiral-piett",
"name": "Admiral Piett",
"role": "Fleet Commander",
"image": "/assets/examples/piett.webp"
}
]
}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-vaderor/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
<!-- 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
<!-- 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
<!-- Find specific team member -->
<div x-text="$x.team.managers.find(p => p.role === 'Lord')?.name || 'Not found'"></div>Other Methods
Data sources support all standard JavaScript array methods:
Transformation:
map()- Transform each itemfilter()- Filter items by conditionreduce()- Reduce to a single valueslice()- Extract a portion of the array
Search:
find()- Find first matching itemfindIndex()- Find index of first matching itemincludes()- Check if array includes valueindexOf()- Find index of value
Iteration:
forEach()- Execute function for each item
Testing:
some()- Check if any item matchesevery()- Check if all items match
Modification:
push()- Add item to endpop()- Remove item from endshift()- Remove item from startunshift()- Add item to startsplice()- Add/remove items at indexconcat()- Combine arraysjoin()- Join items into string
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.
Article does not exist
There is no documentation at this path.