Add scripts and styles to a page
The base layout exposesstyles and scripts blocks. Call {{ parent() }} so the core’s own assets keep loading:
templates/notes.twig
.js or .css file through your Twig namespace inlines it, which keeps a small plugin to two files and no build step. For anything larger, ship a built bundle instead.
Write the Alpine component
xdata names the component the page binds to. Register it with Alpine.data() before Alpine starts:
assets/notes.js
Pass server data to the component
Render data as JSON and read it from a ref, instead of interpolating values into JavaScript:Globals you can use
Because
window.api already points at the right base, call it with a path relative to that base: window.api.get('/notes') hits /api/notes from an app page.
The client resolves with the fetch response, plus a data property holding the parsed JSON body, which is why the examples read res.data. It also handles two cases for you: a failed request shows the server’s error message as a toast and rejects with an ApiError, and a 401 sends the user back to the login page.
Reuse the core’s web components
These custom elements are registered by the core bundles, so plugin templates can use them directly:{% include "/snippets/spinner.twig" %}.
Ship a built bundle
Inlining stops making sense once you have a real frontend, for example a widget or an editor. Build with Vite, publish the output, and read the manifest from PHP.vite.config.js
composer.json
public/e/{vendor}/{name}/, so they’re served from /e/acme/notes/.... Read the manifest to resolve the hashed filenames: