Search by

cordbolte / statamic-alpine

cordbolte

Pass Statamic data to Alpine.js (or any JavaScript) as JSON: select fields, join relations, add Glide images and rendered HTML.

Package info

github.com/cordbolte/statamic-alpine

Type:statamic-addon

pkg:composer/cordbolte/statamic-alpine

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-11-04 13:43 UTC

This package is auto-updated.

Last update: 2026-11-04 13:44:26 UTC


README

Pass Statamic data to Alpine.js as JSON, right from your Antlers templates:

<div x-data="{ posts: {{ alpine:blog limit="6" select="title|url|image" glide="image@thumb?width:400" }} }">
    <template x-for="post in posts">
        <a :href="post.url">
            <img :src="post.image.thumb" :alt="post.title">
            <span x-text="post.title"></span>
        </a>
    </template>
</div>

The alpine tag turns entries, terms, assets or any value of the context into JSON and lets you decide what goes in: only the fields you need, related entries resolved, image variants generated by Glide, rich text as HTML, even fields rendered with your own templates. The output is escaped for an HTML attribute, so it goes straight into x-data.

Despite its name, nothing about it is specific to Alpine: the output is plain JSON for any JavaScript, e.g. Vue props or a data- attribute read by your own script (see Other frameworks).

Why

Statamic offers several ways to hand data to JavaScript, each with a catch:

Way Catch
{{ value | to_json }} outputs everything, including fields that should not reach the browser; relations and images stay raw
REST API or GraphQL an extra request, an endpoint to configure and protect, and the data arrives after the page
JSON written by hand in Antlers quotes, escaping and nesting by hand, in every component

The alpine tag keeps the data in the page and gives you control over its shape.

Requirements

  • Statamic 5 or 6
  • PHP 8.2+

Installation

composer require cordbolte/statamic-alpine

There is nothing to configure.

Tags

Tag Data
{{ alpine :src="variable" }} any value of the context: an entry, a list of entries, an array, a grid or replicator field, an asset … (alpine:data is the same)
{{ alpine:collection from="blog" }} entries of one or more collections, with every parameter of Statamic's collection tag: from, sort, limit, filter, conditions …
{{ alpine:blog }} shortcut for alpine:collection from="blog"
{{ alpine:taxonomy from="tags" }} terms of one or more taxonomies, in the current site (site picks another one), with every parameter of Statamic's taxonomy tag
{{ alpine:blog sort="date:desc" limit="6" title:contains="climate" select="title|url|date" }}
{{ alpine:taxonomy from="topics" select="title|slug|url" }}
{{ alpine :src="team" select="name|role|portrait" glide="*:portrait@small?square:200" }}

All tags take the parameters below.

Parameters

Parameter Purpose Example
select only these fields select="title|url"
ignore leave out these fields ignore="internal_notes"
expose add fields that are not output by default expose="url|author"
join resolve relations (entries, terms, users) into objects join="author|topics"
link output relations as ID and API URL link="related"
glide add image variants to assets glide="image@thumb?width:400"
bard Bard fields with sets as HTML bard="*:content"
parse render fields with a template parse="*:content@partials/rich_text"
key an object keyed by a field, instead of a list key="slug"
absolute absolute Glide URLs (true/false; default: as Glide is configured) absolute="true"

Several values are separated by |.

What the output contains

Entries, terms and assets become objects with:

  • their stored fields. Fields defined in the blueprint are augmented: a text field is its text, a date a timestamp, an asset an object, a grid or replicator a list of objects. Values without a field in the blueprint are output as stored.
  • id, slug and, in dated collections, date,
  • not the fields author, updated_by, updated_at, blueprint and super.

Values Statamic computes, like url, permalink, api_url or last_modified, are only output when you ask for them with select or expose.

Relations (entries, terms and users fields) are output as IDs, unless you join them.

Assets become objects with id, url and the fields of the asset (e.g. alt), plus any Glide variants.

Dates become Unix timestamps in seconds: new Date(post.date * 1000) in JavaScript.

Your own arrays (passed with src) are output as they are, nested values included. Only select and ignore apply to them, see below.

{{ alpine:blog limit="1" expose="url" }}
[{
    "title": "Hello",
    "content": "<p>Welcome …</p>",
    "image": { "id": "assets::photo.jpg", "url": "/assets/photo.jpg", "alt": "A photo" },
    "topics": ["climate", "energy"],
    "id": "1f7d…",
    "slug": "hello",
    "date": 1730073600,
    "url": "/blog/hello"
}]

Field paths

join, link, glide, bard and parse address fields by path:

Path Matches
image the field image of the entries (or of the top level of src)
author:portrait the field portrait within the field author
*:image every field named image, at any depth
* glide only: every asset

In a list passed with src, the position is part of the path (0:image, 1:image); use *:image there.

select, ignore and expose take plain field names and apply at every level (see below).

Selecting fields

Without select, the output contains all fields except the ones in ignore and the default exclusions above. expose adds a default exclusion back, or a computed value:

{{ alpine:blog ignore="internal_notes|seo" expose="author|url" }}

With select, the output contains only the selected fields, including computed values like url. id, slug and date are only included when selected. select applies to every level: joined entries, assets and nested objects keep only the selected fields as well, so select their fields too. Glide variants you request are always kept.

{{ alpine:blog select="title|url|author|name|image|alt" join="author" glide="image@thumb?width:400" }}
[{
    "title": "Hello",
    "url": "/blog/hello",
    "author": { "name": "Jane", "url": "/team/jane" },
    "image": { "url": "/assets/photo.jpg", "alt": "A photo", "thumb": "/img/…?w=400" }
}]

Your own arrays: select and ignore apply to their objects (not to lists) at every level, the default exclusions do not. Without these parameters, data you prepared yourself comes out unchanged.

Prefer select for anything public: without it, every field of the blueprint reaches the browser, including fields added later.

Relations

Entries, terms and users fields are output as IDs. join resolves them into objects, following the same rules as the top level:

{{ alpine:blog join="author|topics" }}
[{
    "title": "Hello",
    "author": { "name": "Jane", "id": "jane", "slug": "jane" },
    "topics": [{ "title": "Climate", "id": "topics::climate", "slug": "climate" }]
}]

Paths join deeper levels, e.g. join="author|author:company".

link outputs only the ID and API URL of each related item, e.g. to load it later. The API URL is null unless the REST API is enabled for it.

{{ alpine:blog link="related" }}
[{ "title": "Hello", "related": [{ "id": "2a9c…", "api_url": "/api/collections/blog/entries/2a9c…" }] }]

Images (Glide)

glide adds image URLs generated by Glide to assets:

glide="{field}@{name}?{param}:{value},{param}:{value}"
Part Meaning
field the asset field, as path; * for every asset
name the key of the URL in the asset object; default glide
param:value any parameter of Statamic's glide tag, e.g. width:800, square:400, fit:crop_focal, format:webp

Several variants and fields are separated by |:

{{ alpine:team select="name|portrait" glide="portrait@small?square:200|portrait@large?width:1200,format:webp" }}
[{ "name": "Jane", "portrait": { "small": "/img/…?s=200", "large": "/img/…?w=1200&fm=webp" } }]

URLs are relative unless Glide is configured with an absolute URL; absolute="true" forces absolute ones.

Rich text (Bard)

Bard fields without sets are already HTML. Bard fields with sets are a list of text and sets; bard turns them into HTML, leaving out the sets:

{{ alpine:blog select="title|content" bard="*:content" }}

To include the sets, render the field with a template (parse).

Rendered HTML (parse)

parse renders fields with an Antlers or Blade template and puts the HTML into the data, e.g. a Bard field with sets, or a card you already have a partial for:

parse="{field}@{template}"
{{ alpine:blog select="title|content" parse="*:content@partials/rich_text" }}

The template is a view name, as for Statamic's partial tag (partials/rich_text or partials.rich_text). It receives the context of the page and the field's value under the field's name:

{{# resources/views/partials/rich_text.antlers.html #}}
<div class="prose">
    {{ content }}
        {{ if type == 'text' }}{{ text }}{{ else }}{{ partial :src="'sets/' + type" }}{{ /if }}
    {{ /content }}
</div>

Line breaks and repeated spaces of the result are collapsed. An alpine tag within such a template outputs an empty list, which prevents endless nesting.

Keyed output (key)

key turns the list into an object keyed by a field of each item, e.g. for lookups in JavaScript:

{{ alpine:blog key="slug" select="slug|title" }}
{ "hello": { "slug": "hello", "title": "Hello" }, "second": { "slug": "second", "title": "Second" } }

The key field must be part of the output: with select, select it as well.

Escaping

The tag outputs JSON with HTML entities (&quot; …), ready for an attribute. The browser decodes the attribute before Alpine evaluates it:

<div x-data="{ items: {{ alpine:blog select="title" }} }">

Use it in attributes only. Within a <script> element entities are not decoded; pass the data through an attribute there as well (see below).

Other frameworks

The output is plain JSON, so any framework can use it:

{{# Vue: a prop #}}
<post-list :posts="{{ alpine:blog select="title|url" }}"></post-list>

{{# Plain JavaScript: a data attribute #}}
<div id="map" data-places="{{ alpine:places select="title|lat|lng" }}"></div>
<script>
    const places = JSON.parse(document.getElementById('map').dataset.places);
</script>

Testing

composer install
vendor/bin/phpunit

Support

Questions, bug reports and feature requests go to GitHub Issues. Support is provided there on a best-effort basis.

License

MIT, see license.