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 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:
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 prefixsourceName- 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$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-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
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 itemfilter()— filter items by conditionreduce()/reduceRight()— reduce to a single valueslice()— extract a portion of the arrayconcat()— combine with another arrayflat()/flatMap()— flatten nested arrayssort()/reverse()— return ordered copiesjoin()— convert to string
Search:
find()/findIndex()— locate the first matching item / its indexincludes()— check whether the array includes a valueindexOf()/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.
Article does not exist
There is no documentation at this path.