Skip to main content
Aikeedo pages are server-rendered Twig with Alpine.js for interactivity. A plugin page gets the same building blocks: the layout’s blocks, the global API client, and the web components the core registers.

Add scripts and styles to a page

The base layout exposes styles and scripts blocks. Call {{ parent() }} so the core’s own assets keep loading:
templates/notes.twig
Including a .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:
Core snippets are includable too, such as {% include "/snippets/spinner.twig" %}.
Match the surrounding markup: box, button, button-accent, input and label are the core’s Tailwind component classes, so your page inherits the admin and app styling, including dark mode.

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
The installer copies those files to public/e/{vendor}/{name}/, so they’re served from /e/acme/notes/.... Read the manifest to resolve the hashed filenames:
For local development, let an environment variable point at your dev server and fall back to the published files:
See Public assets for the copy rules.